Like a VIP badge reader that trusts a forged plastic card, but only on the doors where you actually installed that reader
CVE-2026-0265 is a JWT signature-verification failure in PAN-OS and Panorama when *Cloud Authentication Service (CAS)* is attached to an authentication profile that is then bound to a login interface. The practical surfaces are the GlobalProtect portal, where success means an attacker can impersonate a user and get VPN access, and the management interface, where success can mean full administrator access. Affected releases span PAN-OS 10.2, 11.1, 11.2, and 12.1 before the hotfix/build cutoffs in Palo Alto's advisory; Cloud NGFW and Prisma Access are not affected.
This is not a fake-scary lab bug, but it is also not a universal internet-to-root condition. The decisive friction is configuration: if CAS is not attached to the targeted listener, the vulnerable path is dead. That keeps it out of CRITICAL. What keeps it solidly HIGH is that GlobalProtect portals are often intentionally internet-facing, the bug is pre-auth, and the impact is an immediate trusted foothold into enterprise access infrastructure.
4 steps from start to impact.
Find a CAS-backed PAN-OS login surface
- Network reachability to a PAN-OS login interface
- CAS enabled on the authentication profile
- That profile attached to GlobalProtect or management login
- Many deployments do not use CAS at all
- Many management interfaces are correctly restricted to internal or trusted admin networks
- mTLS on the portal can interfere with unauthenticated probing and opportunistic exploitation
GET /global-protect/prelogin.esp method; management-surface exposure needs separate validation.Forge a trusted JWT
pan_auth_verify accepts an attacker-controlled JWT alg and can be tricked into verifying an HMAC using the CAS RSA public key bytes. That classic JWT algorithm-confusion flaw lets the attacker mint a token that PAN-OS accepts as if it came from CAS.- Knowledge of the target's CAS verification flow
- Ability to craft JWTs against the vulnerable verifier
- Exploitability exists only on the CAS code path
- Attackers still need product-specific implementation details, though public technical analysis now lowers that barrier
Hit the affected auth endpoint
- Target listener reachable from attacker position
- Forged token accepted by the vulnerable build
- GlobalProtect success is limited by whatever that user account can do once on VPN
- Management-interface success requires the much narrower case of exposed admin access with CAS attached
Pivot from identity bypass to network impact
- Useful user entitlements on the impersonated VPN identity or exposed management access
- Post-VPN activity still has to survive internal segmentation, EDR, MFA on downstream apps, and detections
- This CVE alone is an access bypass, not native code execution on the appliance
The supporting signals.
| In-the-wild status | As of 2026-05-29, I found no CISA KEV listing and no vendor confirmation of active exploitation for CVE-2026-0265. That said, Rapid7 reported the original researcher claimed successful exploitation against multiple production GlobalProtect portals, and JPCERT warned public technical detail could accelerate weaponization. |
|---|---|
| Proof-of-concept / public tradecraft | There is no vendor-published exploit, but public tradecraft is already enough for defenders to treat this as operationally real: Bishop Fox blog plus the BishopFox/CVE-2026-0265-check repo materially lower attacker and defender effort. |
| EPSS | User-supplied EPSS is 0.00028. A third-party mirror reported roughly 22.58th percentile on 2026-05-17. That is *very low predictive threat scoring*, but EPSS routinely underrates freshly disclosed perimeter-auth bugs on security appliances. |
| KEV status | Not KEV-listed as of 2026-05-29. No federal emergency signal yet. |
| CVSS / vector reality | NVD had no enrichment when checked, but the Palo Alto CNA entry shows CVSS-BT 7.2 HIGH with CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:U/AU:N/R:U/V:D/RE:M/U:Red. The important field is AT:P: the bug is reachable only when CAS is actually attached to the target listener. |
| Affected population | Affected only if both conditions are true: CAS is enabled in the auth profile and that profile is attached to a login interface. Products in scope: PA-Series, VM-Series, and Panorama. Products out of scope: Cloud NGFW and Prisma Access. |
| Fixed versions | Branch cutoffs are 10.2.7-h34 / 10.2.10-h36 / 10.2.13-h21 / 10.2.16-h7 / 10.2.18-h6, 11.1.4-h33 / 11.1.6-h32 / 11.1.7-h6 / 11.1.10-h25 / 11.1.13-h5 / 11.1.15, 11.2.4-h17 / 11.2.7-h13 / 11.2.10-h6 / 11.2.12, and 12.1.4-h5 / 12.1.7 or later. |
| Exposure and scanning | GlobalProtect portals are intentionally internet-reachable in many enterprises and can be screened anonymously via GET /global-protect/prelogin.esp. That makes fleet triage practical at scale, but it also means exposed CAS-backed portals are a realistic attacker target set. |
| Research / disclosure timeline | Disclosed by Palo Alto on 2026-05-13; advisory updated 2026-05-28. Palo Alto credits Harsh Jaiswal of Hacktron AI plus internal research teams. JPCERT says Hacktron published technical detail on 2026-05-20 showing GlobalProtect bypass leading to possible VPN access. |
noisgate verdict.
The single biggest downward pressure is the CAS prerequisite: if CAS is not attached to the targeted login surface, the bug does not exist for that listener. It still lands in HIGH because the reachable population is not tiny on internet-facing GlobalProtect portals, and a pre-auth VPN foothold on a perimeter security appliance is a serious enterprise access problem even without code execution.
Why this verdict
- Start high on first principles: this is unauthenticated network-reachable auth bypass on a firewall/VPN stack, with no user interaction and potentially high impact if the exposed listener is security-critical
- Subtract for attacker-position narrowing: the bug is not universal across PAN-OS; the attacker needs a listener where CAS is both enabled and attached, which compounds exposure friction and keeps this below CRITICAL
- Add back for exposure reality: GlobalProtect portals are often deliberately internet-facing, so the reachable population is meaningfully larger than 'internal admin panel only' bugs
- Do not overreact to management-interface impact alone: full admin compromise is the worst case, but it requires the narrower and already-bad condition of a reachable management interface using CAS
- Low EPSS and no KEV temper urgency, not severity: the absence of broad exploitation evidence stops an immediate-hours CRITICAL call, but it does not turn a perimeter auth bypass into routine backlog
Why not higher?
I am not calling this CRITICAL because the exploit chain has a real, deployment-level gate: CAS must be configured on the target listener. That requirement sharply reduces exposed population versus a default-on PAN-OS edge bug, and current public reporting does not show confirmed broad in-the-wild exploitation or KEV listing.
Why not lower?
I am not dropping this to MEDIUM because the reachable case is still dangerous and common enough to matter: internet-facing GlobalProtect is normal, and successful exploitation can hand an attacker a trusted VPN session before they ever touch credentials. On a security appliance at the boundary, pre-auth identity bypass carries more operational weight than the low EPSS alone suggests.
What to do — in priority order.
- Detach CAS from exposed login profiles — If you cannot patch immediately, remove CAS from every authentication profile attached to internet-facing GlobalProtect portals or any reachable management login and switch to SAML, RADIUS, LDAP, or local auth. This directly breaks the vulnerable code path and should be completed within 30 days, or faster for externally exposed portals.
- Shut the management interface off the internet — Restrict PAN-OS and Panorama management access to trusted admin IPs or internal networks only. This does not fix the CVE on GlobalProtect, but it removes the highest-blast-radius outcome; complete this within 30 days wherever external management exposure still exists.
- Enable Threat ID 510008 where supported — On PAN-OS 11.2+ with Threat Prevention, enable Threat ID 510008 from content version 9100-10044+ and satisfy Palo Alto's routing/decryption prerequisites so inbound traffic is actually inspected. Treat this as a compensating control to deploy within 30 days, not a substitute for patching.
- Sweep all GlobalProtect portals with an external check — Use the Bishop Fox-style anonymous prelogin probe from an auditor workstation to identify which public portals are both CAS-backed and below fixed build cutoffs. Do this early in the cycle so the patch list is based on real exposure, not generic PAN-OS inventory, and finish the first pass within 30 days.
- MFA by itself does not solve this when the forged token is accepted as a trusted upstream CAS result; the bypass happens *before* your normal auth assurance helps.
- Generic external vuln scanning alone will miss the key precondition because version-only checks overstate exposure and many scanners will not know whether CAS is actually attached.
- Restricting only the management UI is not enough if GlobalProtect remains internet-facing with CAS attached; the portal path can still yield a valid VPN foothold.
Crowdsourced verification payload.
Run this from an auditor workstation or jump host that can reach the target GlobalProtect portal over HTTPS. Invoke it as python3 panos_cve_2026_0265_check.py https://gp.example.com; it needs no credentials and no elevated privileges, but it only verifies the GlobalProtect portal surface, not the management interface path.
#!/usr/bin/env python3
# panos_cve_2026_0265_check.py
# Safe external checker for the GlobalProtect portal surface of CVE-2026-0265.
# Output: VULNERABLE / PATCHED / UNKNOWN
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN, 3=usage
import sys
import re
import json
import base64
import requests
import urllib3
from xml.etree import ElementTree as ET
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
FIXED = {
'10.2.7': '10.2.7-h34',
'10.2.10': '10.2.10-h36',
'10.2.13': '10.2.13-h21',
'10.2.16': '10.2.16-h7',
'10.2.18': '10.2.18-h6',
'11.1.4': '11.1.4-h33',
'11.1.6': '11.1.6-h32',
'11.1.7': '11.1.7-h6',
'11.1.10': '11.1.10-h25',
'11.1.13': '11.1.13-h5',
'11.1.15': '11.1.15',
'11.2.4': '11.2.4-h17',
'11.2.7': '11.2.7-h13',
'11.2.10': '11.2.10-h6',
'11.2.12': '11.2.12',
'12.1.4': '12.1.4-h5',
'12.1.7': '12.1.7',
}
BROWSER_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36'
def b64url_decode(data: str) -> bytes:
data = data.strip()
pad = '=' * ((4 - len(data) % 4) % 4)
return base64.urlsafe_b64decode(data + pad)
def parse_version(v: str):
vs = v.strip()
saas = vs.endswith('.saas')
if saas:
vs = vs[:-5]
m = re.fullmatch(r'(\d+)\.(\d+)\.(\d+)(?:-h(\d+))?', vs)
if not m:
return None
major, minor, patch, hotfix = m.groups()
return (int(major), int(minor), int(patch), int(hotfix or 0), saas)
def compare_versions(a: str, b: str):
pa = parse_version(a)
pb = parse_version(b)
if pa is None or pb is None:
raise ValueError('unparseable version')
ta = pa[:4]
tb = pb[:4]
if ta < tb:
return -1
if ta > tb:
return 1
return 0
def fixed_for(version: str):
pv = parse_version(version)
if pv is None:
return None
base = f'{pv[0]}.{pv[1]}.{pv[2]}'
return FIXED.get(base)
def extract_text(elem, name):
found = elem.find('.//' + name)
if found is None or found.text is None:
return ''
return found.text.strip()
def get_version_from_saml_request(saml_request_b64: str):
html = base64.b64decode(saml_request_b64)
text = html.decode('utf-8', errors='replace')
m = re.search(r'name=["\']Token["\']\s+value=["\']([^"\']+)["\']', text, re.I)
if not m:
raise ValueError('Token not found in decoded saml-request')
token = m.group(1)
parts = token.split('.')
if len(parts) < 2:
raise ValueError('JWT format invalid')
payload = json.loads(b64url_decode(parts[1]).decode('utf-8', errors='replace'))
version = payload.get('PanOSversion') or payload.get('panosversion')
if not version:
raise ValueError('PanOSversion claim missing')
return version
def main():
if len(sys.argv) != 2:
print('Usage: python3 panos_cve_2026_0265_check.py https://gp.example.com')
sys.exit(3)
target = sys.argv[1].strip()
if not re.match(r'^https?://', target, re.I):
target = 'https://' + target
url = target.rstrip('/') + '/global-protect/prelogin.esp'
try:
r = requests.get(url, timeout=20, verify=False, headers={'User-Agent': BROWSER_UA})
except Exception as e:
print(f'UNKNOWN - request failed: {e}')
sys.exit(2)
if r.status_code != 200:
print(f'UNKNOWN - HTTP {r.status_code}')
sys.exit(2)
body = r.text
if 'Valid client certificate is required' in body:
print('UNKNOWN - portal is mTLS-gated; external anonymous verdict not possible')
sys.exit(2)
try:
root = ET.fromstring(body)
except Exception as e:
print(f'UNKNOWN - XML parse failed: {e}')
sys.exit(2)
cas_auth = extract_text(root, 'cas-auth').lower()
if cas_auth == 'no':
print('PATCHED - CAS not attached on this GlobalProtect portal; vulnerable code path not reachable')
sys.exit(0)
if cas_auth != 'yes':
print('UNKNOWN - could not determine CAS attachment or target is not a GlobalProtect portal')
sys.exit(2)
saml_request = extract_text(root, 'saml-request')
if not saml_request:
print('UNKNOWN - CAS appears enabled but saml-request token was not present')
sys.exit(2)
try:
version = get_version_from_saml_request(saml_request)
except Exception as e:
print(f'UNKNOWN - failed to recover PAN-OS version: {e}')
sys.exit(2)
parsed = parse_version(version)
if parsed is None:
print(f'UNKNOWN - unparseable PAN-OS version: {version}')
sys.exit(2)
if parsed[4]:
print(f'PATCHED - cloud-managed .saas build not affected ({version})')
sys.exit(0)
fixed = fixed_for(version)
if not fixed:
print(f'UNKNOWN - PAN-OS base not in local fixed-version map ({version})')
sys.exit(2)
try:
cmpv = compare_versions(version, fixed)
except Exception as e:
print(f'UNKNOWN - version comparison failed: {e}')
sys.exit(2)
if cmpv < 0:
print(f'VULNERABLE - CAS attached and {version} is below fixed build {fixed}')
sys.exit(1)
else:
print(f'PATCHED - CAS attached and {version} is at or above fixed build {fixed}')
sys.exit(0)
if __name__ == '__main__':
main()
If you remember one thing.
Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.