Vulnerabilidades de JWT: como funcionam, como detectar e como corrigir
Tratamento fraco de JSON Web Tokens - alg:none, sem expiração ou um header kid manipulável - permite forjar ou reusar tokens e se passar por usuários.
Severidade típica: Alta · Verificado pelo módulo da NovaPentest: JWT Security
O que é
JSON Web Tokens (JWT, RFC 7519) são tokens compactos e autocontidos, muito usados para autenticação e estado de sessão. Um JWT tem três partes em base64url - header, payload e assinatura - separadas por pontos. O header indica o algoritmo de assinatura; o payload guarda claims como o subject, os papéis e uma expiração (exp); a assinatura permite ao servidor verificar que o token não foi alterado. A segurança de todo o esquema depende de o servidor verificar essa assinatura corretamente e confiar apenas no que consegue comprovar criptograficamente.
As vulnerabilidades surgem quando essa verificação é fraca ou opcional. A falha clássica é aceitar alg:none, em que o token declara não ter assinatura e uma biblioteca permissiva o considera válido - deixando qualquer um reescrever o payload. Problemas relacionados incluem confiar no algoritmo indicado no token (habilitando ataques de algorithm confusion), resolver a chave de assinatura a partir de um header kid influenciável pelo atacante, tokens sem claim exp que nunca expiram, tokens com tempo de vida excessivamente longo que ampliam a janela de replay e transmitir tokens em HTTP em texto claro, onde podem ser capturados.
Como muitas vezes o JWT é a sessão, forjar ou roubar um equivale a logar como a vítima - e, se os papéis estão nos claims, escalar para administrador. Isso se mapeia a OWASP Cryptographic Failures e Authentication Failures e a CWE-347 (verificação inadequada de assinatura) e CWE-613 (expiração de sessão insuficiente).
Como é explorada
O ataque de JWT de maior impacto mira a verificação de assinatura. Na variante alg:none, o atacante pega um token legítimo, edita o payload - por exemplo trocando "role":"user" por "role":"admin" ou mudando o subject -, define o algoritmo do header como "none" e remove a assinatura por completo. Um servidor que respeita o algoritmo do header aceita o token sem assinatura como autêntico, concedendo o que os claims forjados pedirem.
Algorithm confusion é primo disso: um serviço que verifica RS256 com uma chave pública pode ser enganado a verificar um token HS256 em que essa chave pública é usada como segredo do HMAC, permitindo ao atacante assinar os próprios tokens. Um header kid (key ID) manipulável é outro caminho - se o servidor usa o kid como caminho de arquivo ou URL para carregar a chave de verificação, o atacante pode apontá-lo para um arquivo previsível ou um servidor sob seu controle e assinar tokens com uma chave conhecida.
Tokens sem claim exp nunca expiram, então um único token roubado (via XSS, logs, vazamento no referrer ou um link compartilhado) funciona para sempre; expirações longas demais têm o mesmo efeito em menor escala. Tokens enviados em HTTP podem simplesmente ser capturados na rede e reusados. Em todos os casos o ganho é o mesmo: autenticar-se como outro usuário sem as credenciais dele e, quando os claims carregam privilégios, escalar para admin.
Como detectar
O módulo de JWT Security da NovaPentest acessa a URL alvo e procura tokens no formato JWT nos headers da resposta, nos headers Set-Cookie e no corpo. Se um token trafega por HTTP puro, reporta transmissão em texto claro como Critical.
Para até três tokens, ele decodifica o header e o payload e verifica: um header com o algoritmo "none" é reportado como Critical; um kid que parece caminho de arquivo ou URL é reportado como High; um payload sem a claim exp é reportado como High; e uma expiração a mais de 24 horas é reportada como Medium.
Ele também verifica, com um teste diferencial, se o servidor valida assinaturas. Envia o mesmo token duas vezes: uma com a assinatura propositalmente corrompida e outra sem assinatura e com o algoritmo "none". Só reporta Critical quando o servidor rejeita a assinatura corrompida com 401 ou 403, mas aceita o token sem assinatura. Uma página que responde 200 para qualquer token não é reportada, porque ela nem está validando tokens.
O módulo só inspeciona tokens que aparecem na resposta da URL alvo. Ele não faz login, então tokens emitidos só depois da autenticação e APIs que não são a URL escaneada ficam fora do que ele consegue testar.
Para verificar manualmente, decodifique cada segmento do token (um JWT debugger resolve, assim como `base64 -d` em cada parte) e leia alg, kid e exp. Confirme que o endpoint usa HTTPS. Depois, em um endpoint que exige autenticação, confira se um token com payload alterado ou sem assinatura é rejeitado com 401.
Como corrigir
1. Fixe o algoritmo no servidor. Configure a biblioteca de JWT com uma allowlist explícita de algoritmos aceitos (por exemplo HS256 ou RS256) e rejeite qualquer outro - nunca derive o algoritmo do header do token e sempre rejeite alg:none.
2. Verifique toda assinatura e falhe fechado. Um token que não verifica, ou cujo algoritmo não está na allowlist, deve ser rejeitado, não tratado como "sem assinatura mas válido".
3. Não deixe o token escolher a chave. Resolva as chaves de assinatura a partir da configuração do servidor; se usar kid, valide-o contra uma allowlist estrita de identificadores conhecidos e nunca o use como caminho de arquivo ou URL.
4. Sempre defina um exp curto. Use access tokens de vida curta (por volta de 15 a 60 minutos) com rotação de refresh token; adicione nbf/iat e valide-os.
5. Ofereça revogação. Mantenha uma denylist ou um claim de versão/geração de token por usuário para invalidar tokens roubados antes de expirarem.
6. Proteja os tokens em trânsito e em repouso. Sirva todo endpoint que carrega token por HTTPS/HSTS; para JWT em cookie, defina Secure, HttpOnly e SameSite. Mantenha tokens fora de URLs, logs e referrers.
7. Valide audience e issuer (aud/iss) para que um token emitido para um serviço não seja reusado contra outro.
Priorize alg:none e HTTP em texto claro primeiro (forja/interceptação total), depois ausência de expiração e tratamento de kid, e por fim tokens de vida longa.
Verifique o seu próprio site
Comece pelo scan passivo gratuito: não precisa de conta e você recebe uma nota de A a F cobrindo cabeçalhos, TLS, cookies, DNS e outras configurações que qualquer visitante consegue observar. Ele não envia payloads de ataque, então não confirma todas as classes de falha. O scan completo, disponível com uma conta gratuita, executa as verificações ativas, incluindo a descrita nesta página.