Code FightersSem categoria

JSON WEB Tokens: Dicas e ataques para uma implementação segura

O JWT (JSON WEB Tokens) é um padrão aberto, documentado pela RFC-7519 que define como transmitir e armazenar objetos JSON de forma simples, compacta e segura entre diferentes aplicações. É muito utilizado para validar serviços em Web Services, pois os dados contidos em um token podem ser validados a qualquer momento visto que é assinado digitalmente. Nesse artigo mostraremos alguns exemplos de seu uso e analisaremos erros comuns em sua implementação, considerando os principais tipos de ataques e forneceremos algumas recomendações sobre como evitá-los.

Antes de entrarmos em detalhes sobre Dicas e ataques para uma implementação segura em JSON WEB Tokens, é importante entendermos que o JWT é uma estrutura JSON. As especificações sobre como criptografar ou assinar digitalmente um JWT é conhecida como Javascript Object Signing and Encryption (JOSE). Ele é composto pelos seguintes componentes:

JWT: JSON Web Token, o token propriamente dito;
JWE: JSON Web Encryption, a criptografia para assinatura do token;
JWA: JSON Web Algorithms, sobre os algoritmos para assinatura do token;
JWK: JSON Web Keys, as chaves para assinatura;
JWS: JSON Web Signature, sobre a assinatura do token.

Estrutura

Sua estrutura é composta por 3 partes: Header, Payload e Signature; Sendo elas separadas por um ponto (.) e cada parte é individualmente encodada em Base64Url.

Header

O header especifica o algoritmo que foi utilizado para gerar a assinatura do JWT (JSON WEB Tokens) e opcionalmente outras propriedades do token, os principais tipos de algoritmos criptográficos suportados são:

• HMAC
• RSA

Payload 

O Payload é um objeto JSON com as Claims (reivindicações) da entidade tratada, neste trecho geralmente são armazenados dados do usuário autenticado quando usado em processos de autenticação.Essas claims podem ser de 3 tipos, como: Reserved claims, Public claims e Private claims.

Reserved claims: Atributos não obrigatórios (mas recomendados) que são usados na validação do token pelos protocolos de segurança das APIs, como por exemplo:

sub (subject) = Entidade a 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;
aud (audience) = Destinatário do token, representa a aplicação que usara-lo.

Geralmente os atributos mais utilizados são: sub, iss e exp.

Public claims: Estas podem ser definidas à vontade por aqueles que usam JWTs. Mas para evitar colisões, devem ser definidas no IANA JSON Web Token Registry.

Private claims: Atributos personalizados definidos especialmente para compartilhar informações entre aplicações específicas que concordam em utilizá-los.

Assinatura

A assinatura é a junção dos hashes gerados a partir do Header e Payload usando base64UrlEncode, com uma chave secreta ou certificado RSA.

Essa assinatura é utilizada para garantir a integridade do token, no caso, se ele foi modificado e se realmente foi gerado por você.

Isso previne ataques do tipo man-in-the-middle, onde um atacante poderia interceptar a requisição e modificar seu conteúdo com informações falsas. Caso o payload seja alterado, o hash final não será válido, pois não foi assinado com a chave secreta e a informação será rejeitada. 

O resultado final é um token com três seções (header, payload, signature) separadas por “.”

Alguns dos principais erros de implementação

. Armazenar informações sensíveis no payload

Ao analisar um JWT (JSON WEB Tokens), podemos esquecer que as informações contidas nele devem ser consideradas como públicas, dado que qualquer um que obtiver acesso a um token pode facilmente verificar as informações contidas no payload. Para verificar isso, pegue qualquer JWT token e coloque no debugger do site jwt.io e, mesmo que não seja possível validá-lo, será possível identificar e analisar todas as informações contidas nele.

Lembre-se de colocar sempre apenas o que for necessário e nunca colocar informações como senhas, CPF, ou outras informações sensíveis, que além de aumentar o tamanho do token, podem acabar gerando uma falha de segurança.

. Não configurar um tempo de expiração

Desde a primeira implementação é extremamente importante definir um tempo de expiração no JWT. Como todos os serviços que usam o token para validar se o usuário está autenticado confiam nas informações contidas nele, caso este token não tenha um tempo de expiração, qualquer um que obtiver ele, em certas circunstâncias estará autenticado para sempre. Quando você define um tempo de expiração, diminui-se a chance de que um token válido possa ser reutilizado e permitir que uma pessoa mal intencionada se passe por outro usuário nas chamadas para os seus serviços.

. Utilizar um tempo de expiração muito alto

Também não é aconselhado colocar um tempo de expiração muito alto, dado que a ideia é exatamente evitar que tokens válidos circulem entre pessoas mal intencionadas. Vale sempre pensar nas necessidades do seu sistema, dado que muitas vezes se faz desnecessário um tempo de uma semana para a expiração. Um tempo que geralmente é recomendado como padrão é 15 minutos.

Como alternativa à “deslogar” o usuário da aplicação, pode-se usar refresh tokens para gerar novos tokens de autenticação JWT. Os refresh tokens são enviados para o serviço de autenticação quando o seu JWT expirou ou está para expirar e é validado junto ao banco de dados, dando a possibilidade de desabilitar um refresh token. Vale a pena estudar mais a fundo técnicas como esta para garantir uma boa experiência aos usuários.

. Utilizar um algoritmo fraco para a assinatura

O algoritmo padrão usado para assinar um JWT é o HS256, no qual utiliza-se uma chave, como se fosse uma senha, tanto para gerar os tokens quanto para validá-los (ou seja é um algoritmo simétrico). Isso funciona bem, porém, como diversos serviços podem conter esta chave apenas para validar o JWT (imagine que você tem o seu serviço de autenticação e um de gerenciamento de pedidos, os dois teriam a mesma chave em suas configurações) as chances que ela vaze podem ser maiores, e se alguém obtiver acesso a esta chave, consegue facilmente gerar tokens de autenticação válidos, consequentemente gerando muitos riscos.

Uma alternativa é usar um algoritmo assimétrico, ou seja, um algoritmo no qual uma chave privada é utilizada para gerar os tokens e uma chave pública é usada para validá-lo. Com algoritmos como os RSA ou ECDSA, somente seu serviço de autenticação terá a chave necessária para gerar os tokens, enquanto os demais serviços só terão a chave para validá-los, assim minimizando os riscos de segurança.

. Não utilizar as demais claims reservadas do JWT

Não é só o tempo de expiração que pode ser configurado no JWT, alguns outros atributos (claims) também são reservados e possuem suporte das bibliotecas podendo ser utilizados para aumentar ainda mais a segurança dos seus tokens. Por exemplo, o claim “aud” que permite especificar quais serviços devem aceitar o token gerado, ou o “iss” que define qual serviço gerou o token. Para conhecer mais a fundo claims e suas funcionalidades é recomendado ler o RFC do JWT.

Principais Técnicas de Exploração JSON WEB Tokens

. Algoritmo “None”

O algoritmo none é uma adição curiosa ao JWT, originalmente presente no Header para expressar que o token não possui uma assinatura, passou a ser utilizado para a exploração de uma das vulnerabilidades mais conhecidas do JWT.

Infelizmente, algumas implementações e bibliotecas vulneráveis trataram os tokens definidos com o algoritmo “none” como um token válido e com uma assinatura verificada. O resultado? Qualquer pessoa pode criar seus próprios tokens “assinados” com o payload que desejar, permitindo acesso arbitrário a contas e a escalação de privilégios em alguns sistemas.

Para testar esta vulnerabilidade é necessário modificar o header do token, substituindo o conteúdo do atributo de algoritmo para “none”, como demonstrado abaixo:

Headers = {
  “alg”: “none”
}

Então, basta realizar o encode novamente em base64Url de cada parte do token e concatenar as mesmas, com exceção da parte de assinatura, que deverá ser omitida, porém o último ponto deve estar presente, resultando em algo como o exemplo a seguir:

eyJhbGciOiJub25lIn0.eyJuYW1lIjoiYWRtaW4ifQ.

Para evitar esta fragilidade, certifique-se de utilizar uma biblioteca JWT que não esteja exposta a essa vulnerabilidade.

Por último, durante a validação do token, solicite explicitamente que o algoritmo esperado seja usado.

. Troca de Algoritmo

Sabemos que os JSON WEB tokens podem ser assinados por algoritmos simétricos e assimétricos, podendo-se consultar o algoritmo utilizado no “Header”; Nos casos de algoritmos assimétricos como o do RS256, se faz necessário a comparação com uma chave pública, visto que o mesmo já foi assinado utilizando de uma chave privada, diferentemente dos algoritmos assimétricos, como o HS256, que se utiliza apenas de uma chave secreta. 

Tendo isso em mente, algumas implementações vulneráveis que utilizam um algoritmo assimétrico podem ser exploradas ao realizar a substituição para um algoritmo simétrico, podendo assinar novos tokens com a chave pública, que por ser pública muitas vezes pode ser obtida pelo atacante, isso fará com que ela seja comparada com ela mesma na validação visto que tokens como o HS256 utilizam apenas de uma chave.

. Informar uma assinatura inválida

