No Vuln — empresa brasileira de pentest e auditoria de segurança ofensiva
Voltar ao blog

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.

Vulnerabilidade·22 de maio de 2026·10 min de leitura
Diego Melo — autor
Diego Melo

Pesquisador de segurança e fundador da No Vuln

JWT alg:none ainda existe em produção em 2026. Aqui está como

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.json ou similar)
  • Função jwt.verify(token, chave_publica) sem especificar algoritmo aceita ambos
  • Atacante forja JWT com alg: HS256 usando a chave pública como segredo HMAC
  • Servidor valida com HS256 + chave pública = sucesso

Como detectar em pentest:

  1. Capturar JWT legítimo
  2. Decodificar header (sem validar) e identificar alg: RS256
  3. Tentar forjar com HS256 usando chave pública como secret
  4. 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:

  1. Adicionar jku ao header de JWT legítimo apontando para https://burpcollaborator.net/
  2. Enviar request com JWT modificado
  3. Se collaborator recebe ping → vulnerabilidade confirmada

Como corrigir:

  • Allowlist explícita de URLs JKU permitidas
  • Melhor: ignorar completamente parâmetros jku/x5u se 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:

  1. Capturar JWT, modificar kid para ../etc/passwd
  2. Se erro de validação muda (file not found, parse error específico) → vulnerável
  3. 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:

  1. Modificar exp no payload para data passada
  2. Re-assinar (se você tem o secret de teste) ou usar token expirado real
  3. 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:

  1. Cole seu JWT no decoder
  2. Verifique o alg no header
  3. Modifique para none e re-cole — se servidor aceitar (testando contra endpoint protegido), vulnerável
  4. Se alg: RS256 — tente HS256 usando chave pública como secret (variante 1)
  5. Adicione jku ao header apontando para servidor seu — se servidor faz request, variante 2 confirmada
  6. Modifique exp para 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_iss todos True
  • Audience esperada: audience='api.alvo.com'
  • Issuer esperado: issuer='https://auth.alvo.com'
  • Sem jku aceito. Sem kid que 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.

Compartilhar

WhatsAppLinkedInX

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.