Back to Intelligence

USN-8916-2: Ubuntu 26.10 PyJWT Token Forging Fixes — Detection and Remediation Guide for Defenders

SA
Security Arsenal Team
October 19, 2026
9 min read

Canonical has released USN-8916-2, an Ubuntu security update addressing multiple token-forging vulnerabilities in PyJWT, the de facto standard Python library for creating and validating JSON Web Tokens. The advisory applies to Ubuntu 26.10 and fixes several security issues in a single update — a pattern that should immediately raise concern for any organization running Python-based authentication services on Ubuntu.

Here is why this matters operationally: PyJWT sits in the authentication path of an enormous share of modern infrastructure. FastAPI, Django REST Framework, Flask middleware, internal microservice meshes, CI/CD token validation, API gateways written in Python — if any of these decode and trust JWTs using a vulnerable PyJWT build, an attacker who can forge a token effectively becomes any user, including administrators, without credentials. Token forging is not a denial-of-service nuisance; it is a silent authentication bypass that leaves almost no traditional brute-force telemetry.

If your SOC runs Ubuntu 26.10 hosts (or containers built from Ubuntu 26.10 base images) serving Python applications that validate JWTs, treat this as a priority patching event and audit authentication logs for anomalous token usage during the exposure window.

Technical Analysis

Affected Products and Platforms

  • Library: PyJWT (python3-jwt package on Debian/Ubuntu systems; PyJWT on PyPI)
  • Platform: Ubuntu 26.10, per USN-8916-2
  • Downstream exposure: Any Python application, framework, or service that calls jwt.decode() using the vulnerable library version — including applications that vendor or pin PyJWT in virtual environments, containers, or serverless buildpacks (these are not fixed by an OS-level apt update and must be rebuilt separately)

How Token Forging Works Against JWT Validators

Without a published CVE identifier in the advisory summary, defenders should reason about the class of flaw, because PyJWT token-forging issues historically cluster around a small set of exploitable patterns:

  1. Algorithm confusion / weak algorithm enforcement. If the library accepts attacker-controlled alg header values without strict allowlisting, an attacker can downgrade verification — for example, presenting an alg: none token or substituting an HMAC algorithm (HS256) where an asymmetric algorithm (RS256/ES256) is expected, then signing with the public key as the HMAC secret. The forged token validates cleanly and the attacker controls every claim: sub, role, exp, aud.
  2. Claim validation bypass. Improper enforcement of exp, nbf, iss, or aud claims allows replayed, expired, or cross-service tokens to be accepted — enabling lateral movement between microservices that trust a shared issuer.
  3. Key handling flaws. Weaknesses in how the library resolves or coerces verification keys (e.g., accepting attacker-influenced key material via kid traversal or JWK injection) let attackers sign tokens with keys they control.

The attack chain in practice: the attacker obtains or crafts a token, manipulates the header/payload, signs it with a null or attacker-known key, and submits it to the target API. The vulnerable PyJWT decode() call accepts it. The application then authorizes the request as whatever identity the attacker embedded — with no failed-login events, no MFA prompts, and no password spray signatures for your detections to catch.

Exploitation Requirements and Status

Exploitation requires only network access to a JWT-protected endpoint and knowledge of the expected token structure — both trivially obtainable. No user interaction and no prior authentication are needed for pre-auth endpoints. As of this writing, the Ubuntu advisory does not reference a public proof-of-concept or confirmed in-the-wild exploitation, and no CISA KEV entry is associated with this update. However, JWT validation flaws are historically weaponized within days of disclosure because the exploitation primitives are well documented. Assume motivated attackers are diffing the patched package now.

Critical Scoping Caveat

An apt upgrade fixes only the system-packaged python3-jwt. It does not fix:

  • PyJWT installed via pip into virtual environments or user site-packages
  • PyJWT pinned in requirements.txt / poetry.lock inside application repos
  • Container images that bundle their own PyJWT layer

This is where most organizations get burned — they patch the OS and declare victory while their actual production containers remain vulnerable.

Detection & Response

The honest truth about library-level auth flaws: you cannot write a signature for "vulnerable PyJWT executed." What you can do is (a) inventory where vulnerable versions exist, and (b) hunt for the behavioral artifacts of forged tokens in your web and API telemetry. The detections below target exactly those two goals.

