Today, I am going to walk through Editorial on Hack the Box, which is an easy-rated machine created by Lanz. Editorial started off by discovering a blind SSRF vulnerability that was leveraged to perform a port scan on the local server to identify an open port. The open port revealed several API endpoints that could be accessed via the original SSRF vulnerability to discover userland credentials. Vertical escalation to another user was possible due to credentials being left in a Git commit, which led to abusing a Python script to escalate to root.
After reviewing my approach and discussing it with others who have also completed the box, I realized that the web application framework was listening on a known port, eliminating the need for a port scan. I still got the same result, but scanning all 65K ports probably wasn't needed.
Initial Enumeration
As always, I started with an Nmap scan to scan all ports which came back with two ports open: 22 and 80.
Knowing what ports were open, I ran another Nmap scan, but this time focusing on just those ports and using default enumeration scripts.
SSH - 22
SSH is almost never going to be the initial way into a machine, so I skipped it until I had either credentials or a key to authenticate with.
HTTP - 80
The targeted Nmap scan hinted at it not following the redirect to http://editorial.htb. I added the domain to my hosts file so I could resolve it.
After modifying the hosts file, I was able to access the page via the domain name and was brought to the index.
Poking around the page did not reveal much functionality or additional pages that would lead us somewhere. However, there was a feature to submit book information that would then be reviewed by someone.
Part of the submission process was to upload the cover of the book via two methods: the URL of the image or uploading it directly through the page. I first uploaded an example image through the page and noticed the cover preview changed and was uploaded to hxxp://editorial.htb/static/uploads/96d17262-c390-4568-84f9-afbf4178aa74.
I navigated to the URL where the example image was uploaded and was able to download it. This means we can access files in /static/uploads if we know the filename.
I hosted the image myself, used the cover URL feature to upload the book cover image, and noticed the web application hit my listener with the machine's IP, which means I might have an SSRF vulnerability on my hands.
If I did not provide a book cover image during the submission process, the form would default to a generic one located at /static/images/unsplash_photo_1630734277837_ebe62757b6e0.jpeg.
Exploring Possible SSRF on Cover Upload Feature
Rather than continuing to test the potential SSRF vulnerability in Firefox, I captured the same POST request in Burp and passed it to the Repeater tab to make the process easier for myself.
The first thing I did was change my Kali IP in the request to 127.0.0.1 to see if the server would show reveal internal resources. Instead of something interesting, I got the same generic cover image location.
I also attempted to view files such as /etc/passwd which again, did not work as I got the generic cover image.
The last thing I wanted to do was perform a port scan. It is similar to what I did in my Creative walkthrough. However, rather than using wfuzz, I used ffuf.
I started by saving the initial POST request from Burp to a file, similar to what you would do if you wanted to use it in something like SQLMap. After saving it, I changed the port number in the request to FUZZ, as this is where I wanted ffuf to look and replace the port numbers at.
I then passed this request file to ffuf with a wordlist that contained all 65,535 TCP ports. Looking back, I did not need to do this as the web application is running Flask, which listens on TCP/5000, but hindsight is 20/20.
Here, I am filtering out any response sizes of 61 bytes, which was a baseline I assumed for closed ports based on testing. It made showing any open ports a lot easier.
Going back to the request I had in Burp, I changed the port to 5000 and confirmed I did not get the generic Unsplash location but instead received a file in static/uploads which was different than all the previous requests.
Opening the downloaded file, it looked to be in JSON format. It seemed there was an API being served on port 5000, which can only be accessed internally.
💡
Remember, we have the ability to read files in /static/uploads if the filename is known. We learned this from uploading a cover image from before.
SSRF with API Endpoints on Port 5000
To make reading the file I downloaded easier, I passed the file to jq to get proper JSON formatting. The file was a listing of all the various endpoints this API supported.
Using each of these API endpoints in my original SSRF payload in Burp resulted in more files to download that contained useful information from the endpoints, such as this one listing the coupons.
I repeated this process for the rest of the API endpoints and noticed /api/latest/metadata/messages/authors contained credentials for a user called dev.
Knowing SSH was open on the box, I used these credentials to log in and was able to access the system as the dev user.
Userland Enumeration
The dev user did not have any sudo privileges on the machine.
There were no interesting SUID binaries, apart from the ones that we would expect to see.
There were two users on the box: our current user and another called prod.
Exploring further, I found a .gitdirectory in /home/dev/apps/.git
Viewing the HEAD file reveals several commits have occurred since the initial push.
Using git show on each of the commits starting from the initial commit reveals that commit 1e84a036b2f33c59e2390730699a488c65643d28 included the credentials for the prod user.
The password in the commit worked, and I was able to switch to the prod user via my active SSH session.
Enumeration as prod User
Unlike the dev user, prod did have sudo privileges on the box. The user could run /opt/internal_apps/clone_changes/clone_prod_change.py as root - this sounded very interesting.
The script imported a few libraries, including the git module from the GitPython library. It then changed to /opt/internal_apps/clone_changes, takes in a repository URL (url_to_clone) from a command-line argument, initializes a new git repository, and clones the provided repository URL into a new directory called new_changes.
The version of the GitPython library is 3.1.29, which is vulnerable toCVE-2022-24439.
Escalation to Root via clone_prod_change.py
I ran the Python script with sudo privileges using the malicious URL outlined in the Snyk exploit POC. If successful, a new file called pwned would be created in /tmp, and it was.
As I had code execution, I ran the following command. This is one of my go-to escalation vectors, as it does not negatively impact the environment and would be easy to clean up after. I am copying /bin/bash to /tmp/bash and then granting SUID permissions to /tmp/bash.
Verifying /tmp/bash has been copied to /tmp and has the SUID bit set.
Used the binary at /tmp/bash with the -p argument to escalate to the root user.
💡
Including the -p argument here was really important. This is what preserves the effective user ID in the new Bash process. If this was not included, the effective ID would have been dropped and would not result in a root shell.
And that is my walkthrough of Editorial! I hope you enjoyed it and learned a thing or two! I know I sure did while doing the box. Until next time!