# Az - Automation Accounts

{{#include ../../../banners/hacktricks-training.md}}

## Basic Information

Azure Automation Accounts are cloud-based services in Microsoft Azure that help **automate tasks** like resource management, configuration, and updates across Azure and on-premises environments. They provide **Runbooks** (scripts for automation that are executed), **schedules**, and **hybrid worker groups** to run automation **jobs**, enabling infrastructure as code (IaC) and process automation for improved efficiency and consistency in managing cloud resources.

### Settings

- **Credentials**: The password is only accessible within a runbook inside the automation account, they are used to **store usernames and passwords securely**.
- **Variables**: Used to store **configuration data** that can be used in runbooks. This could also be sensitive information like API keys. If the variable is **stored encrypted**, it's only available within a runbook inside the automation account.
- **Certificates**: Used to store **certificates** that can be used in runbooks.
- **Connections**: Used to store **connection information** to external services. This could contain **sensitive information**.
- **Network Access**: It can be set to **public** or **private**.

### Runbooks & Jobs

A Runbook in Azure Automation is a **script that performs tasks automatically** within your cloud environment. Runbooks can be written in PowerShell, Python, or Graphical editors. They help automate administrative tasks like VM management, patching, or compliance checks.

In the **code** located inside **Runbooks** could contains **sensitive info** (such as creds).

A **Job is an instance of a Runbook execution**. When you run a Runbook, a Job is created to track that execution. Each job includes:

- **Status**: Queued, Running, Completed, Failed, Suspended.
- **Output**: The result of the Runbook execution.
- **Start and End Time**: When the job started and completed.

A job contains the **output** of the **Runbook** execution. If you can **read** the **jobs**, do it as they **contain** the **output** of the run (potential **sensitive info**).

### Schedules & Webhooks

There are 3 main ways to execute a Runbook:

- **Schedules**: These are used to **trigger** Runbooks at a **specific time** or **interval**.
- **Webhooks**: These are **HTTP endpoints** that can be used to **trigger** Runbooks from **external services**. Note that the webhook URL is **not visible** after creation.
- **Manual Trigger**: You can **manually trigger** a Runbook from the Azure Portal and from the cli.

### Source Control

It allows to import Runbooks from **Github, Azure Devops (Git) and Azure Devops (TFVC)**. It's possible to indicate it to publish the Runbooks of the repo to Azure Automation account and it's also possible to indicate to **sync the changes from the repo** to the Azure Automation account.

When the sync is enabled, in the **Github repository a webhook is created** to trigger the sync every time a push event occurs. Example of a webhook URL: `https://f931b47b-18c8-45a2-9d6d-0211545d8c02.webhook.eus.azure-automation.net/webhooks?token=DRjQyFiOrUtz%2fw7o23XbDpOlTe1%2bUqPQm4pQH2WBfJg%3d`

Note that these webhooks **won't be visible** when listing webhooks in the associated runbooks to the Github repo. Also note that it's **not possible to change the repo URL** of a source control once it's created.

In order for the configured source control to work, the **Azure Automation Account** needs to have a managed identity (system or user) with the **`Contributor`** role. Moreover, to assign a user managed identity to the Automation Account, it's needed to indicate the client ID of the user MI in the variable **`AUTOMATION_SC_USER_ASSIGNED_IDENTITY_ID`**.

### Runtime Environments

When creating a Runbook it's possible to select the runtime environment. By default, the following runtime environments are available:

- **Powershell 5.1**
- **Powershell 7.1**
- **PowerShell 7.2**
- **Python 3.10**
- **Python 3.8**
- **Python 2.7**

However, it's also possible to **create your own environments**, using one of these as a base. In the case of python, it's possible to upload `.whl` packages to the environment that will be used. In the case of PowerShell, it's possible to upload `.zip` packages with the modules to have in the runtime.

### Hybrid Worker Groups

In Azure Automation, the default execution environment for runbooks is the **Azure Sandbox**, a cloud-based platform managed by Azure, suitable for tasks involving Azure resources. However, this sandbox has limitations, such as restricted access to on-premises resources and constraints on execution time and resource usage. To overcome these limitations, Hybrid Worker Groups are employed. A Hybrid Worker Group consists of **one or more Hybrid Runbook Workers installed on your own machines**, whether on-premises, in other cloud environments or Azure VMs. This setup allows runbooks to execute directly on these machines, providing direct access to local resources, the ability to run longer and more resource-intensive tasks, and the flexibility to interact with environments beyond Azure's immediate reach.

When a hybrid worker group is created it's needed to indicate the **credentials** to use. There are 2 options:

- **Default credentials**: You don't need to provide the credentials and the runbooks will be executed inside the VMs as **System**.
- **Specific credentials**: You need to provide the name of the credentials object inside the automation account, which will be used to execute the **runbooks inside the VMs**. Therefore, in this case, it could be possible to **steal valid credentials** for the VMs.

Therefore, if you can choose to run a **Runbook** in a **Hybrid Worker**, you will execute **arbitrary commands** inside an external machine as **System** (nice pivot technique).

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`**).

### State Configuration (SC)

> [!WARNING]
> As indicated in [the docs](https://learn.microsoft.com/en-us/azure/automation/automation-dsc-overview), Azure Automation State Configuration will be retired on September 30, 2027 and replaced by [Azure Machine Configuration](https://learn.microsoft.com/en-us/azure/governance/machine-configuration/overview).

Automation Accounts also support **State Configuration (SC)**, which is a feature that helps **configure** and **maintain** the **state** of your VMs. It's possible to **create** and **apply** DSC configurations to **Windows** and **Linux** machines.

From an attackers perspective this was interesting because it allowed to **execute arbitrary PS code in all the configured VMs** allowing to escalate privileges to the managed identities of these VMs, potentially pivoting to new networks... Also, the configurations could contain **sensitive info**.

## Enumeration

```bash
# List Automation Accounts
az automation account list --output table

# Get Automation Account details
# Check the network access in `privateEndpointConnections` and `publicNetworkAccess`
# Check the managed identities in `identity`
az automation account show --name <AUTOMATION-ACCOUNT> --resource-group <RG-NAME>

# Get keys of automation account
## These are used for the DSC
az automation account list-keys --automation-account-name <AUTOMATION-ACCOUNT> --resource-group <RG-NAME>

# Get schedules of automation account
az automation schedule list --automation-account-name <AUTOMATION-ACCOUNT> --resource-group <RG-NAME>

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

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

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

# Get credential details
## Note that you will only be able to access the password from inside a Runbook
az rest --method GET \
  --url "https://management.azure.com/subscriptions/<subscription-id>/resourceGroups/<res-group>/providers/Microsoft.Automation/automationAccounts/<automation-account-name>/credentials/<credential-name>?api-version=2023-11-01"

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

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

# Get variables of automation account
## It's possible to get the value of unencrypted variables but not the encrypted ones
az rest --method GET \
  --url "https://management.azure.com/subscriptions/<subscription-id>/resourceGroups/<res-group>/providers/Microsoft.Automation/automationAccounts/<automation-account-name>/variables?api-version=2023-11-01"

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

# Get runbooks of an automation account
az automation runbook list --automation-account-name <AUTOMATION-ACCOUNT> --resource-group <RG-NAME>

# Get runbook details
az automation runbook show --automation-account-name <AUTOMATION-ACCOUNT> --resource-group <RG-NAME> --name <RUNBOOK-NAME>

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

# Get jobs of an automation account
az automation job list --automation-account-name <AUTOMATION-ACCOUNT> --resource-group <RG-NAME>

# Get job details
az automation job show --automation-account-name <AUTOMATION-ACCOUNT> --resource-group <RG-NAME> --name <JOB-NAME>

# Get job output
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"

# Get the Runbook content when the job was executed
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>/runbookContent?api-version=2023-11-01"

# Get webhooks inside an automation account
## It's possible to see to which runbook it belongs in the given data
## For security reasons it's not possible to see the URL of the webhook after creating it, here is a URL example: https://f931b47b-18c8-45a2-9d6d-0211545d8c02.webhook.eus.azure-automation.net/webhooks?token=dOdnxk6z7ugAxiuyUMKgPuDMav2Jw5EJediMdiN4jLo%3d
## Generating a webhook can be useful from a persistence perspective
az rest --method GET \
  --url "https://management.azure.com/subscriptions/<subscription-id>/resourceGroups/<res-group>/providers/Microsoft.Automation/automationAccounts/<automation-account-name>/webhooks?api-version=2018-06-30"

# Get the source control setting of an automation account (if any)
## inside the output it's possible to see if the autoSync is enabled, if the publishRunbook is enabled and the repo URL
az automation source-control list --automation-account-name <AUTOMATION-ACCOUNT> --resource-group <RG-NAME>

# Get custom runtime environments
## Check in defaultPackages for custom ones, by default Python envs won't have anything here and PS1 envs will have "az" and "azure cli"
az automation runtime-environment list \
    --resource-group <res-group>> \
    --automation-account-name <account-name> \
    --query "[?!(starts_with(description, 'System-generated'))]"

# Get State Configurations (SC) of an automation account
az automation dsc configuration list --automation-account-name <AUTOMATION-ACCOUNT> --resource-group <RG-NAME>

# Get State Configuration details
az automation dsc configuration show --automation-account-name <AUTOMATION-ACCOUNT> --resource-group <RG-NAME> --name <DSC-CONFIG-NAME>

# Get State Configuration content
az automation dsc configuration show-content --automation-account-name <AUTOMATION-ACCOUNT> --resource-group <RG-NAME> --name <DSC-CONFIG-NAME>

# Get hybrid worker groups for an automation account
az automation hrwg list --automation-account-name <AUTOMATION-ACCOUNT> --resource-group <RG-NAME>

# Get hybrid worker group details
az automation hrwg show --automation-account-name <AUTOMATION-ACCOUNT> --resource-group <RG-NAME> --name <HYBRID-WORKER-GROUP>

# Get more details about a hybrid worker group (like VMs inside it)
az rest --method GET --url "https://management.azure.com/subscriptions/<subscription-id>/resourceGroups/<res-group>>/providers/Microsoft.Automation/automationAccounts/<automation-account-name>/hybridRunbookWorkerGroups/<hybrid-worker-group-name>/hybridRunbookWorkers?&api-version=2021-06-22"
```

```bash
# Check user right for automation
az extension add --upgrade -n automation
az automation account list # if it doesn't return anything the user is not a part of an Automation group

# Gets Azure Automation accounts in a resource group
Get-AzAutomationAccount

# List & get DSC configs
Get-AzAutomationAccount | Get-AzAutomationDscConfiguration
Get-AzAutomationAccount | Get-AzAutomationDscConfiguration | where {$_.name -match '<name>'} | Export-AzAutomationDscConfiguration -OutputFolder . -Debug
## Automation Accounts named SecurityBaselineConfigurationWS... are there by default (not interesting)

# List & get Run books code
Get-AzAutomationAccount | Get-AzAutomationRunbook
Get-AzAutomationAccount | Get-AzAutomationRunbook | Export-AzAutomationRunbook -OutputFolder /tmp

# List credentials & variables & others
Get-AzAutomationAccount | Get-AzAutomationCredential
Get-AzAutomationAccount | Get-AzAutomationVariable
Get-AzAutomationAccount | Get-AzAutomationConnection
Get-AzAutomationAccount | Get-AzAutomationCertificate
Get-AzAutomationAccount | Get-AzAutomationSchedule
Get-AzAutomationAccount | Get-AzAutomationModule
Get-AzAutomationAccount | Get-AzAutomationPython3Package
## Exfiltrate credentials & variables and the other info loading them in a Runbook and printing them

# List hybrid workers
Get-AzAutomationHybridWorkerGroup -AutomationAccountName <AUTOMATION-ACCOUNT> -ResourceGroupName <RG-NAME>
```

## Privilege Escalation & Post Exploitation

{{#ref}}
../az-privilege-escalation/az-automation-accounts-privesc.md
{{#endref}}

## Persistence

{{#ref}}
../az-persistence/az-automation-accounts-persistence.md
{{#endref}}

## References

- [https://learn.microsoft.com/en-us/azure/automation/overview](https://learn.microsoft.com/en-us/azure/automation/overview)
- [https://learn.microsoft.com/en-us/azure/automation/automation-dsc-overview](https://learn.microsoft.com/en-us/azure/automation/automation-dsc-overview)
- [https://github.com/rootsecdev/Azure-Red-Team#runbook-automation](https://github.com/rootsecdev/Azure-Red-Team#runbook-automation)

{{#include ../../../banners/hacktricks-training.md}}
