Existem muitas bibliotecas disponíveis que suportam JWT, e o próprio padrão possui “rico suporte para mecanismos criptográficos”. Tudo isso significa que o JWT é realmente seguro? Vamos ver.

Texto de pesquisa traduzido do original por MICHAŁ SAJDAK.


O JWT (JSON Web Token) é um mecanismo usado com frequência nas APIs REST e pode ser encontrado em padrões populares, como o OpenID Connect, mas também o encontramos algumas vezes usando o OAuth2. É usado tanto em grandes empresas quanto em organizações menores.

Sumário

Introdução ao JWT

A RFC 7519 diz:

O JSON Web Token (JWT) é um meio compacto e seguro de URL de representar claims a serem transferidas entre duas partes. As claims em um JWT são codificadas como um objeto JSON usado como payload de uma estrutura JSON Web Signature (JWS) ou como texto sem formatação de uma estrutura JSON Web Encryption (JWE) (…)

Simplificando, o JWT é uma sequência de caracteres no formato JSON (https://www.json.org/) codificado na estrutura JWS (JSON Web Signature) ou JWE (JSON Web Encryption). Além disso, cada uma dessas opções deve ser serializada de maneira compacta (uma das duas serializações no JWS e JWE). Na maioria das vezes você pode ver o JWS, e é essa estrutura que é popularmente chamada JWT. Por sua vez, “claim” é geralmente um simples par de “chave”: “valor”

Um exemplo de um JWT é mostrado abaixo:

enter image description here

Você pode ver que temos aqui três longas sequências de caracteres separadas por um ponto. A estrutura do todo é a seguinte:

cabeçalho . payload . assinatura

Os três elementos acima são codificados com o algoritmo BASE64URL (que se parece muito com o BASE64, onde o sinal de mais (+) na saída é substituído por hífen (-), a barra (/) é substituída pelo undescore (_) e existe nenhum preenchimento BASE64 padrão, que geralmente consiste em sinais de igual (=)).

Após decodificar a sequência acima (que pode ser feita, por exemplo, por https://jwt.io/), podemos ver um cabeçalho e payload totalmente legíveis.

Se você quiser saber mais sobre o JWT, os seguintes recursos podem ser úteis:

  1. Introdução.
  2. Mais detalhes técnicos.
  3. JOSE Project (Javascript Object Signing and Encryption) – exceto para JWT / JWT, você pode ler mais sobre JWK (JSON Web Key), JWA (JSON Web Algorithms), uso diferente dos mecanismos no OAuth2 / OpenID Connect; acontece que o JWS pode ter mais de uma assinatura, os JWTs podem ser aninhados… Um bom resumo disponível aqui.

Outros examples de JWT e seus primeiros problemas de segurança

Vamos para o tópico principal - segurança JWT. Às vezes, os JWTs podem ser usados ​​como “chaves de API”. No exemplo de uma chave desse tipo, pode ser assim:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOiIxNDE2OTI5MDYxIiwianRpIjoiODAyMDU3ZmY5YjViNGViN2ZiYjg4NTZiNmViMmNjNWIiLCJzY29wZXMiOnsidXNlcnMiOnsiYWN0aW9ucyI6WyJyZWFkIiwiY3JlYXRlIl19LCJ1c2Vyc19hcHBfbWV0YWRhdGEiOnsiYWN0aW9ucyI6WyJyZWFkIiwiY3JlYXRlIl19fX0.gll8YBKPLq6ZLkCPLoghaBZG_ojFLREyLQYx0l2BG3E

Após decodificar com a função de decodificação BASE64URL, temos as seguintes seções:

Cabeçalho: {
“alg “: “HS256 “,
“typ “: “JWT “
}

Payload: {
“iat”: “1416929061”,
“jti”: “802057ff9b5b4eb7fbb8856b6eb2cc5b”,
“scopes”: {
“users”: {
“actions”: [
“read”,
“create”
]
},
“users_app_metadata”: {
“actions”: [
“read”,
“create”
]
}
}
}

Assinatura:

Apenas um conteúdo binário.

Como você pode ver, com essa “chave da API” (seu conteúdo principal está no payload), podemos implementar a autenticação (tenho o privilégio de me comunicar com a API) e a autorização (no payload acima, você pode ver exemplos de ações que pode ser realizada pelo proprietário da chave).

Do ponto de vista da segurança, existem pelo menos dois problemas em potencial, para começar.

O primeiro é a falta de confidencialidade - conseguimos decodificar o payload (e o cabeçalho) facilmente. Às vezes, não é um problema (ou é mesmo uma vantagem), mas quando exigimos confidencialidade dos dados enviados no token, há uma maneira melhor de fazê-lo: JWE (JSON Web Encryption).

O segundo problema é uma possibilidade potencial de inserir outra ação pelo usuário - por exemplo, excluir - e, assim, ignorar a autorização. Nesse caso, a solução está usando assinaturas em tokens (observe que no exemplo acima, vimos uma “assinatura”). O algoritmo HS256 indicado no cabeçalho é um HMAC-SHA256 padrão - um mecanismo que garante a integridade de toda a mensagem (graças a ele, o usuário não pode alterar o payload; ou então - ele pode, mas a API que aceita o token detectar adulteração durante a verificação da assinatura). Para configurar o HS256, você precisa gerar uma chave (string) e colocá-la na configuração da API.

Por uma questão de clareza da mensagem, é possível imaginar a assinatura de uma maneira muito simples:

SHA-256(cabeçalho || payload || chave) – onde || concatenação

Aviso: falando formalmente, é um pouco mais complicado: usamos o algoritmo HMAC-SHA256 ao qual atribuímos uma chave e uma mensagem igual ao resultado:

BASE64URL(UTF8(Cabeçalho protegido JWS )) || ‘.’ || BASE64URL(JWS Payload)

enter image description here

Se alguém alterar o payload, ele não poderá gerar uma nova assinatura (porque ele precisa de uma chave para ela, que está apenas na configuração da API).

Em resumo, o JWT parece muito mais flexível do que as chaves da API - você pode facilmente transferir quaisquer dados, garantir sua integridade e, se necessário, manter a confidencialidade. Além disso, todas as informações (exceto a chave secreta) podem estar no próprio token. No entanto, não há rosa sem espinhos. 🌹

Espinhos do JWT 🌹

Primeiro espinho: é complexo!

Um dos principais problemas é que o JWT é um mecanismo muito complexo. JWT / JWS / JWE / JWK, uma infinidade de algoritmos criptográficos, duas maneiras diferentes de codificação (serialização), compactação, possibilidade de mais de uma assinatura, criptografia para vários destinatários - esses são apenas alguns exemplos. Todas as especificações relacionadas à JWT têm mais de 300 páginas! A complexidade certamente não é amiga da segurança.

Segundo espinho: none!

Como já mencionado, muitas vezes presume-se que o JWT “adequado” seja um JWT com uma assinatura (JWS), embora, de acordo com a especificação formal do JWT, uma assinatura não seja obrigatória. O documento RFC indica o chamado “JWT não seguro” (ou seja, sem assinatura). Como é o JWT não assinado? No cabeçalho, temos:

{
“alg “: “none “,
“typ “: “JWT “
}

No payload, as coisas de sempre. A seção de assinatura está vazia (ou pode ser ignorada pelo processador de tokens). Curiosamente, o algoritmo “none” é um dos dois algoritmos que DEVEM ser implementados de acordo com a RFC:

Of the signature and MAC algorithms specified in JSON Web Algorithms [JWA], only HMAC SHA-256 (“HS256”) and “none” MUST be implemented by conforming JWT implementations.

O que isso dá ao atacante? Bem, ele pode obter um JWT (com uma assinatura), alterá-lo (por exemplo, adicionar nova permissão) e colocá-lo no cabeçalho {“alg”: ”none”}. Em seguida, envie tudo para a API com ou sem uma assinatura. O servidor deve aceitar esse token? Teoricamente sim, mas isso destruiria toda a idéia de assinaturas JWT. No entanto, essas situações realmente ocorreram (talvez ainda ocorram?) Em muitas bibliotecas que implementam JWTs: https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/, https://www.cvedetails.com/cve/CVE-2018-1000531/.

enter image description here

A propósito, vale a pena mencionar outro problema: o que acontece se tivermos um algoritmo de assinatura no cabeçalho (por exemplo, HS256 ou HS512), mas removermos toda a seção de assinatura do token?

Como você pode ver aqui, algumas vezes esse token será verificado corretamente! Você pode dizer um pouco ironicamente - esta é uma versão moderna do problema “nenhum” (veja a figura abaixo):

(…) Uma vulnerabilidade de validação de entrada no JWTDecoder.decode que pode resultar em um JWT decodificado e, portanto, validado implicitamente, mesmo que não possua uma assinatura válida.

enter image description here

Às vezes, há outro problema - se o invasor não souber criar uma assinatura adequada, talvez ele seja inserido em uma mensagem de erro? Inacreditável? Vamos ver esta vulnerabilidade:

Critical Security Fix Required: You disclose the correct signature with each SignatureVerificationException.

Portanto, se alguém alterou o payload e enviou um token para o servidor, o servidor nos informou educadamente sobre isso, fornecendo um token correto que corresponda ao nosso payload:

enter image description here

Para fazê-lo funcionar, o servidor precisou ser configurado para exibir exceções ao usuário, mas essa não é uma configuração muito rara (embora incorreta). Problema semelhante foi descoberto em outra biblioteca:

All versions of Auth0-WCF-Service-JWT NuGet package lower than 1.0.4 include sensitive information about the expected JWT signature in an error message emitted when JWT signature validation fails.

Terceiro espinho: quebrando a chave HMAC

Vamos voltar à assinatura, ou seja, o algoritmo HS256 (HMAC-SHA256). Como é calculada a assinatura? Como vimos um pouco antes:

enter image description here

Como recuperar a chave do JWT (e ter a capacidade de criar uma assinatura válida para qualquer JWT)? A abordagem padrão utiliza um token gerado pela API e executa ataques clássicos de força bruta / dicionário / híbrido. Uma iteração requer o cálculo de dois hashes do SHA256 (é assim que o HMAC-SHA256 funciona) e também existem ferramentas que automatizam toda a operação, como o hashcat que implementa a quebra da chave JWT usando GPU (s). Com algumas GPUs rápidas, você pode atingir velocidades de mais de um bilhão de verificações por segundo. Além disso, toda a operação pode ser feita offline, sem qualquer interação com a API (basta obter um token arbitrário com uma assinatura). Um exemplo de uma sessão de quebra do JWT se parece com isso:

Session……….: hashcat  
Status………..: Running  
Hash.Type……..: JWT (JSON Web Token)  
Hash.Target……: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMj…  
Guess.Mask…….: ?1?2?2?2?2?2?2 [7]  
Guess.Charset….: -1 ?l?d?u, -2 ?l?d, -3 ?l?d*!$@_, -4 Undefined  
Guess.Queue……: 7/15 (46.67%)  
Speed.Dev.#1…..: 198.0 MH/s (9.68ms) @ Accel:32 Loops:8 Thr:512 Vec:1  
Recovered……..: 0/1 (0.00%) Digests, 0/1 (0.00%) Salts  
Progress………: 17964072960/134960504832 (13.31%)  
Rejected………: 0/17964072960 (0.00%)  
Restore.Point….: 0/1679616 (0.00%)  
Candidates.#1….: U7veran -> a2vbj14

Em uma única placa Nvidia GTX 1070, obtemos uma velocidade de quebra de cerca de 200 milhões de verificações por segundo. Se o hashcat conseguir quebrar a chave, você obterá o resultado abaixo (onde secrety é a chave que você está procurando):

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.W7TG0x5Ed1GLN6eOPhTNHErL8TfwBFRSQnqleolhXG0:secrety

Uma chave do mesmo tamanho da saída de hash (por exemplo, 256 bits para “HS256”) ou maior DEVE ser usada com esse algoritmo. (Este requisito baseia-se na Seção 5.3.4 (Efeito de segurança da chave HMAC) do NIST SP 800-117 [NIST.800-107], que afirma que a força de segurança efetiva é o mínimo da força de segurança da chave e duas vezes o tamanho do valor do hash interno.).

A propósito, vale enfatizar mais uma vez que a quebra da chave JWT pode ser feita completamente offline.

Quarto espinho: muitos cozinheiros estragam o caldo do JWT

Muitos problemas de segurança da JWT surgem como resultado da implementação do padrão (realmente complexo). Vamos ver um exemplo.

Como podemos escolher o algoritmo de assinatura no JWS? Até agora, mencionei o HMAC (e apenas com a função SHA256), mas não é a única opção. Uma visão geral das várias variantes de assinatura pode ser encontrada aqui: https://auth0.com/blog/json-web-token-signing-algorithms-overview/.

Uma opção comum é usar um algoritmo assimétrico - RSA. Nesse caso, veremos no cabeçalho “alg”: “RS512” ou “alg”: “RS256”.

Apenas um pequeno lembrete: uma chave privada RSA é usada para assinaturas e uma chave pública associada a ela pode verificar assinaturas. Portanto, neste caso, em vez de uma chave simétrica (como no algoritmo HS256), geramos um par de chaves RSA.

A propósito, se você vir o RS512 ou o RS256 pela primeira vez, poderá pensar em um requisito para usar chaves RSA de 512 ou 256 bits? Essas chaves podem ser quebradas a um custo e tempo mínimos (consulte: https://eprint.iacr.org/2015/1000.pdf ou https://www.theregister.co.uk/2010/01/07/rsa_768_broken/). Atualmente, mesmo uma chave RSA de 1024 bits não é considerada segura. Felizmente, isso apenas aponta para a função SHA específica usada em conexão com o RSA. Por exemplo, RS512 significa função RSA mais SHA512. Mas e a chave RSA? O comprimento é definido pela pessoa que o gera, o que é outro problema em potencial (além disso, em diferentes tutoriais online, você pode encontrar comandos específicos usando o OpenSSL e gerando chaves de 1024 bits). Também é importante lembrar que o uso do RSA terá um impacto na eficiência de todo o sistema (verificação de assinatura); portanto, nesse caso, escolhemos uma variante mais lenta que o HS256.

Voltando ao ponto, com o algoritmo RSA, temos pelo menos mais um problema de segurança interessante. Como escrevi anteriormente, uma chave pública é usada para verificação de assinatura; portanto, ela geralmente é definida na configuração da API como a chave de verificação. Aqui, é importante notar que, para o HMAC, temos apenas uma chave simétrica usada para assinatura e verificação.

Como um invasor pode forjar um token JWT?

  1. Ele obtém uma chave pública (o próprio nome indica que ela pode estar disponível publicamente). Às vezes, é transmitido dentro do próprio JWT.

  2. Envia o token (com payload alterado) com o algoritmo HS256 definido no cabeçalho (ou seja, HMAC, não RSA) e assina o token com a chave pública RSA. Sim, não há erro aqui - usamos a chave pública RSA (que fornecemos na forma de uma string) como uma chave simétrica para o HMAC.

  3. O servidor recebe o token, verifica qual algoritmo foi usado para a assinatura (HS256). A chave de verificação foi definida na configuração como a chave pública RSA, então…:

  4. A assinatura é validada (porque exatamente a mesma chave de verificação foi usada para criar a assinatura e o invasor configurou o algoritmo de assinatura como HS256).

enter image description here

Qual foi o problema aqui? O fato de ser possível fornecer o algoritmo de assinatura pelo usuário, embora tenhamos a intenção de verificar a assinatura do token usando apenas RSA. Portanto, forçamos apenas um algoritmo de assinatura selecionado (não oferecemos a possibilidade de alterá-lo alterando o token) ou vamos fornecer métodos de verificação separados (e chaves!) Para cada algoritmo de assinatura que suportamos. Aqui está um exemplo real desta vulnerabilidade:

If the server is expecting RSA but is sent HMAC-SHA with RSA’s public key, the server will think the public key is actually an HMAC private key. This could be used to forge any data an attacker wants.

Quinto espinho: fornecendo sua própria chave

Às vezes, você pode fornecer sua própria chave no token, que será usado pela API para verificá-lo! Parece inacreditável, certo? Consulte os detalhes da vulnerabilidade do CVE-2018-0114 na biblioteca node-jose: https://tools.cisco.com/security/center/viewAlert.x?alertId=56326.

The vulnerability is due to node-jose following the JSON Web Signature (JWS) standard for JSON Web Tokens (JWTs). This standard specifies that a JSON Web Key (JWK) representing a public key can be embedded within the header of a JWS. This public key is then trusted for verification. An attacker could exploit this by forging valid JWS objects by removing the original signature, adding a new public key to the header, and then signing the object using the (attacker-owned) private key associated with the public key embedded in that JWS header.

O problema não é novo, anteriormente (2016), uma vulnerabilidade semelhante foi detectada na biblioteca Go-jose (https://mailarchive.ietf.org/arch/msgif/jose/gQU_C_QURVuwmy-Q2qyVwPLQlcg):

RFC 7515, https://tools.ietf.org/html/rfc7515#section-4.1.3 “jwk” (JSON Web Key) Header Parameter allows the signature to include the public key that corresponds to the key used to digitally sign the JWS. This is a really dangerous option.

Um outro exemplo está aqui (perl-Crypt-JWT):

ADDED in 0.23
1 – use “jwk” header value for validating JWS signature if neither “key” nor “kid_keys” specified, BEWARE: DANGEROUS, UNSECURE!!!
0 (default) – ignore “jwk” header value when validating JWS signature

Sexto espinho: a criptografia JWT funciona mesmo?

E a criptografia (JSON Web Encryption)? Aqui, você pode escolher entre vários algoritmos (criptografia da própria mensagem ou criptografia da chave simétrica usada para criptografar a mensagem). Novamente, há algumas pesquisas interessantes, ou seja, várias implementações do JWE tornaram possível ao invasor recuperar a chave privada: https://blogs.adobe.com/security/2017/03/critical-vulnerability-uncovered-in -json-encryption.html. Mais especificamente, houve um problema na implementação do algoritmo ECDH-ES (que, a propósito, tem o status “Recomendado +” no documento RFC: https://tools.ietf.org/html/rfc7518).

Talvez a vulnerabilidade anterior tenha sido apenas um acidente? Vamos ver o AES no modo GCM: https://rwc.iacr.org/2017/Slides/nguyen.quan.pdf. Os pesquisadores do Google estão escrevendo sobre isso no contexto da JWT e resumem: o GCM é frágil, mas suas implementações raramente foram verificadas.

Também podemos escolher o algoritmo RSA com preenchimento PKCS1v1.5. O que há de errado com isso? Os problemas são conhecidos desde 1998: ftp://ftp.rsa.com/pub/pdfs/bulletn7.pdf. E algumas pessoas resumem assim:

PKCS#1v1.5 is awesome – if you’re teaching a class on how to attack cryptographic protocols.

Mais detalhes sobre possíveis algoritmos e possíveis problemas podem ser encontrados aqui:

Sempre estaremos fadados ao fracasso usando o JWE? Claro que não, mas vale a pena verificar se usamos um algoritmo de criptografia adequadamente seguro (e sua implementação segura).

Sétimo espinho: decodificação / verificação - isso importa? 😉

Agora podemos nos sentir um pouco sobrecarregados pela multiplicidade de opções. Afinal, queremos apenas “decodificar” o token no lado da API e usar as informações nele contidas. Lembre-se, no entanto, que “decodificar” nem sempre significa o mesmo que “verificar” - supostamente óbvio, mas bibliotecas diferentes podem fornecer funções diferentes para decodificar e / ou verificar tokens. Um exemplo desse tipo de pergunta ou dúvida pode ser encontrado aqui.

Em resumo, se eu usar a função decode (), só posso decodificar o payload (ou o cabeçalho) de BASE64URL sem nenhuma verificação. A verificação pode ser uma função separada, embora também possa ser incorporada em um decode (). Às vezes, são os usuários que solicitam essa opção - no caso citado abaixo - alguém pede para sobrecarregar o método decode () para que ele também possa aceitar o próprio token (sem a chave):

Eu vi um problema na estrutura para obter o payload de um JWT. Para obter um payload de um JWT sem validação, não precisamos de uma chave / segredo. Portanto, no arquivo jwt/src/JWT/JwtDecoder.cs, perdemos uma sobrecarga para o método Decode que precisa apenas de um token.

Se o programador, usando a biblioteca, agora usar a decodificação de chamada mais simples (token), a assinatura não será verificada.

Além disso, na discussão citada acima, há uma pergunta sobre a possibilidade de verificar a assinatura no lado do cliente, que, como já sabemos no caso do HS256, pode ser complicada (o cliente deve ter uma chave secreta, que pode ser extraída por um atacante). Infelizmente, muitas vezes o uso do JWT se resume ao seguinte: vamos escolher os primeiros algoritmos que vemos e copiar os trechos de código dos tutoriais. Funciona? Sim, então qual é o problema?

Oitavo espinho: capturar qualquer token = assumir o acesso à API?

Uma das vantagens frequentemente apontadas do JWT é a implementação da autenticação (ou autorização - depende do contexto em que o todo será usado) sem a necessidade de executar uma consulta no banco de dados. Além disso, podemos fazer isso em paralelo em vários servidores independentes (APIs). Afinal, apenas o conteúdo do token é suficiente para tomar uma decisão aqui. Ele também tem uma desvantagem - e se a chave de assinatura disponível em muitos servidores vazar? Obviamente, será possível gerar tokens assinados corretamente, aceitos por todas as máquinas que usam a chave apropriada para verificação. O que um invasor pode ganhar com isso? Por exemplo, acesso não autorizado a funções da API ou outras contas de usuário.

Também temos um problema potencialmente diferente aqui - e se um token gerado puder ser usado em muitos contextos diferentes? Por exemplo, geramos um token válido com o conteúdo de {“login” = “manager”}. E em uma função da API, é possível editar alguns dados, mas, ao mesmo tempo, esquecemos que uma função completamente diferente, recebendo o mesmo token, oferece a possibilidade de acesso total!

Nesse caso, é possível usar determinados parâmetros definidos pela própria especificação: iss (issuer) e aud (audience). Graças a eles, os tokens podem ser aceitos apenas por nossos destinatários específicos.

Por exemplo:

{
“iss ” = “my_api “,
“login ” = “manager “,
“aud ” = “store_api “
}

Podemos apontar outro problema - algumas palavras-chave reservadas no JWT (Registered Claim Names) - por exemplo iss / aud / iat / jti - pode ser colocado ao lado de qualquer elemento definido pelo usuário do JWT - por exemplo faça login a partir do nosso exemplo. Isso está causando outra confusão, que vou apontar no próximo problema.

Nono espinho: replay JWT

E se um token específico deve ser usado apenas uma vez? Vamos imaginar um cenário em que um usuário grave um token gerado para executar o método DELETE em nossa API. Então, por exemplo depois de um ano - quando ele teoricamente não tem mais as permissões apropriadas - ele tenta usá-lo novamente (o chamado replay attack).

A maneira de fazer isso é usar as seguintes claims: jti e exp. Jti (JWT ID) é um identificador de token, que deve ser exclusivo, e exp é uma definição da data de validade de um token. A combinação de ambos os campos nos dará uma validade adequadamente curta do token e sua singularidade.

Vale ressaltar, no entanto, se temos uma implementação correta dessas metades. Agora observe o bug no qual o valor exp não foi levado em consideração (https://github.com/jwt-dotnet/jwt/issues/134).

JWT not throwing ExpiredTokenException in .NetCore

Os desenvolvedores de bibliotecas usaram a declaração de expiração (que não está na especificação da JWT) para executar verificações de expiração; o bug foi corrigido após o relatório.

Décimo espinho: timing attacks na assinatura

Se a assinatura do JWS for verificada byte por byte com a assinatura correta (gerada pela parte que aceita o JWS) e se a verificação terminar no primeiro byte inconsistente, podemos ser suscetíveis a timing attacks.

Observe que, nesse caso, quanto mais bytes correspondentes tivermos, mais comparações serão necessárias e, portanto, maior o tempo necessário para a resposta.

É possível observar os tempos de resposta gerando assinaturas sucessivas, começando no primeiro byte da assinatura, passando para o segundo, etc. A descrição exata desse ataque pode ser vista aqui: https://hackernoon.com/can-timing-attack-be-a-practical-security-threat-on-jwt-signature-ba3c8340dea9.9.

Segundo o relatório, com uma grande quantidade de tráfego gerado (até 55 mil solicitações por segundo), a assinatura de qualquer mensagem pode ser obtida em 22 horas (condições laboratoriais). Com menos tráfego, é claro, precisaremos de mais tempo (vários dias), mas o efeito pode ser chocante (podemos gerar qualquer JWT e preparar uma assinatura que será verificada como correta).

O ataque é realmente possível no cenário real? Como você pode imaginar, as mudanças no tempo de resposta são mínimas, mas você pode tentar medi-las. Nesse ponto, também vale lembrar um texto um pouco mais antigo sobre ataques de tempo: https://www.cs.rice.edu/~dwallach/pub/crosby-timing2009.pdf. As seguintes medidas foram alcançadas:

Our work analyses the limits of attacks based on accurately measuring network response times and jitter over a local network and across the Internet. We present the design of filters to significantly reduce the effects of jitter, allowing an attacker to measure events with 15-100µs accuracy across the Internet and as good as 100ns over a local network.

Por outro lado, um exemplo de declaração de um tipo específico de vulnerabilidade pode ser encontrado, por exemplo, aqui ou aqui.

Décimo-primeiro espinho: grande quantidade de bibliotecas disponíveis

Como um dos primeiros problemas relacionados ao JWT, menciono várias opções disponíveis e vários algoritmos. Há também um grande número de implementações de JWT, muitas vezes incompletas, escritas, para dizer o mínimo, no joelho. Não é fácil de seguir e, às vezes, problemático em termos de padrão de segurança, requer o uso de algoritmos criptográficos complexos por pessoas que geralmente não possuem um conhecimento profundo de criptografia. Garbage in - garbage out: é difícil esperar bibliotecas super seguras aqui.

Outros exemplos de bugs em bibliotecas:

Ao mesmo tempo, vale mencionar problemas de segurança relacionados às bibliotecas usadas em geral. Vale a pena verificar se eu uso uma biblioteca com vulnerabilidades públicas conhecidas. Ou talvez minhas bibliotecas estejam usando dependências vulneráveis? Será que não é necessário eu desenvolver um mecanismo de monitoramento da biblioteca usada caso alguém localize uma vulnerabilidade significativa no futuro?

Alternativa ao JWT?

Olhando para muitos dos problemas de segurança que apontei no JWT, pode-se perguntar se temos uma alternativa comprovada. No momento, a resposta é não. Uma das novas idéias é o PASETO:

Paseto is everything you love about JOSE (JWT, JWE, JWS) without any of the many design deficits that plague the JOSE standards.

Simplificando, o PASETO deve ser uma versão segura do JWT. Realmente cumprirá as promessas? No momento, é realmente difícil dizer - é um projeto muito jovem e ainda em fase de desenvolvimento. Mais informações podem ser encontradas aqui e aqui. As motivações por trás do deste projeto podemos ler aqui.

O JWT pode ser seguro?

As opiniões na comunidade de segurança são divididas. Algumas pessoas desencorajam categoricamente o uso do JWT, outras apontam para implementações mal preparadas, enquanto outras descrevem os mecanismos do JWT exatamente como são, deixando a decisão para o usuário. Além disso, o JWT é um mecanismo muito popular, para o qual não há alternativa popular e padronizada, que comprovou adicionalmente sua segurança.

Os problemas de segurança mostrados antes podem ser colocados em três categorias:

  1. Problemas com a própria especificação JWT (por exemplo, nenhum algoritmo).
  2. Erros de implementação de biblioteca, incluindo erros de implementação de algoritmo criptográfico (é provavelmente o grupo mais numeroso).
  3. Uso incorreto da biblioteca.

A maioria dos problemas comuns é apresentada abaixo.

Vamos resumir tudo com conselhos práticos que podem aumentar a segurança de uso da JWT. Algumas recomendações podem conter abreviaturas de pensamento, explicadas anteriormente no texto e adicionalmente abordadas nos materiais de origem vinculados.

enter image description here

O que devo fazer?

Pra começar

  1. Entenda o que você deseja usar: considere se você precisa do JWS ou JWE, escolha os algoritmos apropriados, entenda sua finalidade (pelo menos em um nível geral - por exemplo, HMAC, chave pública, chave privada). Descubra o que exatamente oferece a biblioteca JWT que você escolheu. Talvez exista um mecanismo mais simples e pronto que você possa usar?

Chaves

  1. Use chaves simétricas / assimétricas adequadamente complexas.

  2. Tenha um cenário preparado em caso de comprometimento (divulgação) de uma das chaves.

  3. Mantenha as chaves em um local seguro (por exemplo, não as codifique permanentemente no código-fonte).

  4. Idealmente, não permita definir o algoritmo de assinatura arbitrário pela parte remetente (é melhor forçar um algoritmo (s) de assinatura específico (s) no lado do servidor).

Assinatura

  1. Verifique se sua implementação não aceita o algoritmo de nenhuma assinatura.

  2. Verifique se sua implementação não aceita uma assinatura vazia (ou seja, a assinatura não está marcada).

  3. Se você usa o JWE, verifique se está usando algoritmos seguros e se está usando a implementação segura desses algoritmos.

  4. Distinga entre Verifique () e Decodifique (). Em outras palavras, verifique se você tem certeza de que está verificando a assinatura.

Regras gerais

  1. Verifique se o token gerado em um local não pode ser usado em outro para obter acesso não autorizado.

  2. Verifique se o modo de depuração está desativado e se não pode ser ativado com um truque simples (por exemplo,? Debug = true).

  3. Evite enviar tokens em URLs (isso pode vazar dados confidenciais - por exemplo, esses tokens são gravados nos logs do servidor da web).

Payload

  1. Verifique se você está colocando informações confidenciais no payload do JWS (não recomendado).

  2. Verifique se você está protegido contra um ataque de repetição (reenviando um token).

  3. Certifique-se de que os tokens tenham um período de validade suficientemente curto (por exemplo, usando a claim “exp”).

  4. Verifique se a “exp” está realmente marcada. Pense se você precisa invalidar um token específico (s) (o padrão não fornece ferramentas para isso, mas existem várias maneiras de implementar esse tipo de mecanismo).

Bibliotecas

  1. Leia a documentação da biblioteca com atenção.

  2. Verifique as vulnerabilidades na biblioteca que você usa (por exemplo cvedetails.com ou no site do projeto).

  3. Verifique se seus projetos anteriores não usam uma biblioteca vulnerável; verifique se você está monitorando novos erros na biblioteca (eles podem aparecer, por exemplo, após um mês de implementação).

  4. Acompanhe novas vulnerabilidades em bibliotecas que suportam JWT. Talvez, no futuro, alguém encontre uma vulnerabilidade em outro projeto, que pode ser explorada da mesma forma na biblioteca que você está usando.

Referências

  1. JSON Web Token Best Current Practices: https://tools.ietf.org/html/draft-ietf-oauth-jwt-bcp-04

  2. JWT Handbook: https://auth0.com/resources/ebooks/jwt-handbook

  3. Discussão sobre vulnerabildiade no JWT: https://lobste.rs/s/r4lv76/jwt_is_bad_standard_everyone_should_avoid

  4. JWT Cheat Sheet for Java (OWASP). https://www.owasp.org/index.php/JSON_Web_Token_(JWT)_Cheat_Sheet_for_Java

  5. Algumas de idéias de uso mais seguro do JWT: https://dev.to/neilmadden/7-best-practices-for-json-web-tokens

  6. Alguns arguntos contra o uso JWT para sessões: http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/

  7. Comparação de JWTs com IDs de sessão e outros recursos de segurança relevantes: http://by.jtl.xyz/2016/06/the-unspoken-vulnerability-of-jwts.html