PAN-OS GlobalProtect CAS CVE-2026-0265 Vulnerability Checker
🗓️ 21 May 2026 00:00:00Reported by Bishop Fox Team X, Rapid7 Research / Deral Heiland adaptationType
metasploit🔗 www.rapid7.com👁 6 Views
| Reporter | Title | Published | Views | Family All 19 |
|---|---|---|---|---|
| Exploit for CVE-2026-0265 | 20 May 202622:36 | – | githubexploit | |
| Exploit for CVE-2026-0265 | 20 May 202620:12 | – | githubexploit | |
| Exploit for CVE-2026-0265 | 17 May 202601:31 | – | githubexploit | |
| CVE-2026-0265 | 13 May 202617:38 | – | attackerkb | |
| The vulnerability of the Cloud Authentication Service (CAS) component of the PAN-OS operating system, related to incorrect verification of the cryptographic signature, allows attackers to bypass existing security mechanisms. | 18 May 202600:00 | – | bdu_fstec | |
| CVE-2026-0265 | 13 May 202610:50 | – | circl | |
| Palo Alto Networks PAN-OS 数据伪造问题漏洞 | 13 May 202600:00 | – | cnnvd | |
| CVE-2026-0265 | 13 May 202617:38 | – | cve | |
| CVE-2026-0265 PAN-OS: Authentication Bypass with Cloud Authentication Service (CAS) enabled | 13 May 202617:38 | – | cvelist | |
| EUVD-2026-30066 | 13 May 202618:30 | – | euvd |
10
##
##
# CVE-2026-0265 PAN-OS GlobalProtect CAS Exposure and Version Scanner
#
# Safe scanner: no authentication bypass attempted, no session creation,
# no body modification, and no firewall state changes.
#
# Detection logic derived from:
# https://github.com/BishopFox/CVE-2026-0265-check
#
# Copyright (c) Bishop Fox
# Original utility released under the MIT License.
#
# This Metasploit module is distributed under the Metasploit Framework license.
##
##
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::Retry
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report
DEFAULT_USER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 ' \
'(KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36'
# Per-base patched-hotfix cutoffs from Bishop Fox scanner / PA advisory logic.
# Any hotfix strictly below the listed value on the same base is vulnerable.
ADVISORY_PATCHED_HOTFIX = {
[10, 2, 4 ] => 44,
[10, 2, 7 ] => 34,
[10, 2, 10] => 36,
[10, 2, 13] => 21,
[10, 2, 16] => 7,
[10, 2, 18] => 6,
[11, 1, 4 ] => 33,
[11, 1, 6 ] => 32,
[11, 1, 7 ] => 6,
[11, 1, 10] => 25,
[11, 1, 13] => 5,
[11, 2, 4 ] => 17,
[11, 2, 7 ] => 13,
[11, 2, 10] => 6,
[12, 1, 4 ] => 5
}.freeze
# Train-base floors: anything >= this base in the train is patched.
ADVISORY_BASE_FLOOR = {
[11, 1] => 15,
[11, 2] => 12,
[12, 1] => 7
}.freeze
# Highest base release represented by the advisory matrix for each affected train.
# A later base release is presumed patched so future releases do not fall through
# to the affected-train fallback below.
ADVISORY_LAST_KNOWN_BASE = {
[10, 2] => 18,
[11, 1] => 15,
[11, 2] => 12,
[12, 1] => 7
}.freeze
UNAFFECTED_TRAINS = [
[8, 1],
[9, 1]
].freeze
DEFAULT_RETRY_TIMEOUT = 15
def initialize(info = {})
super(
update_info(
info,
'Name' => 'PAN-OS GlobalProtect CAS CVE-2026-0265 Vulnerability Checker',
'Description' => %q{
This module checks a PAN-OS GlobalProtect portal for CVE-2026-0265
using the Bishop Fox scanner decision flow. It performs anonymous
GET requests to the GlobalProtect prelogin endpoint, retrying transient
failures and HTTP 503 responses, then checks
whether CAS authentication is enabled, decodes the embedded SAML/JWT
token when present, extracts PanOSversion, and compares it against
the advisory version matrix.
},
'Author' => [
'Bishop Fox Team X', # original scanner logic
'Rapid7 Research / Deral Heiland adaptation' # Metasploit port workflow
],
'License' => MSF_LICENSE,
'References' => [
['CVE', '2026-0265'],
['URL', 'https://security.paloaltonetworks.com/CVE-2026-0265'],
['URL', 'https://github.com/BishopFox/CVE-2026-0265-check']
],
'DisclosureDate' => '2026-05-21',
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [],
'SideEffects' => [IOC_IN_LOGS]
}
)
)
register_options(
[
OptString.new('TARGETURI', [true, 'GlobalProtect prelogin endpoint path', '/global-protect/prelogin.esp']),
OptString.new('USERAGENT', [true, 'User-Agent used for the prelogin probe', DEFAULT_USER_AGENT]),
OptInt.new('RETRY_TIMEOUT', [true, 'Maximum time in seconds to retry transient failures or HTTP 503 responses', DEFAULT_RETRY_TIMEOUT])
]
)
end
def run_host(ip)
check_host(ip)
end
def check_host(ip)
finding = scan_target
target = target_label(ip)
print_status("#{target} - CAS enabled: #{finding[:cas_enabled]}")
print_status("#{target} - PAN-OS version: #{finding[:panos_version] || 'unknown'}")
case finding[:verdict]
when 'VULNERABLE'
print_good("#{target} - Status: VULNERABLE")
print_status("#{target} - #{finding[:note]}") if finding[:note]
report_vulnerability(ip, finding)
Exploit::CheckCode::Appears(
finding[:note] || "CAS is enabled and PAN-OS #{finding[:panos_version]} is in an affected version range."
)
when 'PATCHED', 'NOT-AFFECTED-SAAS', 'NOT-AFFECTED-NO-CAS', 'NOT-AFFECTED-NOT-GLOBALPROTECT'
print_status("#{target} - Status: NOT VULNERABLE (#{finding[:verdict]})")
print_status("#{target} - #{finding[:note]}") if finding[:note]
Exploit::CheckCode::Safe(
finding[:note] || "The target is not affected (#{finding[:verdict]})."
)
else
print_warning("#{target} - Status: UNDETERMINED (#{finding[:verdict]})")
print_warning("#{target} - #{finding[:error]}") if finding[:error]
print_status("#{target} - #{finding[:note]}") if finding[:note]
Exploit::CheckCode::Unknown(
finding[:error] || finding[:note] || "Unable to determine vulnerability status (#{finding[:verdict]})."
)
end
rescue ::Rex::ConnectionError, ::Timeout::Error, ::EOFError, ::Errno::EPIPE, ::Errno::ECONNRESET => e
reason = "Network error: #{e.class}: #{e.message}"
print_error("#{target_label(ip)} - Status: UNDETERMINED (#{reason})")
Exploit::CheckCode::Unknown(reason)
rescue ::StandardError => e
reason = "Module error: #{e.class}: #{e.message}"
print_error("#{target_label(ip)} - Status: UNDETERMINED (#{reason})")
Exploit::CheckCode::Unknown(reason)
end
def target_label(ip)
Rex::Socket.to_authority(ip, datastore['RPORT'])
end
def scan_target
res = fetch_prelogin
return error_finding('UNDETERMINED-ERROR', 'No HTTP response received') unless res
body = res.body.to_s
if body.include?('GlobalProtect portal does not exist')
return {
verdict: 'NOT-AFFECTED-NOT-GLOBALPROTECT',
cas_enabled: 'No',
panos_version: nil,
note: 'Response indicates this listener is not a GlobalProtect portal.'
}
end
if body.include?('Valid client certificate is required')
return {
verdict: 'UNDETERMINED-MTLS-GATED',
cas_enabled: 'Unknown',
panos_version: nil,
note: 'mTLS gate hides the authentication profile configuration.'
}
end
if body.include?('CAS is not supported by the client')
return {
verdict: 'UNDETERMINED-VERSION-GATED',
cas_enabled: 'Yes',
panos_version: nil,
note: 'CAS is attached but the probe was User-Agent/client-version gated.'
}
end
cas_auth_value = body[%r{<cas-auth>([^<]*)</cas-auth>}i, 1]
unless cas_auth_value == 'yes'
return {
verdict: 'NOT-AFFECTED-NO-CAS',
cas_enabled: 'No',
panos_version: nil,
note: 'prelogin response did not contain <cas-auth>yes</cas-auth>.'
}
end
claims = decode_token_from_prelogin(body)
unless claims
return error_finding('UNDETERMINED-ERROR', 'cas-auth=yes but Token decode from prelogin response failed', 'Yes')
end
panos_version = claims['PanOSversion']
verdict = advisory_verdict_for_version(panos_version.to_s)
if verdict == 'ERROR'
return error_finding(
'UNDETERMINED-ERROR',
"PanOSversion=#{panos_version.inspect} could not be parsed against advisory matrix",
'Yes',
panos_version
)
end
patch_reference = advisory_patch_reference_for_version(panos_version.to_s)
note = case verdict
when 'VULNERABLE'
if patch_reference && patch_reference[:type] == :hotfix
"#{panos_version} is below the advisory patched hotfix #{patch_reference[:version]}."
else
"#{panos_version} is within an affected PAN-OS release train."
end
when 'PATCHED'
if patch_reference && patch_reference[:type] == :hotfix
"#{panos_version} is at or above the advisory patched hotfix #{patch_reference[:version]}."
elsif patch_reference && patch_reference[:type] == :base
"#{panos_version} is at or above the advisory patched base release #{patch_reference[:version]}."
else
"#{panos_version} is not affected per advisory version logic."
end
when 'NOT-AFFECTED-SAAS'
'SaaS builds are not affected per advisory logic.'
end
{
verdict: verdict,
cas_enabled: 'Yes',
panos_version: panos_version,
note: note
}
end
def fetch_prelogin
retry_timeout = datastore['RETRY_TIMEOUT'].to_i
unless retry_timeout.positive?
print_warning("RETRY_TIMEOUT must be greater than 0; using the default of #{DEFAULT_RETRY_TIMEOUT} seconds.")
retry_timeout = DEFAULT_RETRY_TIMEOUT
end
retry_until_truthy(timeout: retry_timeout) do
res = send_request_cgi(
{
'method' => 'GET',
'uri' => normalize_uri(datastore['TARGETURI']),
'headers' => {
'User-Agent' => datastore['USERAGENT']
}
}
)
res if res && res.code != 503
rescue ::Rex::ConnectionError, ::Timeout::Error, ::EOFError, ::Errno::EPIPE, ::Errno::ECONNRESET => e
vprint_warning("Transient request failure: #{e.class}: #{e.message}")
nil
end
end
def decode_token_from_prelogin(body)
saml_request = body[%r{<saml-request>([^<]+)</saml-request>}i, 1]
return nil unless saml_request
html_form = Rex::Text.decode_base64(saml_request.strip).to_s
token = html_form[/name="Token"\s+value="([^"]+)"/, 1]
return nil unless token
parts = token.split('.')
return nil unless parts.length == 3
payload_json = Rex::Text.decode_base64url(parts[1])
JSON.parse(payload_json)
rescue ::StandardError
nil
end
def advisory_patch_reference_for_version(panos_version)
match = panos_version.match(/^(\d+)\.(\d+)\.(\d+)(?:-h(\d+))?$/)
return nil unless match
maj = match[1].to_i
min = match[2].to_i
pat = match[3].to_i
floor = ADVISORY_BASE_FLOOR[[maj, min]]
return { type: :base, version: "#{maj}.#{min}.#{floor}" } if floor && pat >= floor
cutoff = ADVISORY_PATCHED_HOTFIX[[maj, min, pat]]
return { type: :hotfix, version: "#{maj}.#{min}.#{pat}-h#{cutoff}" } if cutoff
nil
end
def advisory_verdict_for_version(panos_version)
return 'ERROR' if panos_version.empty?
return 'NOT-AFFECTED-SAAS' if panos_version.include?('.saas')
match = panos_version.match(/^(\d+)\.(\d+)\.(\d+)(?:-h(\d+))?$/)
return 'ERROR' unless match
maj = match[1].to_i
min = match[2].to_i
pat = match[3].to_i
hf = match[4] ? match[4].to_i : 0
return 'PATCHED' if UNAFFECTED_TRAINS.include?([maj, min])
floor = ADVISORY_BASE_FLOOR[[maj, min]]
return 'PATCHED' if floor && pat >= floor
cutoff = ADVISORY_PATCHED_HOTFIX[[maj, min, pat]]
return (hf >= cutoff ? 'PATCHED' : 'VULNERABLE') if cutoff
last_known_base = ADVISORY_LAST_KNOWN_BASE[[maj, min]]
return 'PATCHED' if last_known_base && pat > last_known_base
return 'VULNERABLE' if [[10, 2], [11, 1], [11, 2], [12, 1]].include?([maj, min])
'PATCHED'
end
def error_finding(verdict, error, cas_enabled = 'Unknown', panos_version = nil)
{
verdict: verdict,
cas_enabled: cas_enabled,
panos_version: panos_version,
error: error
}
end
def report_vulnerability(ip, finding)
report_vuln(
host: ip,
port: datastore['RPORT'],
proto: 'tcp',
name: 'PAN-OS GlobalProtect CAS CVE-2026-0265',
refs: references,
info: "CAS enabled: #{finding[:cas_enabled]}, PAN-OS version: #{finding[:panos_version]}, verdict: #{finding[:verdict]}"
)
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
21 May 2026 00:00Current
7.2High risk
Vulners AI Score7.2
CVSS 3.18.1
CVSS 49.2
EPSS0.01053
SSVC