[2026-08-23]::Web App Pentest::5 min read

None of Your Business — Bypassing JWT Signature Validation

How a forgotten 'alg: none' code path turned a read-only audit token into full admin access.


Finding has been remediated and disclosed to the client. All screenshots below are recreated mockups with fabricated data — no real hosts, credentials, or personal information from any engagement.

The Setup

Every API pentest starts the same way: get a token, decode it, poke it. This time the target was a fairly standard REST backend sitting behind an API gateway — Bearer auth, JSON responses, the usual admin/audit role split. I’d been handed a low-privilege audit account: read-only access, no write scopes, nothing exciting on paper.

Naturally, the first stop wasn’t the API surface. It was the token itself.

Recon: Reading the Token

Dropped the JWT into a decoder and looked at the header:

{
  "alg": "HS256",
  "typ": "JWT"
}

Standard HMAC signing. Nothing screams “vulnerable” yet — but alg confusion bugs are common enough in home-grown or older JWT middleware that it’s always worth a poke before moving on. The payload had the shape you’d expect from a role-based system:

{
  "roleId": 135,
  "roleName": "AUDIT_USER",
  "roleType": "READ_ONLY",
  "status": "ACTIVE"
}

The Bug: Signature Exclusion via alg: none

Some JWT libraries — especially older ports or homegrown parsers — treat alg as advisory rather than mandatory. If the server accepts none as a valid algorithm, it means: trust this payload, don’t bother checking the signature.

I ran a quick exclusion matrix against the auth header — flipping the algorithm field through a handful of values and either stripping or mangling the signature segment on each request:

JWT signature exclusion results table showing every alg:none variant returning HTTP 200

Every single variant came back 200 OK with a fully populated response. The signature verification wasn’t just weak — for this endpoint, it wasn’t happening at all once alg left the small set of values the developer had explicitly tested against.

From Read-Only to God Mode

Here’s where it stopped being a curiosity. Since the server wasn’t validating the signature, it also wasn’t validating that I had generated that token. I could hand-craft the payload directly:

{
  "roleId": 1,
  "roleName": "SYSTEM_ADMIN",
  "roleType": "FULL_ACCESS",
  "status": "ACTIVE"
}

Base64-encode header + payload, tack on an empty (or garbage) signature segment, send it as the Bearer token — and the gateway happily treated me as a fully privileged admin. No password, no MFA prompt, no second factor to bypass. Just an unsigned claim the server chose to believe.

Forged unsigned JWT sent as a Bearer token, returning a full admin listing in the response

From there it was a normal admin session: user enumeration, role management endpoints, the whole back office. The “audit” account I’d started with had effectively become root for the application layer.

Why This Keeps Happening

alg: none bugs feel like a relic — CVE-worthy write-ups about this exact class go back over a decade — but they resurface constantly in:

  • Custom JWT parsing logic instead of a maintained library
  • Middleware upgrades that silently reintroduce permissive algorithm handling
  • API gateways that terminate auth before the app, and assume downstream services will re-validate (they often don’t)
  • Libraries where “algorithm allowlisting” is opt-in instead of the default

The root cause is almost always the same: the server trusts the client to declare how it should be verified. A JWT should never get to vote on its own validation method.

Lessons

For defenders:

  • Hard-code the expected algorithm server-side. Never read alg from the incoming token to decide how to verify it.
  • Explicitly reject none at the library/config level — don’t rely on it being “off by default,” confirm it.
  • Treat JWT verification failures as loud events, not silent 200s.
  • Re-validate tokens at every service boundary, not just the gateway.

For attackers/testers:

  • Always run the algorithm exclusion matrix (none, case variations, HS256RS256 confusion) on every JWT-authenticated endpoint, even ones that look boring.
  • A “read-only audit” token is still worth full attention — privilege escalation bugs love low-value accounts precisely because nobody hardens the ceiling above them.
  • Confirm impact before declaring critical: forging a token is one thing, proving it maps to real unauthorized actions (data access, state change) is what makes the finding land.