PG: Hunit
| ターゲット // Hunit | |
|---|---|
| Platform | OffSec Proving Grounds |
| OS | Linux |
| Difficulty | Medium |
| IP | 192.168.142.125 |
Enumeration#
Nmap#
▶ Nmap output
PORT STATE SERVICE VERSION
8080/tcp open http-proxy (custom web app + REST API)
12445/tcp open netbios-ssn Samba smbd 4.6.2
18030/tcp open http Apache httpd 2.4.46 ((Unix))
43022/tcp open ssh OpenSSH 8.4 (protocol 2.0)
Everything is on non-standard ports. SMB (12445) and the Apache instance on 18030 turn out to be dead ends — the static site on 18030 is just a landing page, and Samba exposes nothing interesting. SSH sits on 43022, so keep that port in mind: any creds we find get tried there.
The real surface is the custom web app on 8080.
Port 8080 - Web App + API#
Directory fuzzing surfaces a blog-style app (/article/...) plus a /js/ bundle — but the interesting bit is a REST API mounted at /api/. Ask it what it exposes:
| |
| |
A /user/ endpoint on an unauthenticated API is a giant flashing sign.
Foothold#
API Info Disclosure -> Credential Dump#
The /api/user/ endpoint happily returns the full user table — logins and cleartext passwords:
| |
{"login":"rjackson","password":"yYJcgYqszv4aGQ","description":"Editor"}
{"login":"jsanchez","password":"d52cQ1BzyNQycg","description":"Editor"}
{"login":"dademola","password":"ExplainSlowQuest110","description":"Admin"}
{"login":"jwinters","password":"KTuGcSW6Zxwd0Q","description":"Editor"}
{"login":"jvargas","password":"OuQ96hcgiM5o9w","description":"Editor"}
The dademola account is flagged Admin. Since SSH lives on 43022, try password reuse there — admin creds go first:
| |
We’re in. local.txt is right there in the home directory.
Privilege Escalation#
Recon -> git user + cron backups#
Enumerating local users shows a third account worth noting — a git user pinned to git-shell:
| |
A leftover crontab backup spells out what root runs on a timer:
| |
Root executes /root/git-server/backups.sh every 3 minutes. If we can influence that script’s contents, we get code execution as root.
Git push -> cron RCE#
The git user’s SSH private key is sitting in /home/git/.ssh and is readable. git-shell itself is locked down (no interactive commands, and we can’t drop anything into /home/git/git-shell-commands as dademola), but the key still lets us clone the server-side repo — which is the same git-server root’s cron runs backups.sh out of.
Clone it over the non-standard SSH port using GIT_SSH_COMMAND:
| |
Edit backups.sh to append a reverse shell, then commit and push it back. SUID and mkfifo-style one-liners choked on shell escaping during push, so a plain bash TCP callback (or its base64 form to sidestep quoting) is what lands cleanly:
| |
Commit and push back to the server:
| |
Start a listener on 8080, wait up to 3 minutes for cron to fire backups.sh, and catch the shell as root.
Proof#
User flag on local.txt:

Root was obtained via the cron-triggered backups.sh reverse shell above (caught as root on the listener). No proof.txt value was captured in these notes, so it’s not reproduced here.
Key Takeaways#
- Always enumerate REST APIs like a directory tree — an unauthenticated
/api/user/endpoint dumping cleartext passwords is game over before you touch a single exploit. - Non-standard ports don’t change the playbook: harvested creds get sprayed at SSH wherever it’s actually listening (here, 43022) — pass it with
-p. - A readable server-side git repo whose scripts are run by root on a cron is a clean privesc: clone, poison the script, push, wait for the timer.
GIT_SSH_COMMANDhandles the odd SSH port/key. - When crafting a reverse-shell payload that passes through
git commit/push, expect shell-escaping to eat SUID/mkfifoone-liners — a base64-encoded bash TCP callback survives the round-trip.