This is a valet key that becomes dangerous only after the attacker is already inside the car
Palo Alto says CVE-2026-0251 covers multiple local privilege escalation bugs in the GlobalProtect app that let a non-admin user run arbitrary commands as NT AUTHORITY\SYSTEM on Windows and as root on macOS and Linux. Affected ranges are Windows/macOS 6.0.0 through 6.0.12, Windows/macOS 6.2.0 through 6.2.8-h9, Windows/macOS 6.3.0 through 6.3.3-h10, Linux 6.0.0 through 6.0.10, Linux 6.2.0 through 6.2.9, and Linux 6.3.0 through 6.3.3-h1; Android, ChromeOS, iOS, and UWP are not affected.
The important part is the friction audit: this is not initial access, not remote, and not internet-scalable. Palo Alto only discloses CWE-426, so the most plausible abuse pattern is a local search-path hijack against a privileged GlobalProtect component; that is serious on an already-compromised workstation, but it does not behave like a perimeter breaker. That keeps it in MEDIUM instead of HIGH despite the ugly end state.
4 steps from start to impact.
Land as a standard user with a commodity loader
- Local user account or user-level code execution on the endpoint
- GlobalProtect app installed in an affected version range
- Email security, browser isolation, EDR, and application control should block many pre-CVE footholds
- This prerequisite implies the attacker is already past your primary prevention layers
Stage a search-path hijack payload
CWE-426 classification, the attacker likely abuses an untrusted search path by planting a malicious executable or library where a privileged GlobalProtect component will search for it. The weaponized tool here is a custom search-path hijack payload using the ATT&CK T1574 family rather than an internet exploit kit.- A writable path that is searched by the vulnerable GlobalProtect component
- Knowledge of the target filename or load order
- Exact path/name requirements break many opportunistic attempts
- Least-privilege filesystem ACLs, tamper protection, and app control can block the file drop
Trigger the privileged helper or service
sc.exe, launchctl, systemctl, or simply a reconnect/login event that wakes the helper.- Ability to wait for or trigger the affected GlobalProtect process path resolution
- The service/helper runs with elevated privileges
- Service restart rights may be limited for non-admins
- Some environments may require timing the attack around reconnects, logons, or client updates
PanGPS, PanGPA, launch daemons, or Linux service units into unexpected child binaries.Convert user foothold into SYSTEM/root execution
cmd.exe/powershell.exe on Windows or shell tooling on macOS/Linux.- Successful privileged execution via the vulnerable component
- Post-escalation actions are noisy and often high-signal to EDR
- Modern endpoint controls can still contain blast radius after privilege escalation
The supporting signals.
| In-the-wild status | No active exploitation signal in the sources reviewed: Palo Alto says it was *not aware of malicious exploitation* as of the advisory update, and the CVE is *not* in CISA KEV. |
|---|---|
| Proof-of-concept availability | No public PoC was surfaced in the official/advisory-linked material; Palo Alto marks exploit maturity as UNREPORTED. Given the CWE-426 classification, a private search-path hijack proof is plausible, but there is no public weaponization evidence in the reviewed sources. |
| EPSS | User-provided EPSS is 0.00007; Tenable shows 0.00006 on its retrieved page, which is consistent with an extremely low near-term exploitation probability. The fetched public pages did not surface a reliable percentile for citation. |
| KEV status | Not listed in the Known Exploited Vulnerabilities Catalog. That matters because this is the strongest public exploitation triage input most enterprise patch teams use. |
| Vector reality check | Current vendor/NVD-enriched scoring shows CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N with a 5.9 Medium label. Translation: easy to use once local code exists, but local-only and low-privilege dependent. |
| Affected versions | Windows/macOS: < 6.0.13, < 6.2.8-h10, < 6.3.3-h11. Linux: < 6.0.11, all 6.2.0 through 6.2.9, and < 6.3.3-h2. Android, ChromeOS, iOS, and UWP are unaffected per the vendor advisory. |
| Fixed versions | Windows/macOS fixed streams are 6.0.13, 6.2.8-h10 (6.2.8-948), and 6.3.3-h11 (6.3.3-c1016); Linux fixed streams are 6.0.11 and 6.3.3-h2 (6.3.3-c42), with Linux 6.2.x customers effectively moving to the 6.3.3-h2 train. |
| Exposure population | This is a client-side endpoint issue, so internet census tools are the wrong lens: Censys Search maps internet-exposed services, but that does not tell you how many laptops have a vulnerable GlobalProtect app installed. Your CMDB, EDR, MDM, and software inventory are the authoritative exposure sources here. |
| Disclosure timeline | Published on 2026-05-13; advisory updated through 2026-06-02, including version corrections for 6.3.x Windows/macOS and acknowledgement updates. That history matters because early downstream mirrors showed stale fixed-version guidance. |
| Reporter / discovery source | The vendor says the issue was discovered externally and credits *internal security research teams, Liang Zhu, and Martijn van Ramesdonk* in the acknowledgement section of the advisory. |
noisgate verdict.
The single biggest severity suppressor is that exploitation requires an attacker to already have a local low-privilege foothold on the endpoint. That makes this a post-initial-access privilege escalator, not an internet-scale entry point, even though successful abuse ends in full SYSTEM/root control.
Why this verdict
- Starts dangerous on impact: successful exploitation gives arbitrary command execution as
SYSTEMorroot, which is endpoint-total compromise. - Downward pressure: attacker position is already post-breach. The chain requires local user context or user-level malware, which implies the attacker has already beaten your initial-access controls.
- Downward pressure: reachable population is narrower than it looks. GlobalProtect is widely deployed, but only installed endpoints in specific version trains are relevant, and the bug is not remotely enumerable from the public internet.
- Slight upward pressure: blast radius per host is high. On a managed VPN client, SYSTEM/root lets an operator tamper with defenses and deepen persistence, so this is not backlog junk.
Why not higher?
There is no unauthenticated remote path, no network exposure requirement, no active exploitation evidence, and no public PoC in the reviewed sources. For a 10,000-host environment, this lands in the bucket of important endpoint hardening and privilege containment, not perimeter emergency response.
Why not lower?
Once the attacker reaches the vulnerable step, the payoff is full administrative execution with no user interaction required. A local PE that turns a normal user into SYSTEM/root on a widely deployed enterprise VPN client is still consequential enough that dismissing it as LOW would understate the per-host blast radius.
What to do — in priority order.
- Inventory GlobalProtect versions from EDR/MDM — Use software inventory to identify Windows, macOS, and Linux endpoints in the affected streams and separate them by version train. For a MEDIUM verdict there is no noisgate mitigation SLA, so do this as part of normal vulnerability operations while you work toward the remediation window.
- Tighten application control around privileged client paths — Constrain unsigned or user-writable binaries from being executed or loaded by privileged GlobalProtect components, especially via WDAC/AppLocker, macOS execution controls, and Linux package/file integrity controls. This is one of the few meaningful compensating controls for a likely search-path hijack pattern; there is no mitigation SLA for MEDIUM, but keep it in place until patched.
- Harden user-writable search paths — Review filesystem ACLs, PATH-like resolution behavior, and writable directories adjacent to GlobalProtect helper/service execution contexts so a standard user cannot plant executable content in privileged search locations. That reduces exploit reliability immediately even where patch rollout is slower.
- Hunt for elevated child-process anomalies — Create detections for unexpected child processes of GlobalProtect components such as
PanGPS,PanGPA, launch daemons, and Linux service units, especially where the child binary is unsigned or launched from user space. This does not fix the bug, but it gives you the best chance to catch exploitation if an already-compromised endpoint tries to cash in the PE.
- Perimeter controls like WAF, VPN portal ACLs, or internet blocking do not help because this is not a network-reachable flaw in the portal/gateway plane.
- MFA does not stop local privilege escalation after the attacker already has code execution on the endpoint.
- Only patching PAN-OS GlobalProtect gateways does nothing for this CVE; the vulnerable component is the endpoint app.
Crowdsourced verification payload.
Run this on the target endpoint itself from a local shell or your EDR live-response console: python3 gp_cve_2026_0251_check.py on macOS/Linux or py gp_cve_2026_0251_check.py on Windows. It needs only normal user rights for most checks, though some locked-down Windows installs may require local admin to read all uninstall registry keys; output is VULNERABLE, PATCHED, or UNKNOWN.
#!/usr/bin/env python3
# CVE-2026-0251 GlobalProtect version check
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
import os
import platform
import plistlib
import re
import subprocess
import sys
from pathlib import Path
APP_NAME = 'GlobalProtect'
class V:
def __init__(self, major, minor, patch, hotfix=0):
self.major = major
self.minor = minor
self.patch = patch
self.hotfix = hotfix
def key(self):
return (self.major, self.minor, self.patch, self.hotfix)
def __lt__(self, other):
return self.key() < other.key()
def __le__(self, other):
return self.key() <= other.key()
def __ge__(self, other):
return self.key() >= other.key()
def __gt__(self, other):
return self.key() > other.key()
def __repr__(self):
return f'{self.major}.{self.minor}.{self.patch}-h{self.hotfix}' if self.hotfix else f'{self.major}.{self.minor}.{self.patch}'
def parse_version(text):
if not text:
return None
# Accept versions like 6.3.3-h11, 6.3.3, 6.2.8-948, 6.3.3-c1016
m = re.search(r'(\d+)\.(\d+)\.(\d+)(?:-h(\d+))?', text)
if not m:
return None
major, minor, patch = int(m.group(1)), int(m.group(2)), int(m.group(3))
hotfix = int(m.group(4)) if m.group(4) else 0
return V(major, minor, patch, hotfix)
def run_cmd(cmd):
try:
p = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
out = (p.stdout or '') + '\n' + (p.stderr or '')
return out.strip()
except Exception:
return ''
def detect_windows_version():
# Registry via PowerShell (most reliable without pywin32 dependency)
ps = r'''
$paths = @(
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*',
'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*'
)
$items = foreach ($p in $paths) {
Get-ItemProperty -Path $p -ErrorAction SilentlyContinue |
Where-Object { $_.DisplayName -match 'GlobalProtect' } |
Select-Object -First 1 DisplayName, DisplayVersion
}
$first = $items | Select-Object -First 1
if ($first) { $first.DisplayVersion }
'''
out = run_cmd(['powershell', '-NoProfile', '-Command', ps])
v = parse_version(out)
if v:
return v, out.strip()
# Fallback: WMIC may still exist on some systems
out = run_cmd(['wmic', 'product', 'where', "name like 'GlobalProtect%%'", 'get', 'version'])
v = parse_version(out)
if v:
return v, out.strip()
return None, ''
def detect_macos_version():
candidates = [
'/Applications/GlobalProtect.app/Contents/Info.plist',
'/Library/PreferencePanes/GlobalProtect.prefPane/Contents/Info.plist'
]
for p in candidates:
try:
if Path(p).exists():
with open(p, 'rb') as f:
info = plistlib.load(f)
text = str(info.get('CFBundleShortVersionString') or info.get('CFBundleVersion') or '')
v = parse_version(text)
if v:
return v, text
except Exception:
pass
out = run_cmd(['defaults', 'read', '/Applications/GlobalProtect.app/Contents/Info', 'CFBundleShortVersionString'])
v = parse_version(out)
if v:
return v, out.strip()
return None, ''
def detect_linux_version():
# App CLI
for cmd in [
['globalprotect', 'version'],
['globalprotect', 'show', '--version'],
['/opt/paloaltonetworks/globalprotect/globalprotect', 'version']
]:
out = run_cmd(cmd)
v = parse_version(out)
if v:
return v, out.strip()
# Debian/Ubuntu package query
out = run_cmd(['dpkg-query', '-W', '-f=${Package} ${Version}\n', 'globalprotect'])
v = parse_version(out)
if v:
return v, out.strip()
# RPM query
for pkg in ['GlobalProtect', 'globalprotect']:
out = run_cmd(['rpm', '-q', pkg])
v = parse_version(out)
if v:
return v, out.strip()
return None, ''
def in_range(v, start, end_exclusive):
return v >= start and v < end_exclusive
def assess_windows_or_macos(v):
# Advisory-covered vulnerable windows/macOS streams
if in_range(v, V(6, 0, 0), V(6, 0, 13)):
return 'VULNERABLE'
if in_range(v, V(6, 2, 0), V(6, 2, 8, 10)):
return 'VULNERABLE'
if in_range(v, V(6, 3, 0), V(6, 3, 3, 11)):
return 'VULNERABLE'
# Known-not-listed stream: 6.1.x not present in vendor matrix
if v.major == 6 and v.minor == 1:
return 'UNKNOWN'
# Newer branches or fixed branches treated as patched
return 'PATCHED'
def assess_linux(v):
if in_range(v, V(6, 0, 0), V(6, 0, 11)):
return 'VULNERABLE'
# Vendor says 6.2.0 through 6.2.9 are vulnerable
if v.major == 6 and v.minor == 2 and v <= V(6, 2, 9, 999):
return 'VULNERABLE'
if in_range(v, V(6, 3, 0), V(6, 3, 3, 2)):
return 'VULNERABLE'
if v.major == 6 and v.minor == 1:
return 'UNKNOWN'
return 'PATCHED'
def main():
sysname = platform.system().lower()
version_obj = None
raw = ''
if 'windows' in sysname:
version_obj, raw = detect_windows_version()
platform_name = 'Windows'
elif 'darwin' in sysname:
version_obj, raw = detect_macos_version()
platform_name = 'macOS'
elif 'linux' in sysname:
version_obj, raw = detect_linux_version()
platform_name = 'Linux'
else:
print(f'UNKNOWN - unsupported platform: {platform.system()}')
sys.exit(2)
if not version_obj:
print(f'UNKNOWN - {APP_NAME} version not detected on {platform_name}')
sys.exit(2)
if platform_name in ('Windows', 'macOS'):
verdict = assess_windows_or_macos(version_obj)
else:
verdict = assess_linux(version_obj)
print(f'{verdict} - platform={platform_name} detected_version={version_obj} raw="{raw}"')
if verdict == 'PATCHED':
sys.exit(0)
elif verdict == 'VULNERABLE':
sys.exit(1)
else:
sys.exit(2)
if __name__ == '__main__':
main()
If you remember one thing.
Sources
- Palo Alto Networks advisory for CVE-2026-0251
- NVD entry for CVE-2026-0251
- CISA Known Exploited Vulnerabilities Catalog
- FIRST EPSS data and scoring notes
- Tenable CVE page showing EPSS and downstream risk data
- GlobalProtect 6.3.3-h11 Windows and macOS addressed issues
- GlobalProtect 6.3.3-h2 Linux addressed issues
- Censys Search overview
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.