YAML
---
title: JWT With None Algorithm Observed in Web Request
tid: 4c8a2f19-7b3d-4e61-9a52-8f1d6c0b3e77
status: experimental
description: Detects JWT tokens presented with alg=none or 'none'-variant headers in web/proxy request logs, a hallmark of JWT algorithm-confusion and token-forging attacks against Python services using PyJWT. The JWT header segment eyJhbGciOiJub25l decodes to {"alg":"none...
references:
  - https://linuxsecurity.com/advisories/ubuntu/ubuntu-8916-2-pyjwt
  - https://attack.mitre.org/techniques/T1550/004/
author: Security Arsenal
date: 2026/04/06
tags:
  - attack.defense_evasion
  - attack.t1550.004
logsource:
  category: webserver
detection:
  selection_jwt_none:
    c-uri|contains:
      - 'eyJhbGciOiJub25l'
      - 'eyJhbGciOiJOb25l'
      - 'eyJhbGciOiJOT05F'
  selection_auth_header:
    cs-headers|contains:
      - 'eyJhbGciOiJub25l'
      - 'eyJhbGciOiJOb25l'
      - 'eyJhbGciOiJOT05F'
  condition: 1 of selection_jwt_none or 1 of selection_auth_header
falsepositives:
  - Internal penetration tests or deliberately unsigned tokens in dev environments; production services should never accept alg=none
level: high
---
title: High Volume of JWT-Bearing Requests From Single Source to API Endpoints
id: 9e2b5d47-1c6a-4f83-b7d4-2a8e5c9f0d31
status: experimental
description: Detects a single source issuing repeated authenticated API requests with varying Authorization bearer tokens, consistent with token-forging attempts or claim fuzzing against JWT-protected Python services. Tune the threshold to your baseline API traffic.
references:
  - https://linuxsecurity.com/advisories/ubuntu/ubuntu-8916-2-pyjwt
  - https://attack.mitre.org/techniques/T1550/004/
author: Security Arsenal
date: 2026/04/06
tags:
  - attack.credential_access
  - attack.t1550.004
logsource:
  category: webserver
detection:
  selection:
    c-uri|contains:
      - '/api/'
      - '/auth/'
      - '/v1/'
      - '/v2/'
    cs-headers|contains: 'eyJ'
  timeframe: 5m
  condition: selection | count(c-uri) by c-ip > 200
falsepositives:
  - Legitimate high-volume API clients, load balancers, and health checkers; whitelist known service accounts and internal CIDRs
level: medium
KQL — Microsoft Sentinel / Defender
// Hunt for forged-JWT indicators in web/proxy telemetry ingested into Sentinel.
// Looks for alg=none tokens (base64url header eyJhbGciOiJub25l...) and
// sources presenting many distinct bearer tokens — consistent with PyJWT token forging.
let noneAlgHeader = dynamic(["eyJhbGciOiJub25l", "eyJhbGciOiJOb25l", "eyJhbGciOiJOT05F"]);
union isfuzzy=true
    (CommonSecurityLog
    | where RequestURL has_any (noneAlgHeader)
        or AdditionalExtensions has_any (noneAlgHeader)
    | project TimeGenerated, SourceIP, RequestURL, RequestMethod, DestinationHostName),
    (Syslog
    | where SyslogMessage has_any (noneAlgHeader)
    | project TimeGenerated, HostIP, Computer, SyslogMessage)
| summarize FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated), Hits=count()
  by SourceIP, RequestURL
| order by Hits desc;
// Secondary hunt: distinct-token fan-out per source IP against API paths (token fuzzing / claim manipulation)
CommonSecurityLog
| where RequestURL has_any ("/api/", "/auth/", "/v1/", "/v2/")
| where AdditionalExtensions has "eyJ"
| summarize DistinctTokens=dcount(extract(@"eyJ[A-Za-z0-9_-]{20,}", 0, AdditionalExtensions)),
            Requests=count()
  by SourceIP, bin(TimeGenerated, 5m)
| where DistinctTokens > 20
| order by DistinctTokens desc;
VQL — Velociraptor
-- Inventory PyJWT installations and versions across Linux endpoints.
-- Surfaces system packages, virtualenvs, and vendored copies that apt patching will miss.
LET pkg_files = SELECT FullPath, Mtime
FROM glob(globs=[
  '/usr/lib/python3*/dist-packages/jwt/__init__.py',
  '/usr/lib/python3*/dist-packages/PyJWT-*.dist-info/METADATA',
  '/usr/local/lib/python3*/dist-packages/PyJWT-*.dist-info/METADATA',
  '/home/*/.local/lib/python3*/site-packages/PyJWT-*.dist-info/METADATA',
  '/opt/**/site-packages/PyJWT-*.dist-info/METADATA',
  '/srv/**/site-packages/PyJWT-*.dist-info/METADATA',
  '/var/www/**/site-packages/PyJWT-*.dist-info/METADATA'
])

