Um adolescente de 16 anos, um JWT sem assinatura e 17 trilhões de linhas da Microsoft
Imagina a cena: você tem 16 anos, está fuçando APIs por diversão e descobre que a Microsoft esqueceu de validar a assinatura dos seus tokens JWT. Não em um serviço qualquer, mas em um sistema de analytics interno com acesso a 17,3 trilhões de registros. Parece roteiro de série da Netflix, mas aconteceu em setembro de 2026.
O pesquisador conhecido como “Faav” encontrou a falha no Microsoft Titan, um serviço interno de analytics que deveria estar protegido atrás de VPN e autenticação corporativa. O problema? O backend verificava os claims do token, mas nunca conferia se a assinatura era válida. Tipo checar o nome no crachá sem olhar a foto.
Eu já vi bugs de autenticação em produção, mas esse aqui é daqueles que fazem qualquer dev parar e pensar: “será que eu também estou fazendo isso?”
O que é o Microsoft Titan (e por que ele importa)
O Titan é um serviço interno da Microsoft feito para analytics e telemetria de produtos. Funcionários usam ele para rodar queries SQL em dados internos, dashboards e métricas de uso. Pense nele como um Metabase corporativo turbinado, conectado a 17 bancos de dados diferentes.
O serviço expõe uma rota /v2/Query que aceita queries SQL diretas. Em teoria, só funcionários autenticados via Azure AD, atrás de VPN, com tokens válidos teriam acesso. Em teoria.
| Dado | Valor | |
|---|---|---|
| —— | ——- | |
| Linhas acessíveis | 17,3 trilhões | |
| Bancos conectados | 17 | |
| Tabelas únicas | 9.863 | |
| Contas de funcionários | ~25.000 | |
| Emails expostos | 17.990 |
Esses números incluem dados do Bing Search Analytics, metadados internos, configurações de dashboards e registros de funcionários. Claro, “17,3 trilhões” inclui dados históricos, duplicados e derivados. Mas o ponto permanece: o acesso era total.
A anatomia do ataque: alg: none
Pra quem trabalha com APIs, JWT (JSON Web Token) é pão de cada dia. Você recebe um token, decodifica, valida a assinatura e confia nos claims. Pelo menos é assim que deveria funcionar.
Um JWT tem três partes separadas por ponto:
header.payload.signature
O header declara o algoritmo usado pra assinar:
{
"alg": "RS256",
"typ": "JWT"
}
O payload carrega os claims (quem é o usuário, quando expira, permissões):
{
"sub": "1234567890",
"upn": "user@microsoft.com",
"aud": "titan-api",
"exp": 1727654400
}
E a assinatura garante que ninguém alterou nada.
O ataque do Faav explorou algo que existe na própria especificação do JWT: o algoritmo none. Quando você seta "alg": "none" no header, está dizendo “esse token não tem assinatura”. E se o servidor aceitar isso sem reclamar… bom, você acabou de desligar toda a segurança.
{
"alg": "none",
"typ": "JWT"
}
O token forjado ficou assim (simplificado):
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1cG4iOiJhZG1pbiIsImF1ZCI6InRpdGFuLWFwaSJ9.
Repara no ponto final sem nada depois: é a assinatura vazia. O backend do Titan olhava os claims (upn, aud, tid, appid), mas nunca chamava a função de verificação criptográfica. Conferiu o crachá, mas não a foto.
O truque do upn: "admin"
Aqui a coisa fica ainda mais interessante. O Faav tentou primeiro colocar um email qualquer no claim upn (User Principal Name), mas não conseguia permissões elevadas. Aí veio o insight: em vez de user@microsoft.com, colocou simplesmente "admin".
{
"upn": "admin",
"aud": "titan-api",
"tid": "tenant-id-here",
"appid": "app-id-here"
}
Isso resolveu para uma conta de administrador local do Titan, com acesso total a todos os 17 bancos de dados. A partir daí, bastava mandar queries SQL pela rota /v2/Query:
SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES
Resultado: 9.863 tabelas. Acesso completo a dados de analytics do Bing, registros de funcionários, emails internos, configurações de produtos.
A timeline da divulgação (e o bounty ridículo)
A parte que mais dói é a resposta da Microsoft:
| Data | Evento | |
|---|---|---|
| —— | ——– | |
| 5 de setembro | Faav reporta a vulnerabilidade | |
| 9 de setembro | Microsoft restringe a API exposta | |
| 17 de setembro | Bounty concedido: US$ 5.000 |
Cinco mil dólares. Por uma falha que expunha 17,3 trilhões de registros, dados de 25 mil funcionários e analytics internos do Bing. Pra colocar em perspectiva: o café do escritório da Microsoft em Redmond provavelmente custa mais por mês.
O Faav agiu de forma responsável, extraiu apenas amostras limitadas para provar o conceito, não acessou dados pessoais de clientes, e reportou imediatamente. A Microsoft corrigiu em 4 dias úteis, o que até é rápido para uma big tech.
Mas US$ 5.000? Esse bounty virou meme no Hacker News. Um comentário resumiu bem: “US$ 5.000 pelo acesso a 17 trilhões de linhas dá menos de $0.000000000289 por registro.”
Por que esse bug ainda acontece em 2026
Você leu até aqui e está pensando: “mas alg: none é um bug conhecido há mais de 10 anos, como que isso ainda existe?” Ótima pergunta. E a resposta é desconfortável.
O problema está na especificação
O RFC 7519 (a spec do JWT) lista none como um algoritmo válido. A ideia original era permitir tokens pré-verificados em ambientes onde a integridade é garantida por outras camadas (TLS, por exemplo). Na prática, isso criou uma bomba-relógio que libraries mal implementadas e devs desavisados ativam sem querer.
Libraries que confiam demais no header
Muitas libraries de JWT fazem assim por padrão:
# ERRADO - aceita qualquer algoritmo que o token declarar
payload = jwt.decode(token, key, algorithms=jwt.get_unverified_header(token)["alg"])
O token malicioso diz “eu uso none“, a library obedece e pula a verificação. O fix é trivial:
# CORRETO - força o algoritmo esperado
payload = jwt.decode(token, key, algorithms=["RS256"])
Variantes de bypass
Mesmo quando a library bloqueia "none", atacantes testam variações de case:
NoneNONEnOnENoNe
Algumas implementações fazem comparação case-sensitive e deixam essas variantes passarem. Em 2026, isso ainda pega gente.
A falsa sensação de segurança
No caso do Titan, o serviço estava atrás de VPN e usava Azure AD. Os devs provavelmente pensaram: “quem vai forjar um token se já está autenticado na VPN?”. Esse raciocínio ignora que VPN não é boundary de confiança total. Qualquer funcionário (ou alguém com acesso à rede) poderia explorar a falha.
Como verificar se o seu código está vulnerável
Vou ser direto: se você trabalha com JWT, pare o que está fazendo e confira agora.
Node.js (jsonwebtoken)
// VULNERAVEL - não especifica algoritmo
const decoded = jwt.verify(token, secret);
// SEGURO - rejeita qualquer coisa que não seja RS256
const decoded = jwt.verify(token, publicKey, { algorithms: ['RS256'] });
Python (PyJWT)
# VULNERAVEL - aceita o que vier
payload = jwt.decode(token, secret, algorithms=None)
# SEGURO - whitelist de algoritmos
payload = jwt.decode(token, public_key, algorithms=["RS256"])
Go (golang-jwt)
// VULNERAVEL - sem validação de algoritmo
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return myKey, nil
})
// SEGURO - verifica o método de assinatura
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("algoritmo inesperado: %v", token.Header["alg"])
}
return myKey, nil
})
Java (jjwt)
// SEGURO - jjwt rejeita alg:none por padrão desde v0.12
Jwts.parser()
.verifyWith(publicKey)
.build()
.parseSignedClaims(token);
Checklist rápido
- [ ] Você especifica o algoritmo na verificação? (não deixa o token decidir)
- [ ] Seu servidor rejeita
alg: nonee todas as variações de case? - [ ] Você valida
iss,audeexpalém da assinatura? - [ ] Suas chaves de assinatura estão rotacionadas periodicamente?
- [ ] Você tem testes automatizados que enviam tokens com
alg: none?
Se marcou menos de 4, você tem trabalho pra fazer.
Outros ataques JWT que você deveria conhecer
O alg: none é o mais famoso, mas não é o único. Aqui vão outros que continuam pegando gente:
Algorithm Confusion (RS256 para HS256)
Quando o servidor usa RS256 (assimétrico), o atacante troca o header para HS256 (simétrico) e usa a chave pública (que é, bom, pública) como secret. Se a library aceitar a troca, a assinatura bate.
{
"alg": "HS256",
"typ": "JWT"
}
A assinatura é gerada com HMAC usando a chave pública RSA como secret. Se o servidor verificar com a mesma chave pública e aceitar HS256, o token passa.
JWK Injection
O header pode conter um campo jwk com a chave pública embutida. O atacante gera seu próprio par de chaves, assina o token, e inclui sua chave pública no header. Servers que confiam no jwk do token verificam a assinatura com a chave do atacante.
kid (Key ID) Injection
O campo kid identifica qual chave usar. Se o servidor usar esse valor em uma query (arquivo, banco de dados), o atacante pode injetar paths ou SQL:
{
"alg": "HS256",
"kid": "../../etc/passwd"
}
Ou pior:
{
"kid": "key' UNION SELECT 'secret' --"
}
O que fazer na prática: 7 regras de ouro
Depois de ver tudo isso, aqui vai o que eu recomendo pra qualquer projeto:
1. Nunca confie no header do token.
O algoritmo deve ser definido no servidor, hardcoded. Ponto.
2. Use libraries atualizadas.
Versões modernas do jsonwebtoken (Node), PyJWT (Python), golang-jwt (Go) e jjwt (Java) já rejeitam alg: none por padrão. Mas só se você atualizar.
3. Valide todos os claims relevantes.
Assinatura é o mínimo. Verifique iss (issuer), aud (audience), exp (expiration) e nbf (not before).
4. Rotacione chaves.
Se uma chave vazar, o blast radius é limitado se ela expira em dias, não anos.
5. Use JWKS (JSON Web Key Sets).
Em vez de uma chave estática, use um endpoint JWKS que permite rotação automática. Azure AD, Auth0, Keycloak, todos suportam.
6. Teste com tokens maliciosos.
Inclua no seu CI/CD testes que enviam tokens com alg: none, com algorithm confusion, com claims alterados. Bibliotecas como jwt_tool automatizam isso.
7. Defesa em profundidade.
JWT não é a única camada. Rate limiting, logging de acessos anômalos, least privilege nos claims. O Titan tinha dados de 17 bancos acessíveis por um token admin. Isso é permissão demais pra qualquer conta.
CVEs relacionados que apareceram em 2026
O caso do Titan não é isolado. Só em 2026 já tivemos pelo menos três CVEs de algorithm confusion em JWT:
- CVE-2026-22817: library popular de JWT em Ruby aceitava troca de RS256 para HS256 sem validação
- CVE-2026-27804: framework PHP permitia
alg: noneem variações de case (NoNe,NONE) - CVE-2026-23552: plugin de autenticação para WordPress aceitava JWK injection via header
Cada um desses bugs é variação do mesmo problema fundamental: confiar no que o token diz sobre si mesmo. E cada um foi descoberto por pesquisadores independentes, não por auditorias internas. Isso deveria preocupar qualquer empresa que depende de JWT para autenticação.
O que a comunidade está dizendo
O post do Faav no blog pessoal explodiu no Hacker News com quase 200 pontos e centenas de comentários. As reações variam entre admiração pelo pesquisador de 16 anos e indignação pelo bounty de US$ 5.000.
Alguns pontos que surgiram na discussão:
“Se a Microsoft paga US$ 5.000 por acesso a 17 trilhões de registros, quanto paga por um XSS no Bing? Uma bala Juquinha?”
“O kid tem 16 anos. Imagina o que ele vai achar quando tiver 25 e uma decade de experiência.”
“Isso mostra que até com Azure AD, VPN e infraestrutura enterprise, um erro básico de implementação derruba tudo.”
A real é que bug bounties de big techs são notoriamente baixos comparados ao impacto. A Microsoft paga entre US$ 500 e US$ 100.000 dependendo do programa, mas a mediana fica bem abaixo do que a falha “vale” em termos de risco.
JWT não é vilão (se você usar direito)
Depois de ler tudo isso, alguém sempre pergunta: “então JWT é inseguro? Deveria usar sessions?” A resposta é: JWT funciona perfeitamente, desde que você entenda o modelo de ameaça.
Sessions server-side têm suas vantagens (revogação instantânea, por exemplo), mas JWT brilha em arquiteturas distribuídas, microserviços e APIs stateless. O problema nunca foi o JWT em si, e sim implementações que ignoram verificações básicas.
Se você está começando um projeto novo em 2026, considere:
- APIs internas entre microserviços: JWT com RS256 + JWKS
- Autenticação de usuários: JWT de curta duração (15 min) + refresh token opaco em cookie HttpOnly
- APIs públicas: OAuth 2.0 com tokens opacos (deixe o identity provider lidar com a criptografia)
E pelo amor de tudo que é sagrado, valide a assinatura.
Fonte de inspiração: How I Could’ve Accessed 17 Trillion Microsoft Records por Faav













