This is a write-up for the recently retired Canape machine on the Hack The Box platform. If you don’t already know, Hack The Box is a website where you can further your cybersecurity knowledge by hacking into a range of different machines. At any stage in the write-up, you can click on a command to be redirected to a page which describes what the command does.

TL;DR: Exploiting cPickle & Bad Pip Permissions. | This was yet another very well-crafted box, largely because there was minimal guesswork required. While sometimes it can take forever to guess your way through a system, this one was very logical, and as such, fun to do.

PART ONE: WWW-DATA

As per usual, I began with a nmap scan:

Since we have an open HTTP port, we know that we can visit it in the browser. The shown apache version is fairly recent, and so there are very few known vulnerabilities on it at the time of writing.

We are presented with a Simpson’s fan site.

As shown, the website looks like a simple website, dedicated to The Simpsons. We can see here that the server is running CouchDB (as it says in the Best Quotes description) for a quotes database, which is useful to note. We can also see that there are some pages for submitting/viewing quotes, which we will come to later.

For now, we’ll just enumerate further. After browsing through the site at surface level, I decided to run a dirb scan to find other accessible directories:

george@kali:~/htb/canape$ dirb http://10.10.10.70/ ----------------- DIRB v2.22 By The Dark Raver ----------------- URL_BASE: http://10.10.10.70/ WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt ----------------- GENERATED WORDS: 4612 ---- Scanning URL: http://10.10.10.70/ ---- (!) WARNING: NOT_FOUND[] page not stable, unable to determine the correct URLs {200}. (Try using FineTunning: '-f') ----------------- DOWNLOADED: 0 - FOUND: 0 george@kali:~/htb/canape$

Strangely, dirb returned an error relating to determining invalid webpages. In order to determine why this was the case, I manually checked some incorrect webpages:

The website seems to generate a random string whenever an invalid webpage is accessed, which pretty much renders tools like dirb useless. It’s worth noting that I did mess around with the strings for a while, to try and determine whether they actually were random or whether they were encrypted/encoded messages.

To combat this, I wrote a short script that checks whether the webpage contains one long uppercase string, and if not, logs it:

If it works, it works.

Let’s now run this:

george@kali:~/htb/canape$ python3 dir.py Beginning scan on URL http://10.10.10.70/ with common wordlist. 4614 words found. 1 | Attempting: 2 | Attempting: .bash_history 3 | Attempting: .bashrc [...] 4612 | Attempting: zorum 4613 | Attempting: zt #################### Scan complete! Words identified: 1: .git/HEAD 2: cgi-bin/ 3: check 4: quotes 5: server-status 6: static 7: submit ####################

After a while, the script finishes, and we can the accessible directories.

The cgi-bin and server-status directory/file are both forbidden, while submit, check and quotes are all core parts of the website. However, the .git directory looks interesting.

We can recursively download the .git repository with this command:

We can then clone the local git repository with:

The website’s entire git repository is now cloned into our own folder called “git”. We can now see a large part of the website’s source code in a file called __init__.py.

george@kali:~/htb/canape$ cd git/ george@kali:~/htb/canape/git$ ls __init__.py static templates

Let’s comb through the code for anything useful.

An excerpt from __init__.py.

It looks like on the “submit quotes” page, we can choose anyone from a specific whitelist, and the code gets saved as a file made from the MD5 hash of the chosen character and the given hash.

Just so that this makes a bit more sense, let’s visit the submit quote page:

We can write and submit quotes in the submit page.

Submitting this with an invalid character name simply throws an error.

Another interesting thing to note in this chunk of code is how it only checks whether the character’s name is in the character slot, and not whether there is any extra text.

The next interesting part of the file is the check page, which we haven’t seen anything about before:

Another excerpt from __init__.py.

We can see that it uses cPickle to load the contents of a file specified in the post request.

We also know from the submit code that we can work out the request form ID by calculating an md5 hash of the data sent, and so we can force the script to cPickle any data we want.

A Google search on cPickle reveals a very prominent exploit in its data serialization, which is explained fairly thoroughly here.

Thanks to this article, we now know that we have to do the following in order to obtain a shell:

Create a payload with pickle.

Submit the payload in the a POST request to /submit.