SELECT FullPath,
       parse_string_with_regex(string=FullPath,
           regex='PyJWT-(?P<ver>[0-9.]+)').ver AS InstalledVersion,
       Mtime AS LastModified
FROM pkg_files
ORDER BY InstalledVersion ASC
Bash / Shell
#!/usr/bin/env bash
# USN-8916-2 PyJWT remediation and verification script for Ubuntu 26.10
set -euo pipefail

echo "=== [1] Current OS-packaged PyJWT version ==="
dpkg -l python3-jwt 2>/dev/null || echo "python3-jwt not installed via apt"

echo "=== [2] Applying security updates ==="
apt-get update -qq
apt-get install -y --only-upgrade python3-jwt

echo "=== [3] Post-patch version verification ==="
dpkg -l python3-jwt | tail -n +6
python3 -c "import jwt; print('System PyJWT version:', jwt.__version__)" 2>/dev/null || true

echo "=== [4] Hunting for pip/virtualenv/vendored PyJWT copies NOT covered by apt ==="
find / -type d -name 'PyJWT-*.dist-info' 2>/dev/null | while read -r d; do
  ver=$(grep -m1 '^Version:' "$d/METADATA" 2>/dev/null | awk '{print $2}')
  echo "FOUND pip-installed PyJWT $ver at $d"
done

echo "=== [5] Scanning application dependency pins ==="
grep -rn --include='requirements*.txt' --include='poetry.lock' --include='Pipfile.lock' \
  -E 'pyjwt|PyJWT' /opt /srv /var/www /home 2>/dev/null | head -n 50

echo ""
echo "ACTION REQUIRED: Any pip/virtualenv/vendored copies above must be upgraded"
echo "independently (pip install -U PyJWT) and their services/containers REBUILT"
echo "and RESTARTED. An apt upgrade alone does not fix them."

Remediation

  1. Patch immediately. Apply USN-8916-2 on all Ubuntu 26.10 systems:
    Bash / Shell

sudo apt update && sudo apt install --only-upgrade python3-jwt

Code
   Confirm the installed version matches the fixed release listed in the [official advisory](https://linuxsecurity.com/advisories/ubuntu/ubuntu-8916-2-pyjwt) and the corresponding [Ubuntu Security Notice](https://ubuntu.com/security/notices) (USN-8916-2).

2. **Fix the shadow copies.** Enumerate every pip-installed, virtualenv, and container-bundled PyJWT (use the script and VQL above). Upgrade each to the patched release with `pip install -U PyJWT`, then **rebuild container images and restart every dependent service**. A running Python process holds the old library in memory until restarted.

3. **Harden JWT validation in application code** as a defense-in-depth measure regardless of library version:
   - Enforce an explicit algorithm allowlist: `jwt.decode(token, key, algorithms=["RS256"])` — never derive the accepted algorithm from the token header.
   - Require strict claim validation: `options={"require": ["exp", "iat", "iss", "aud"]}` and always pass `issuer=` and `audience=`.
   - Never accept `alg: none` in production.
   - Resolve `kid` against a fixed, server-controlled key map — never against attacker-reachable paths or URLs.

4. **Audit the exposure window.** Review API and authentication logs for the period the vulnerable version was deployed: tokens accepted with unusual claims (unexpected `sub`, elevated `role`/`scope` values), sessions without corresponding authentication events in your IdP logs, and any requests matching the `alg=none` indicators above. Token forging produces valid-looking sessions — correlate against upstream IdP issuance records to catch tokens your IdP never issued.

5. **Rotate keys if compromise is suspected.** If hunt results show forged-token usage, rotate JWT signing keys, revoke outstanding refresh tokens, and force re-authentication. A forged-token capability persists until the trust anchor changes.

6. **Add dependency scanning to CI.** Wire `pip-audit` or an equivalent SCA check into build pipelines so the next PyJWT-class advisory is caught at build time rather than by an analyst reading a mailing list.

## Related Resources

[Security Arsenal Red Team Services](https://securityarsenal.com/services/red-teaming)
[AlertMonitor Platform](https://securityarsenal.com/products/alertmonitor)
[Book a SOC Assessment](https://securityarsenal.com/contact)
[pen-testing Intel Hub](https://securityarsenal.com/intel/incident-response)

Is your security operations ready?

Get a free SOC assessment or see how AlertMonitor cuts through alert noise with automated triage.