Skip to content

Patching Bloodhound CE for Owned and Password Analysis

License

Notifications You must be signed in to change notification settings

A3-N/PatchHound

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PatchHound

Credential importer + “Owned” tagging for BloodHound Community Edition (CE).

Inspired by knavesec/Max

screenshot


TL;DR

  • auth — log in to BloodHound CE and cache a JWT (used by other commands for API).

  • patch — read a potfile (cracked creds) + NTLM file, parse, stats, and update graph nodes in Neo4j.

    • Always sets:
      • Patchhound_has_hash (bool)
      • Patchhound_has_pass (bool) – true when a cracked password is known
    • With -t/--temp also writes (temporary values):
      • Patchhound_nt – NTLM hash
      • Patchhound_pass – plaintext password
        These two are expected to be ephemeral; BloodHound CE does not persist custom node props across container restarts by default.
    • With -o/--owned after patching, discover eligible SIDs via Neo4j and then append “Owned” selectors via the BloodHound v2 HTTP API.
      • Lookups (who to own) happen only in Neo4j; the API is used only to append the Owned selectors.
  • search (optional helper) — lightweight BHCE search.

Colors can be disabled with --no-color. Verbose output via -v, I love verbose, can't miss an import.

screenshot


What’s New (this version)

  • Minimal graph writes by default: only Patchhound_has_hash and Patchhound_has_pass.
  • Temporary writes with -t: add Patchhound_nt and Patchhound_pass.
  • “Owned” via API with -o:
    • Query Neo4j for :User nodes where Patchhound_has_pass = true and a non-empty SID (objectid).
    • Also note which of those SIDs have a linked :AZUser with the same on‑prem SID.
    • Append those SIDs to an asset group using PUT /api/v2/asset-groups/{id}/selectors.
  • Better identity matching (no fallbacks; union of all paths):
    • :User {name} (e.g., DOM\sam)
    • :User.samaccountname
    • :User.userprincipalname / userPrincipalName
    • :AZUser.userprincipalname / userPrincipalName
    • :Computer.samaccountname
    • Case-insensitive comparisons for SAM/UPN; UPN can be synthesized from dom.com\SAM → [email protected] when UPN token isn’t present.
  • Cleaner output:
    • Non-verbose: just the green checks JWT valid, Potfile Check, NTLM Check, Neo4j auth OK, plus “Waiting…” banners before progress bars.
    • Verbose: pretty, line-per-stat blocks; $HEX[...] decodes printed as nthash:HEXHASHCAT:password; excluded lines (with reasons).

Installation

python3 -m venv .venv && source .venv/bin/activate
pip install neo4j requests

Defaults live in src/conn.py:

  • DEFAULT_URI
  • DEFAULT_USER
  • DEFAULT_PASS (BH CE default is bloodhoundcommunityedition)

Adjust there or wire flags as you prefer.


Usage

Help

python3 PatchHound.py --help
python3 PatchHound.py [--no-color] [subcommand] -h

1) Authenticate (stores JWT for other commands)

python3 PatchHound.py auth -u http://localhost:8080/ -U admin -p 'Exclude -p for PassPrompt' [-v]
  • Writes a session file at: ${TMPDIR}/patchhound.session.json

2) Patch (parse files, set minimal flags; optional temp writes)

python3 PatchHound.py patch -c crack.potfile -n ntds.txt [-t] [-o] [-v]

What it reads

  • -c/--clears potfile — expected format: <32hex_ntlm>:<password>
    • $HEX[...] values are decoded; in verbose you’ll see lines like:
      nthash:$HEX[hex...]:decoded-password
      
    • Any line that doesn’t match the expected schema is excluded and listed (verbose) with a reason.
  • -n/--ntlm hash file — consumes lines containing one or more 32-hex NTLMs and an account token:
    • prefers DOMAIN\SAM; captures explicit UPN tokens where present; synthesizes UPN as SAM@fqdn when possible.
    • Non-conforming lines are excluded with reasons (verbose).

How matching works (Neo4j)

  • For each candidate row we try all of these simultaneously:
    • (:User {name})
    • (:User).samaccountname
    • (:User).userprincipalname | userPrincipalName
    • (:AZUser).userprincipalname | userPrincipalName
    • (:Computer).samaccountname
  • Comparisons for SAM/UPN are case-insensitive (toUpper on both sides).
  • Nodes are de-duplicated and updated idempotently.

What gets written

  • Always:
    • Patchhound_has_hash = true
    • Patchhound_has_pass = (pwd != null)
  • With -t/--temp:
    • Patchhound_nt = <ntlm>
    • Patchhound_pass = <password>

Output

  • Non-verbose:
    [+] JWT valid
    [+] Potfile Check
    [+] NTLM Check
    [+] Neo4j auth OK
    [+] Waiting for Neo4j
    Applying [████████████████████████████] 14598/14598 (100%)
    [+] Updated nodes: 1234
    
  • Verbose also prints pretty stats, decoded HEX, and excluded input lines.

3) “Owned” append via API (with -o)

  • After patching (or even when nothing is applied), -o will:

    1. Query Neo4j for users where Patchhound_has_pass = true and a non-empty SID (u.objectid).
    2. Count how many of those SIDs have a matching :AZUser on‑prem SID (several property names supported).
    3. Append those SIDs to the configured asset group via BHCE v2 API.
  • Non-verbose:

    [+] Waiting for Neo4j and API
    Owned API [████████████████████████████] 1876/1876 (100%)
    [+] Owned API: attempted 1876 selector adds
    
  • Verbose final summary (printed after all logic):

    [*] Owned summary:
        users_with_password_true  : 1876
        with_sid                  : 1876
        distinct_sids_sent        : 1876
        sids_with_azuser_link     : 0
        asset_group_id            : 2
        example_request:
          PUT http://localhost:8080/api/v2/asset-groups/2/selectors
          payload: [{"selector_name":"Manual","sid":"S-1-5-21-...","action":"add"}]
    

Flags (current)

  • -v, --verbose — verbose output (stats, excluded lines, HEX decodes, summaries)
  • --no-color — disable colored/ASCII output
  • patch:
    • -c, --clears — path to potfile (required)
    • -n, --ntlm — path to NTLM hash file (recommended)
    • -t, --temp — write Patchhound_nt and Patchhound_pass
    • -o, --owned — append “Owned” selectors via API based on Neo4j discovery
  • auth:
    • -u, --url
    • -U, --username
    • -p, --password

Notes & Caveats

  • BloodHound CE typically does not persist arbitrary custom node properties across container restarts; the temporary fields (Patchhound_nt, Patchhound_pass) are intended as ephemeral conveniences.
  • UPNs can be lower or upper; comparisons are case-insensitive. For logs, you might see uppercase normalization.
  • :AZUser often lacks samAccountName; when hybrid, an on-prem SID may be available under several different property names. The matcher checks the common ones.

Troubleshooting

  • Neo4j driver not installedpip install neo4j
  • JWT invalid → re-run auth
  • No updates applied:
    • Potfile has no valid lines (format must be 32hex:password)
    • NTLM file had no (acct, hash) pairs
  • Owned append failed:
    • Check API base URL and JWT in the session file
    • Verify your account can mutate asset groups; AAD proxy / reverse proxy headers can interfere

Screenies

s1 s2 s2 s3 s4

About

Patching Bloodhound CE for Owned and Password Analysis

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages