Vulnerabilidades comuns em JWT: alg:none e outros ataques

1. Introdução ao ecossistema de ameaças JWT

1.1. Por que JWTs são alvos frequentes de ataques

JSON Web Tokens (JWT) são amplamente utilizados para autenticação e autorização em APIs modernas. Sua popularidade os torna alvos constantes de ataques. A natureza stateless dos JWTs — onde o servidor não mantém sessão — significa que um token comprometido concede acesso imediato sem verificação adicional. Além disso, a confiança depositada na assinatura digital cria uma falsa sensação de segurança se a implementação for falha.

1.2. Visão geral das principais classes de vulnerabilidade

As vulnerabilidades em JWT podem ser agrupadas em:
- Manipulação do cabeçalho: alteração do algoritmo (alg, kid, jku)
- Confusão de algoritmos: troca entre simétrico e assimétrico
- Falhas de validação: omissão da verificação de assinatura
- Ataques de replay: reutilização de tokens expirados ou roubados

1.3. O papel da confiança no cabeçalho e na assinatura

O cabeçalho JWT contém metadados críticos como o algoritmo de assinatura. Se o servidor confiar cegamente nesses metadados sem validação explícita, um atacante pode manipular o token para contornar a segurança. A assinatura, por sua vez, só é eficaz se for verificada corretamente com o algoritmo e a chave apropriados.

2. Ataque alg:none — Assinatura desativada

2.1. Como o ataque funciona: manipulando o cabeçalho alg

O ataque alg:none explora bibliotecas que aceitam o valor none no campo alg do cabeçalho, indicando que nenhuma assinatura é necessária. Um token malicioso seria:

Header: {"alg":"none","typ":"JWT"}
Payload: {"sub":"admin","iat":1700000000}
Signature: (vazio)

Token completo (Base64 URL-encoded):

eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJhZG1pbiIsImlhdCI6MTcwMDAwMDAwMH0.

2.2. Implementação insegura: bibliotecas que aceitam none por padrão

Versões antigas de bibliotecas como jsonwebtoken (Node.js) e pyjwt (Python) aceitavam alg:none por padrão. Exemplo de código vulnerável:

// Node.js - código vulnerável
const jwt = require('jsonwebtoken');
const token = req.headers.authorization.split(' ')[1];
const decoded = jwt.verify(token, 'minha-chave-secreta'); // Aceita 'none' sem verificar

2.3. Mitigação: validação explícita do algoritmo e rejeição de none

A mitigação exige forçar o algoritmo esperado:

// Node.js - código seguro
const jwt = require('jsonwebtoken');
const token = req.headers.authorization.split(' ')[1];
const decoded = jwt.verify(token, 'minha-chave-secreta', { algorithms: ['HS256'] });
// Rejeita automaticamente 'alg:none' e algoritmos não listados

3. Confusão de algoritmos (Algorithm Confusion)

3.1. Troca de algoritmo: de RS256 (assimétrico) para HS256 (simétrico)

Neste ataque, o servidor espera RS256 (par de chaves pública/privada), mas o atacante altera o cabeçalho para HS256 (chave secreta simétrica). Se a chave pública estiver disponível (como em JWKS endpoints públicos), o atacante a utiliza como segredo HMAC para assinar tokens.

3.2. Exploração da chave pública como segredo HMAC

Token malicioso forjado:

Header: {"alg":"HS256","typ":"JWT"}
Payload: {"sub":"admin","role":"superuser"}
Signature: HMAC-SHA256(chave_publica_conhecida, header.payload)

O servidor, ao receber o token, usa a chave pública como segredo HMAC e valida a assinatura com sucesso.

3.3. Mitigação: fixar o algoritmo esperado e validar o tipo de chave

Solução: sempre especificar o algoritmo no momento da verificação:

// Python - código seguro com PyJWT
import jwt
public_key = open('public.pem').read()
try:
    decoded = jwt.decode(
        token,
        public_key,
        algorithms=['RS256'],  # Força RS256, rejeita HS256
        options={'verify_exp': True}
    )
except jwt.InvalidAlgorithmError:
    print("Algoritmo inválido detectado!")

4. Ataques de injeção no cabeçalho JWT

4.1. Manipulação de claims via kid (Key ID) — path traversal

O campo kid é usado para selecionar a chave de verificação. Se a implementação busca um arquivo com base no valor de kid, um atacante pode fazer path traversal:

Header: {"alg":"HS256","kid":"../../etc/passwd"}
Payload: {"sub":"admin"}

