A self-contained JWT security assessment tool for authorised penetration testing and bug bounty research. Pure Python standard library — no external dependencies.
- Decode & Analyse — Pretty-print JWT structure with automated security flagging
- Crack — Brute-force HMAC secrets against wordlists (HS256/384/512)
- Forge — Re-sign tokens with tampered claims using known/cracked secrets
- Alg-None — Generate
alg:nonebypass variants - Confuse — RSA→HMAC key confusion attack (RS256 → HS256)
- KID Injection — Generate path traversal, SQLi, and command injection variants via the
kidheader
No installation required. Just Python 3.6+:
chmod +x jwtforge.py
./jwtforge.py -hOr run directly:
python3 jwtforge.py <command>Pretty-print the JWT structure and flag common security issues:
python3 jwtforge.py decode "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"Security flags:
- ⚠ Weak algorithms (
alg=none) - ⓘ Symmetric vs asymmetric algorithm usage
- ⚠ Missing
expclaim (token never expires) - ⚠ Sensitive claims exposed in payload (passwords, API keys)
- ⓘ Missing issuer/audience claims
- ⓘ Path traversal patterns in
kidheader
Test weak HMAC secrets against a wordlist:
python3 jwtforge.py crack "<token>" /path/to/wordlist.txtSupports: HS256, HS384, HS512
Example wordlist: /usr/share/wordlists/rockyou.txt (if available on Kali)
Re-sign a JWT with a known secret and optionally modify claims:
# Basic re-sign
python3 jwtforge.py forge "<token>" --secret "mysecret"
# Role escalation attempt
python3 jwtforge.py forge "<token>" --secret "mysecret" --set role=admin
# Modify multiple claims
python3 jwtforge.py forge "<token>" --secret "mysecret" --set role=admin --set sub=attackerGenerate alg:none bypass variants (signature stripped):
python3 jwtforge.py alg-none "<token>"Generates variants: none, None, NONE, nOnE, NoNe, NONe, nonE, NOne — mixed casing defeats naive blocklists that only reject the exact lowercase string.
What it does: Removes the signature and sets alg: none. If the server skips signature verification for this algorithm, the token will be accepted regardless of payload tampering.
Re-sign an RSA token using the RSA public key as an HMAC secret:
python3 jwtforge.py confuse "<token>" --pubkey /path/to/public.pemWhat it does: If the server mistakenly uses the RSA public key to verify HMAC signatures (a critical implementation bug), this attack lets you forge tokens by signing with the public key.
Produces: HS256/384/512 variants signed against several faithful representations of the key — the PEM exactly as stored, with and without a trailing newline, CRLF-normalised, and the base64 body alone. This matters: the byte-for-byte value a server uses as the HMAC secret is library-specific (usually the full PEM on disk, not the stripped body). Trying one representation is why this attack is often wrongly written off as "not vulnerable" — try each generated token; the one that authenticates tells you exactly how the server loads the key.
Generate injection payloads via the kid (Key ID) header:
python3 jwtforge.py kid "<token>" --inject "/var/www/html"What it does: The kid header specifies which key to use for verification. Vulnerable applications may use this value in a file path, SQL query, or command without proper sanitisation.
Generated variants — where the resulting key is predictable, the token is re-signed so it actually validates (not left with a stale, useless signature):
/dev/null(path traversal & absolute) — forces an empty key; token is re-signed withb"", so it authenticates against any server that loads the key from that path.- SQLi returning an attacker-controlled key —
... UNION SELECT 'AAAA'-- -; token re-signed withAAAA, valid if the injection lands. - Path traversal to an attacker keyfile — signature kept as-is (outcome depends on the file the server reads).
- Command injection — signature kept as-is (server-dependent).
The re-signed variants are the important upgrade: a kid pointing at /dev/null only bypasses auth if the token is also signed with the empty key — otherwise the server rejects it. This tool does that step for you.
JWT supports an alg: none algorithm intended for debugging. The spec says implementations MUST reject unsigned tokens, but many libraries don't properly enforce this. An attacker can:
- Tamper with the payload (e.g., set
role: admin) - Change
algtonone - Strip the signature
If the server accepts it, they've bypassed authentication entirely.
RSA signatures use a private key to sign and a public key to verify. HMAC uses the same secret for both.
The vulnerability: Some implementations use whatever key is available for verification. If you can get the RSA public key and the server uses it as an HMAC secret, you can forge valid tokens.
The kid header tells the server which key to use:
{
"alg": "HS256",
"kid": "/path/to/secret.key",
"typ": "JWT"
}Vulnerable code might look like:
key_path = f"/keys/{header['kid']}"
key = open(key_path).read() # Unsafe!An attacker sets kid: ../../../etc/passwd and the server verifies with the contents of /etc/passwd as the HMAC secret.
0— Success1— Error / Secret not found130— Interrupted by user (Ctrl+C)
Authorised use only. This tool is for legitimate security testing, penetration testing with written authorisation, and bug bounty programs within scope. Unauthorised use is illegal. Use responsibly.
For authorised pentesting and bug bounty use only.
CobraSEC — Offensive Security & Adversary Emulation