Calculate the filename as an MD5 hash.

Listen on a port with netcat/meterpreter.

Use the /check endpoint to force the server to deserialize the data with Pickle.

Spawn TTY shell in netcat/meterpreter.

In order to do this, I wrote a python script that automates the majority of it.

An interesting challenge that I faced was successfully delivering a character name with the payload still working. I ended up posting the character with…

character = payload+”S’homer’

”

We use S’homer’ because that way, it is declared as a string and so the script will not crash.

You can find more information about string handling in cPickle here.

Anyway, here is my finished script:

We can now run this script in order to obtain a shell.

We now have a shell as www-data!

I ran the command python -c ‘import pty; pty.spawn(“/bin/sh”)’ in order to spawn a TTY shell, however this isn’t completely necessary.

We can immediately go and look for the user flag, but sadly we don’t have the appropriate permissions.

We don’t have the permissions to access the homer directory.

PART TWO: UPGRADING TO USER

From here, I enumerated for a while to try and find anything of interest.

However, all systems seemed up to date, and there didn’t seem to be any unusual services running (other than CouchDB — which we already know about). In fact, the only interesting file at all was the .htpasswd file that is found in /var/www/git. This file contains the following:

homer:Git Access:7818cef8b9dc50f4a70fd299314cb9eb

Although it may be possible to bruteforce this password, I wasn’t able to. Since we know from earlier that CouchDB is running on the server, so let’s check that out now.

CouchDB always runs on port 5984 by default, and so we can view it by using curl on that port:

curl http://localhost:5984/

{"couchdb":"Welcome","version":"2.0.0","vendor":{"name":"The Apache Software Foundation"}} {"couchdb":"Welcome","version":"2.0.0","vendor":{"name":"The Apache Software Foundation"}}

After doing some research on CouchDB, I found that we can view all the databases using the _all_dbs endpoint:

The most interesting ones here seem to be the _users database and the _passwords database, so let’s take a look at them:

curl http://localhost:5984/_users/_all_docs

{"error":"unauthorized","reason":"You are not a server admin."}

$ curl http://localhost:5984/passwords/_all_docs

{"error":"unauthorized","reason":"You are not authorized to access this db."} {"error":"unauthorized","reason":"You are not a server admin."}{"error":"unauthorized","reason":"You are not authorized to access this db."}

Oh… Let’s try something else.

We can very quickly find a CouchDB exploit that allows us to add new users, so let’s serve that over with a python SimpleHTTPServer.

python -m SimpleHTTPServer 1336 | We create a user with the username “george” and the password “george”.

Let’s now try using the credentials of the new user to access the passwords database:

Since we only need the table IDs to view the tables, we can disregard all of the other information. We now have 4 different entries to the password database. In order to view the entries, I wrote a short script that spits out the usernames and passwords:

Although this could have easily been done manually, automation is always fun. Let’s run this and see what we can find:

$ python getpass.py ### Username: Password: 0B4jyA0xtytZi7esBNGp ### Username: couchy Password: r3lax0Nth3C0UCH ### Username: homer Password: h02ddjdj2k2k2 ### Username: homerj0121 Password: STOP STORING YOUR PASSWORDS HERE -Admin

We can now test all of this passwords against the “homer” user.

This means that we can now obtain the user flag through cat /home/homer/flag.txt!

When attempting the root flag, my first thought is usually to check the root user’s directory permissions, however there was nothing of particular interest there:

PART THREE: ROOT

We can now check our own users sudo permissions with sudo -l:

sudo -l homer@canape:/$ Matching Defaults entries for homer on canape: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin User homer may run the following commands on canape: (root) /usr/bin/pip install *

I already have a very vague idea of how pip works, so working out an exploit shouldn’t be too hard.

Essentially, when installing a python package, pip always runs the setup.py file, and so since we can run setup.py as root, we can essentially run anything we want.

I used the official Python docs in order to construct a pip package that should give us a shell. The script is as minimalistic as I could make it, and just imports all necessary build packages, and then opens up a connection to a specified IP.

With this script finished, we can then serve it over via a SimpleHTTPServer and then use our pip installation to run it!

We install our custom package with Pip, and run it.

And with that, we have root.