JWT alg:none ainda existe em produção em 2026. Aqui está como
JWT alg:none virou meme em 2015. Em 2026 ainda aparece em produção em SaaS brasileiros. Análise das 4 variantes modernas e como testar e corrigir.
Pesquisador de segurança e fundador da No Vuln

Em 2015, JWT alg: none virou meme — a vulnerabilidade tão básica que ninguém poderia ter mais em produção. Em 2026, eu ainda acho em produção. Como é possível?
Resposta curta: o bug original “morreu” (libraries modernas rejeitam por padrão), mas 4 variantes modernas continuam aparecendo em pentest com regularidade incômoda. Esse post é o guia técnico de cada variante, como detectar em 30 segundos e como corrigir.
O bug original (recapitulação rápida)
JWT permite especificar o algoritmo de assinatura no header. Em 2015, descobriram que muitas libraries aceitavam alg: none — ou seja, JWT sem assinatura. Atacante editava o payload, removia a assinatura, e o servidor aceitava:
- Header:
{"alg":"none","typ":"JWT"} - Payload:
{"sub":"admin","role":"admin"} - Signature: (vazia)
Servidor processava como token válido de admin. Catástrofe global.
Hoje, libraries modernas (jsonwebtoken v9+, PyJWT 2+, JJWT 0.12+) rejeitam alg: none por padrão. Bug morto. Exceto que não está morto. Migrou de forma.
Variante 1: Algorithm Confusion (HS256 vs RS256)
O bug: servidor configurado para validar JWT com RS256 (assinatura assimétrica), mas a função de verificação aceita HS256 (assinatura simétrica). Atacante usa a chave PÚBLICA RSA do servidor como segredo HMAC.
Por que funciona:
- RS256 valida com
chave_publica - HS256 valida com
secret_compartilhado - A chave pública RSA está disponível em endpoint público (
/.well-known/jwks.jsonou similar) - Função
jwt.verify(token, chave_publica)sem especificar algoritmo aceita ambos - Atacante forja JWT com
alg: HS256usando a chave pública como segredo HMAC - Servidor valida com HS256 + chave pública = sucesso
Como detectar em pentest:
- Capturar JWT legítimo
- Decodificar header (sem validar) e identificar
alg: RS256 - Tentar forjar com HS256 usando chave pública como secret
- Se servidor aceitar → vulnerabilidade confirmada
Como corrigir:
- ❌ Vulnerável:
jwt.decode(token, public_key) - ✅ Correto:
jwt.decode(token, public_key, algorithms=['RS256'])com whitelist explícita
Variante 2: JKU / X5U SSRF
O bug: parâmetros opcionais do JWT header (jku, x5u) permitem indicar URL externa de onde buscar a chave de verificação. Servidor que confia cegamente faz request para URL controlada por atacante.
Exploração: atacante adiciona "jku": "https://atacante.com/jwks.json" no header do JWT. Servidor faz GET na URL atacante, recebe chave pública atacante, valida JWT com ela → atacante forjou token válido.
Bonus: além de bypass de auth, vira SSRF chain. Adicionar "jku": "http://169.254.169.254/latest/meta-data/iam/security-credentials/" faz servidor request metadata interno da AWS. Resposta vai para parser de JWKS (falha de parse, mas request foi feito). Cloud metadata exfiltrado.
Como detectar:
- Adicionar
jkuao header de JWT legítimo apontando parahttps://burpcollaborator.net/ - Enviar request com JWT modificado
- Se collaborator recebe ping → vulnerabilidade confirmada
Como corrigir:
- Allowlist explícita de URLs JKU permitidas
- Melhor: ignorar completamente parâmetros
jku/x5use você não os usa (configurar library para desabilitar)
Variante 3: kid Path Traversal
O bug: parâmetro kid (Key ID) usado para selecionar qual chave validar o token. Servidor que usa kid como path para arquivo no filesystem aceita path traversal.
Exploração clássica: atacante envia "kid": "../../../../tmp/known_content". Se atacante consegue escrever em /tmp/known_content(via upload de arquivo, log poisoning, etc.), pode fazer servidor “ler” essa chave conhecida e validar JWT forjado com ela.
Variante mais comum em 2026: kid aceita SQL injection (servidor query no DB pela chave). Atacante envia "kid": "1' UNION SELECT 'CHAVE_CONHECIDA' --".
Como detectar:
- Capturar JWT, modificar
kidpara../etc/passwd - Se erro de validação muda (file not found, parse error específico) → vulnerável
- Testar SQL injection no
kid(' OR 1=1 --)
Como corrigir: kid deve ser ID opaco, validado contra allowlist fixa. NUNCA usado como path filesystem ou string SQL.
Variante 4: Token expiration ignorado
O bug: mais simples e mais comum do que parece. Servidor não valida o claim exp. Algumas libraries não validam por padrão — você precisa passar flag explícita.
Token roubado em 2023 ainda válido em 2026. Sessão eterna. É o tipo de bug que sobrevive a múltiplos pentests porque ninguém pensa em testá-lo (todo mundo assume que valida).
Como detectar:
- Modificar
expno payload para data passada - Re-assinar (se você tem o secret de teste) ou usar token expirado real
- Se servidor aceitar → vulnerável
Como corrigir: sempre passar options={'require': ['exp'], 'verify_exp': True} ou equivalente da library.
Por que essas variantes ainda existem em 2026
Três motivos práticos:
1. Libraries antigas em produção legada
Sistemas ERP, sistemas governamentais, sistemas legados de banco usam libraries de 2017–2020 que ainda têm os bugs. Migração de version major é trabalho que ninguém prioriza.
2. Configurações default subótimas
Devs copiam exemplo do README: jwt.decode(token, key). Sem ler que precisa passar algorithms=['RS256']. CVE em produção esperando.
3. Code review sem foco em auth
Auth code raramente passa por code review especializado. Bug entra em PR de feature “menor” e nunca é visto. Toda equipe que não tem auth review específico tem pelo menos 1 dessas variantes em produção, estatisticamente.
Como testar você mesmo em 30 minutos
Use jwt.io ou ferramenta similar:
- Cole seu JWT no decoder
- Verifique o
algno header - Modifique para
nonee re-cole — se servidor aceitar (testando contra endpoint protegido), vulnerável - Se
alg: RS256— tente HS256 usando chave pública como secret (variante 1) - Adicione
jkuao header apontando para servidor seu — se servidor faz request, variante 2 confirmada - Modifique
exppara data passada — se aceitar, variante 4
5 testes. 30 minutos. Se alguma passar — CVE aberto em produção.
Configuração segura completa
O padrão correto que toda implementação de JWT deveria seguir:
- Algorithms whitelist:
algorithms=['RS256']ou equivalente, sem fallback - Claims obrigatórios:
options={'require': ['exp', 'iat']} - Validação ativa:
verify_exp, verify_iat, verify_nbf, verify_aud, verify_isstodosTrue - Audience esperada:
audience='api.alvo.com' - Issuer esperado:
issuer='https://auth.alvo.com' - Sem
jkuaceito. Semkidque aceite path. Sem fallback de algoritmo. Sem expiração opcional.
A lição mais importante
Os 4 padrões acima não são bugs obscuros. Cada um aparece em pentest com regularidade. A diferença entre time vulnerável e time seguro não é talento — é processo:
- Code review focado em auth
- Pentest periódico que valida especificamente esses padrões
- Atualização de libraries (PyJWT, jsonwebtoken sempre na última major)
- CI com testes automatizados de JWT validation
Se sua aplicação usa JWT (e quase toda usa em 2026), vale 30 minutos testando você mesmo os 4 vetores antes de descobrir do jeito difícil.
Para conversar sobre pentest focado em auth e JWT, veja as páginas dedicadas: /pentest-api e /pentest-saas. Para entender outros padrões de auth que dominam ATO em 2026, veja OAuth Account Takeover.
Próximo passo
Quer aplicar isso ao seu sistema?
A No Vuln faz pentest profundo com a mesma metodologia descrita neste artigo. Solicite uma proposta — NDA bilateral em 24h, escopo definido em call técnica.