Se o servidor lê o arquivo indicado por kid como chave, pode expor dados sensíveis ou usar um conteúdo controlado pelo atacante.

4.2. Injeção de parâmetros como jku e jwk para forjar chaves

Campos como jku (JWKS URL) e jwk (JSON Web Key) permitem que o token especifique sua própria chave de verificação. Se o servidor não validar a origem desses parâmetros, o atacante pode hospedar uma chave falsa e assinar tokens arbitrários.

Header: {
  "alg":"RS256",
  "jku":"https://atacante.com/fake-jwks.json"
}

4.3. Mitigação: sanitização de entradas e validação de fontes confiáveis

// Node.js - validação de kid
const allowedKids = ['key-01', 'key-02', 'key-03'];
const decoded = jwt.decode(token, { complete: true });
if (!allowedKids.includes(decoded.header.kid)) {
    throw new Error('kid não autorizado');
}
// Para jku: restringir a URLs conhecidas e usar HTTPS com validação de certificado

5. Falhas na validação de assinatura

5.1. Omisso de verificação em bibliotecas desatualizadas

Algumas bibliotecas oferecem métodos como decode() (sem verificação) e verify() (com verificação). Desenvolvedores desatentos usam decode() para validar tokens:

// Código vulnerável - sem verificação de assinatura
const payload = jwt.decode(token); // Apenas decodifica, não verifica

5.2. Tratamento incorreto de JWTs malformados ou Base64 inválido

JWTs malformados podem causar exceções não tratadas, levando a bypass de autenticação:

// Token com padding Base64 inválido
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiJ9==.assinatura_invalida

5.3. Mitigação: uso de bibliotecas atualizadas e testes de integridade

// Sempre usar verify() e tratar exceções
try {
    const payload = jwt.verify(token, secret, { algorithms: ['HS256'] });
} catch (err) {
    if (err.name === 'JsonWebTokenError') {
        console.error('Token inválido:', err.message);
        return res.status(401).send('Token inválido');
    }
}

6. Ataques de replay e expiração inadequada

6.1. Reuso de tokens roubados sem verificação de jti (JWT ID)

Sem um identificador único (jti), tokens roubados podem ser reutilizados indefinidamente:

Payload: {"sub":"usuario123","exp":1700100000}  // Sem jti

6.2. Tokens com exp ausente ou muito longo

Tokens sem expiração (exp) ou com expiração excessivamente longa (anos) aumentam a janela de ataque:

Payload: {"sub":"admin","exp":9999999999}  // Expira no ano 2286

6.3. Mitigação: implementação de nonces, blacklist e rotação de tokens

// Implementação de jti e verificação de expiração
const payload = {
    sub: 'usuario123',
    jti: uuidv4(),  // Identificador único
    exp: Math.floor(Date.now() / 1000) + 3600,  // 1 hora
    iat: Math.floor(Date.now() / 1000)
};

// Blacklist de tokens revogados
const blacklistedTokens = new Set();
if (blacklistedTokens.has(token)) {
    return res.status(401).send('Token revogado');
}

7. Boas práticas defensivas para desenvolvedores

7.1. Checklist de validação: algoritmo, assinatura, claims e tempo

1. Algoritmo: forçar explicitamente (ex: ['HS256', 'RS256'])
2. Assinatura: sempre verificar com chave apropriada
3. Claims obrigatórias: validar sub, exp, iat, nbf
4. Tempo: verificar exp, rejeitar tokens expirados
5. jti: exigir identificador único e manter blacklist
6. kid: validar contra lista de chaves permitidas
7. jku/jwk: desabilitar ou restringir a fontes confiáveis

7.2. Uso de bibliotecas seguras e configuração explícita

// Exemplo de configuração segura com jsonwebtoken (Node.js)
const jwt = require('jsonwebtoken');
const options = {
    algorithms: ['HS256'],
    issuer: 'https://meu-dominio.com',
    audience: 'https://api.meu-dominio.com',
    clockTolerance: 30  // Tolerância de 30 segundos para diferenças de relógio
};
const payload = jwt.verify(token, secret, options);

7.3. Monitoramento e logging de tentativas de manipulação de JWT

// Logging de tentativas suspeitas
function validateToken(token) {
    try {
        const decoded = jwt.verify(token, secret, { algorithms: ['HS256'] });
        return decoded;
    } catch (err) {
        console.warn(`Tentativa de manipulação de JWT: ${err.message}`);
        console.warn(`Token: ${token.substring(0, 50)}...`);
        // Alertar equipe de segurança
        securityAlert(err, token);
        return null;
    }
}

Referências