Devido a erros de implementação, é possível que a assinatura do token nunca seja verificada ao chegar na aplicação. Dessa forma, um invasor pode simplesmente ignorar o mecanismo de segurança fornecendo uma assinatura inválida.

. Força Bruta em chaves HMAC fracas

Caso a aplicação esteja usando uma chave HMAC fraca (algs HS256/HS384/HS512), que por ser simétrica utiliza de apenas uma chave, existe a possibilidade de utilizar força bruta na chave para a identificação da mesma e podendo assim ser utilizada para assinar um novo token.

O cracking de HMAC é, portanto, totalmente offline e pode ser executado em grande escala por um invasor, em outras palavras, o token pode ser testado para senhas válidas sem ter que enviá-lo de volta para a aplicação verificá-lo.

Existem inúmeras ferramentas para crackear JWT, algumas delas são a jwt_tool e a jwt-cracker.

Caso você possua uma GPU de qualidade para quebrar hashes, a mesma pode ser utilizada em conjunto com a ferramente hashcat, realizando um teste com um número incrivelmente alto de tarefas paralelas. O comando abaixo demonstra a utilização do hashcat com a utilização de um dicionário de possíveis chaves secretas:

. Manipulação de Kid

Como descrito na RFC o kid (Key identifier) é uma das “claims” opcionais que o desenvolvedor pode incluir no header, é comumente utilizado quando você tem várias chaves para assinar os tokens e precisa procurar a certa para verificar a assinatura.

Por ser controlado pelo usuário, um atacante pode manipular seu valor a fim de explorar vulnerabilidades como:

1 – Directory traversalComo o “kid” é frequentemente usado para retornar um arquivo de chave do sistema, se não for sanitizado antes do uso, pode levar a um ataque de Directory Traversal. Quando for esse o caso, o invasor poderá especificar qualquer arquivo do sistema como a chave a ser usada para verificar o token.

No exemplo acima, o invasor irá forçar a aplicação a usar um arquivo disponível publicamente como a chave e assinar um token HMAC usando esse arquivo.

2 – SQL Injection

Outro ataque possível de ser realizado no “kid” é o ataque de SQL Injection, em que o atacante pode injetar códigos SQL para retornar qualquer valor que quiser. Por exemplo, a injeção abaixo fará com que a aplicação retorne a string “secret_key” (uma vez que a string chamada “test” não existe no banco de dados). O token será então verificado com a string “secret_key” como a chave secreta.

O que podemos concluir sobre segurança em JSON WEB Tokens?

O JSON Web Token é uma maneira segura de transferir dados entre duas partes, contanto que seja corretamente implementada e utilizada, tendo seus prós e contras, cada tipo de implementação deve levar em conta os benefícios e riscos de seu uso.

Um dos cenários que a utilização de JWT passa a não ser recomendada é para o  gerenciamento de sessões de usuários, visto seu tamanho, impossibilidade de ser invalidado e dentre outros riscos que esta implementação pode acarretar, porém ainda assim JWTs podem ser utilizados em conjunto com tokens de sessão, cada um com seu próprio propósito.

Para concluir existem diversas boas práticas para o uso de JWT, dentre as principais são:

  • Restringir algoritmos aceitos para o único que deseja utilizar;
  • Verificar todos os tokens antes de processar os dados do payload;
  • Sempre sanitizar dados que podem ser manipulados pelo usuário;
  • Restringir URLs de quaisquer certificados JWKS / X509;
  • Use o processo de assinatura mais forte para o qual você possa dispor de tempo de CPU;
  • Use chaves assimétricas se os tokens forem usados ​​em mais de um servidor;
  • Utilize chaves fortes;
  • Procure utilizar sempre a versão mais recente da biblioteca JWT que você optar, e realize uma busca por possíveis CVE’s para verificar se a versão utilizada não tem vulnerabilidades publicamente conhecidas.

Referências

About author

Articles

Graduado em Segurança da Informação com cerca de 4 anos de experiência, tem atuado como Pentester na Conviso com foco em análises de aplicações web. Já publicou artigos e CVE's nesta área, e é apaixonado por achar vulnerabilidades e entender a fundo aplicações.
Related posts
Code Fighters

Dicas e truques para Pentest em API

Com o objetivo de possibilitar a comunicação entre diferentes plataformas, a utilização de APIs…
Read more
Code FightersProduto

A fantástica palavra de GraphQL

Tendo o GraphQL como estrela da publicação, precisamos contextualizar um pouco sobre o tema.
Read more
Code FightersMobileSegurança de Aplicação

Pré-testes Android: Conceitos básicos e uma introdução ao tema

Fazer um bom laboratório para testes de segurança em aplicações android pode ser uma tarefa…
Read more

Deixe um comentário