One of the most common ways for attackers to escalate from Azure Cloud to on-premises environments are Azure managed machines (Azure virtual machines, Azure Arc joined systems, etc.) that either are directly domain-joined, part of MS Entra Directory Services or only have a network connection to on-premises infrastructure. Either way, the first step in this escalation is always code execution on that machine.
There are two commonly used and quite well understood means to execute code on Azure managed machines: through the „Run Command“ feature, the way intended by Microsoft to do it, and through installing a Custom Script Extension. There are others (see Azure Threat Research Matrix) that are less known, too. Usually, from a Red Teamer’s point of view, the less known a feature is, the better, as it is less likely to get you caught. For that reason this blog will introduce another way to execute commands, for which I haven’t found any blogs, detection tips or OpSec considerations yet: Machine Configurations.
Machine Configurations
Microsoft’s documentation describes the Machine configuration feature as a „native capability to audit or configure operating system settings as code for machines running in Azure and hybrid Arc-enabled machines. You can use the feature directly per-machine, or orchestrate it at scale by using Azure Policy.“
So while its root is based in compliance through Azure Policy, it also provides capabilities to configure systems „as code“, meaning with Desired State Configuration (DSC). And by design DSC can also be used to execute arbitrary code on the target system. Note that this is different from AZT301.3, as it refers to the execution through the „Desired State Configuration extension“, which is not used here and also requires a different set of permissions.
One of the most interesting things about this execution technique is that you do not need any permissions that imply code execution like these:
Microsoft.Compute/virtualMachines/write
Microsoft.Compute/virtualMachines/extensions/write
Microsoft.HybridCompute/machines/extensions/write
Microsoft.HybridCompute/machines/runcommands/write
- etc.
Instead, one single permission will grant code execution on all Azure Arc machines and on all Azure virtual machines that are set up for it: Microsoft.GuestConfiguration/guestConfigurationAssignments/write
This is not a widely used permission, only two built-in roles have it (excluding the Owner and Contributor roles of course). Both are classified as „Job function roles“ by the Azure Portal, not as „Privileged administrator roles“.
Code Execution through Machine Configuration
To actually execute code requires several steps, which are documented in more detail here. The following describes a minimal configuration to execute a command using the DSC Script resource, though it should be noted that this is not the only possible way of code execution through DSC.
First, the DSC module is created as a MOF file. The script will only write the current date and user information to a temporary file.
There are a few things here that might need clarification: firstly, this is deliberately not following DSC guidelines and uses the TestScript ScriptBlock for execution. The reason is that this block is even executed when compliance to the configuration is checked, while code in SetScript would only be executed if a configuration alternation is requested. This means, in terms of an Azure machine configuration, the corresponding assignment can only be set to „Audit“ and it is not required to configure it to „AuditAndSet“ or similar, and the code is still executed. Furthermore, as long as the TestScript directive will result in a $True
result (which is the reason for the Test-Path statement), the machine configuration will be marked as „Compliant“ in all Azure tools, hopefully making this configuration less of an eye-catcher. Finally, everything named „SecurityCompliance“ can be replaced with a name freely chosen by the user.
In the next step the created MOF file has to be bundled into a guest configuration like this.
The result will be a ZIP file „SecurityCompliance.zip“, which has then to be provided via an HTTPS link, for example on an Azure Storage Account through a SAS authenticated public blob (I will not provide the steps for this here – I’m sure you can figure it out by yourself).
After around 5 minutes, the code is executed for the first time. Due to the integration of the code into the TestScript block, it will be executed roughly every 15 minutes when the Guest Configuration service re-checks policy compliance.
Within the Azure Portal, the machine configuration looks quite inconspicuous. The only thing that stands out is the „Manual assignment“ source type. Though, if permissions allow it, it is easy enough to push the machine configuration to all Azure VMs and Azure Arc machines through an Azure policy – but that is left as an exercise to the reader.
Pre-Requisites for Azure Virtual Machines
While the Machine Configuration feature can be used out of the box for Azure Arc machines, it requires some setup for Azure virtual machines:
- a managed identity
- the installation of the extension
Microsoft.GuestConfiguration/ConfigurationForWindows
Azure Portal tells as much, but clicking “Enable…” will only lead to a page that asks you to enable some Azure Policies that will enable these prerequisites for all Azure VMs. So it’s safe to assume that if the feature is used anywhere within the subscription, or even the management group, it will be enabled for all Azure VMs.
Interestingly enough, the machine configuration can still already be deployed through the corresponding Azure PowerShell cmdlet.
It will, however, never exit the “Pending” state and is never executed. Reason being that without the Guest Configuration service, nothing is ever going to pull the configuration from the queue. On the other hand, this also means that an attacker could leave a malicious configuration in the machine’s queue, where it will be executed in the future once the pre-requisites are met. The Azure Portal will only show the failed pre-requisites information page depicted above, even if a machine configuration is already deployed and pending.
For an attacker to manually enable the pre-requisites, further permissions are required – this time permissions that would allow executing code on the machine through regular means, too:
Microsoft.Compute/virtualMachines/write
(to enable the managed identity)Microsoft.Compute/virtualMachines/read
(to enable the managed identity and to enable the Guest Configuration extension – yeah, even when using Azure REST API directly)Microsoft.Compute/virtualMachines/extensions/write
(to enable the Guest Configuration extension)
To enable the managed identity (using the Update-AzVM
cmdlet) requires more permissions, so instead the REST API is used to aim for minimal required permissions:
This command can be used to enable the Guest Configuration extension. Note that this will result in an error message without the permission Microsoft.Compute/virtualMachines/extensions/read
. The extension will still be enabled afterward though, regardless of the error message.
After both prerequisites are met, the machine configuration is executed and the test file is created.
Furthermore, even though technically all requirements are fulfilled, the Azure Portal still insists that you need to enable the corresponding Azure Policies, even after our machine configuration has successfully been deployed and was executed. Consequently, the executed configuration will not be visible within the Azure Portal until those policies are enabled:
Prevention
To disable this attack vector in Azure Arc, the following command can disable the Guest Configuration service on Azure Arc machines. The command has to be executed on every Azure Arc machine itself, as this cannot be configured through Azure (for details refer to the documentation):
With this configuration set, the machine configuration will be deployed, but will remain in the Pending state until the Guest Configuration service is re-enabled again – quite similar to the behavior of Azure VMs if the Guest Configuration extension is not installed. Again, all queued machine configurations will automatically be executed once the service is re-enabled.
Detection
There are two aspects to look at: Azure and on the machine itself. For Azure, the creation of the machine configuration is recorded in the Activity Log as a “write GuestConfigurationAssignments” event. However, no information on the executed code or the used link to the ZIP file can be found in the Portal or the event. As previously noted, a machine configuration that is deployed as “manual assignment”, could serve as an indicator of compromise though. However, the permission Microsoft.GuestConfiguration/guestConfigurationAssignments/write
also enables an attacker to delete the configuration after its execution, thus the presence of the configuration should not be relied upon.
On the machine itself, the log file C:\ProgramData\GuestConfig\arc_policy_logs\gc_agent.log
will contain information on the executed configuration and the used download link.
Debug information on the execution chain can also be found in C:\ProgramData\GuestConfig\arc_policy_logs\gc_worker.log
, though they appear to be less relevant in this case.
The DSC module is executed within a gc_worker.exe
process, so all potentially malicious code will originate from that process. The gc_worker.exe
has previously been spawned by the service binary gc_arc_service.exe
, a service called “GCArcService” (Guest Configuration Arc Service).
The MOF file, corresponding dependencies and some config options can be found in C:\ProgramData\GuestConfig\Configuration\<machine-config-name>\
respectively. However, this folder will be deleted once the Machine Configuration is deleted in Azure, so this is again an IOC that attackers are able to remove themselves.