Exploração de uma vulnerabilidade de falsificação crítica na CryptoAPI do Windows
Contribuições editoriais e adicionais por Tricia Howard
Resumo executivo
Recentemente, o Grupo de inteligência sobre a segurança da Akamai analisou uma vulnerabilidade crítica no Windows CryptoAPI que foi comunicada pela NSA (Agência de Segurança Nacional) e pelo NCSC (National Cyber Security Center) à Microsoft.
A vulnerabilidade, designada CVE-2022-34689, conseguiu uma pontuação CVSS de 7,5. Ela foi corrigido em agosto de 2022, porém, foi anunciada publicamente na Patch Tuesday de outubro de 2022.
De acordo com a Microsoft, a vulnerabilidade permite que um invasor se passe por uma entidade legítima.
A causa raiz do bug é a suposição de que a chave de índice do cache do certificado, que é baseada em MD5, é livre de colisões. Desde 2009, sabe-se que a resistência à colisão do MD5 está quebrada.
O fluxo de ataque é duplo. A primeira fase requer pegar um certificado legítimo, modificá-lo e servir a versão modificada à vítima. A segunda fase envolve a criação de um novo certificado cujo MD5 colide com o certificado legítimo modificado e o uso do novo certificado para falsificar a identidade do sujeito do certificado original.
Pesquisamos por toda parte aplicações que usam o CryptoAPI de uma maneira vulnerável a esse ataque de falsificação. Até agora, descobrimos que versões antigas do Chrome (v48 e anteriores) e as aplicações baseadas em Chromium podem ser exploradas. Acreditamos que existem mais alvos vulneráveis e que a nossa pesquisa ainda está em andamento.
Descobrimos que menos de 1% dos dispositivos visíveis nos data centers estão corrigidos, tornando o restante desprotegido da exploração dessa vulnerabilidade.
Nesta publicação do blog, fornecemos uma explicação detalhada sobre o fluxo de ataque potencial, as consequências e uma PoC (prova de conceito) que demonstra o ataque completo. Também fornecemos um OSQuery para detectar versões vulneráveis da biblioteca CryptoAPI.
Histórico
Há três meses, em nossa análise da Patch Tuesday de outubro de 2022, compartilhamos uma descrição básica de uma vulnerabilidade de falsificação crítica no Windows CryptoAPI: CVE-2022-34689. De acordo com a Microsoft, essa vulnerabilidade permite que um invasor "falsifique sua identidade e execute ações, como autenticação ou assinatura de código, como o certificado-alvo".
O CryptoAPI é a principal API no Windows para lidar com qualquer situação relacionada à criptografia. Em particular, ele lida com certificados, desde a leitura e análise até a validação em relação às autoridades de certificação (CAs) verificadas. Os navegadores também usam o CryptoAPI para validação de certificado TLS. Um processo que resulta no ícone de cadeado que todos aprendem a verificar.
No entanto, a verificação de certificados não é exclusiva para os navegadores e também é usada por outros clientes TLS, como a autenticação da Web do PowerShell, curl, wget, gerenciadores de FTP, EDRs, e muitas outras aplicações. Além disso, certificados de assinatura de código são verificados em executáveis e bibliotecas, e certificados de assinatura de driver são verificados ao carregar os drivers. Como se pode imaginar, uma vulnerabilidade no processo de verificação de certificados é muito lucrativa para os invasores, pois permite que eles disfarcem suas identidades e burlem as proteções de segurança críticas.
Esta não é a primeira vez que a Agência de Segurança Nacional divulgou uma vulnerabilidade no CryptoAPI. Em 2020, ela descobriu e divulgou o CurveBall (CVE-2020-0601). Explorar o CurveBall ou o CVE-2022-34689 resulta em falsificação de identidade. Porém, apesar do CurveBall ter afetado muitas aplicações, o CVE-2022-34689 tem mais pré-requisitos e, portanto, tem um escopo mais limitado de alvos vulneráveis.
Detalhes da vulnerabilidade
Para analisar a vulnerabilidade, primeiro tentamos localizar o código corrigido. Usamos o BinDiff, uma ferramenta popular de difusão binária, para observar as várias alterações de código no CryptoAPI. Em crypt32.dll, apenas uma função foi alterada: CreateChainContextFromPathGraph. Como parte dessa função, há uma comparação de dois certificados: um que é recebido como entrada e outro que reside no cache do certificados da aplicação de recebimento (mais sobre este cache adiante).
A inspeção das alterações revelou que as verificações memcmp foram adicionadas à função em dois locais (Figura 1).
Antes da correção, a função determinava se um certificado recebido já estava no cache (e, portanto, verificado) apenas com base na impressão digital MD5. Após a correção, a adição memcmp exige que os conteúdos reais dos dois certificados correspondam completamente.
Neste ponto, teorizamos que, se um invasor pudesse fornecer um certificado mal-intencionado cujo MD5 colidisse com um que já esteja no cache do certificado da vítima, ele poderia burlar a verificação vulnerável e fazer com que confiassem em seu certificado mal-intencionado (Figura 2).
Cache do certificados do CryptoAPI
O CryptoAPI pode usar um cache para certificados finais recebidos para melhorar o desempenho e a eficiência. Este mecanismo está desativado por padrão. Para ativá-lo, o desenvolvedor da aplicação precisa passar determinados parâmetros para CertGetCertificateChain, a função da API do Windows que eventualmente leva ao código vulnerável (Figura 3).
CertGetCertificateChain recebe vários parâmetros interessantes:
hChainEngine : um objeto configurável usado para controlar a maneira como os certificados são validados
pCertContext : o contexto do certificado de entrada, uma estrutura de dados criada usando o certificado de entrada pela função WintAPI CertCreateCertificateContext
dwFlags : os sinalizadores que especificam configurações adicionais
ppChainContext : o objeto de saída que contém (entre outros campos) o status de confiança, ou seja, o veredito da verificação da cadeia
Para ativar o mecanismo de cache para certificados finais, o desenvolvedor precisa definir o sinalizador CERT_CHAIN_CACHE_END_CERT em dwFlagsou criar um mecanismo de cadeia personalizado e definir o sinalizador CERT_CHAIN_CACHE_END_CERT em seu campo de dwFlags .
Para entender como o cache é implementado e usado, vejamos a função FindIssuerObject , que extrai o certificado do cache. Em termos gerais, a função se comporta da seguinte forma:
Ela calcula o índice do bucket do certificado de entrada no cache com base nos quatro bytes menos significativos da impressão digital MD5.
Se ele existir no cache, a função compara toda a impressão digital MD5 dos certificados em cache e de entrada.
Se as impressões digitais corresponderem (acerto do cache), o certificado de entrada será confiável e retornado. Deste ponto em diante, a aplicação usa os atributos do certificado de entrada (como a chave pública, o emissor etc.) e não o certificado em cache.
Se as impressões digitais não corresponderem (falha do cache), a função vai para o próximo certificado no bucket, compara sua impressão digital MD5 e repete o procedimento.
A Microsoft confia na validade dos certificados em cache e não executa nenhuma verificação de validade adicional depois que um certificado final é encontrado no cache. Isso, por si só, é uma suposição de trabalho razoável. No entanto, o código pressupõe que dois certificados são idênticos se suas impressões digitais MD5 corresponderem. Essa é uma suposição incorreta que pode ser explorada e que foi a origem do patch.
Para apoiar nossa hipótese, escrevemos uma pequena aplicação que usa CertGetCertificateChain e depuramos o fluxo de verificação do certificado em crypt32.dll. Usando o WinDbg, simulamos um cenário em que a impressão digital MD5 de nosso próprio certificado (autoassinado) corresponde a um certificado legítimo que já estava no cache. Como mostrado na Figura 4, nosso certificado criado era confiável.
Ao ignorar apenas uma verificação, poderíamos fazer com que o Windows acreditasse que nosso certificado mal-intencionado era legítimo.
Como a vulnerabilidade pode ser explorada
Construir um certificado com uma impressão digital MD5 que corresponda exatamente a um determinado valor MD5 é chamado de ataque preimagem, e é computacionalmente inviável até hoje. No entanto, é possível gerar com eficiência dois certificados com dois prefixos escolhidos que terão as mesmas impressões digitais MD5. Esse tipo de ataque é chamado de colisão de prefixo escolhido.
Ao escolher esse caminho, precisaremos fornecer de alguma forma dois certificados para a aplicação vítima. Um certificado será assinado, verificado e armazenado em cache corretamente (que chamaremos de "certificado-alvo modificado"). Ele será gerado de uma maneira que facilite um ataque de colisão de prefixo escolhido. O segundo certificado (que chamaremos de "certificado mal-intencionado") conterá a identidade falsificada. Ele colidirá com a impressão digital MD5 do primeiro certificado (Figura 5).
Falsificação de certificados por meio de colisões MD5
As colisões MD5 nos levam 14 anos de volta no tempo, quando a Beyoncé lançou "Single Ladies", o Obama foi eleito primeiro presidente e as colisões MD5 foram usadas pela primeira vez para falsificar certificados SSL (Secure Sockets Layer). Há uma grande diferença entre esse primeiro ataque e o cenário com o qual lidamos hoje. O cenário anterior atacou assinatura MD5, mas na vulnerabilidade atual estamos lidando com impressões digitais MD5. Vamos entender a diferença.
De acordo com a RFC 5280, seção 4.1, um certificado é uma sequência ASN.1 com duas seções (Figura 6):
tbsCertificate (ou certificado "a ser assinado"): esta é a parte que contém todos os detalhes relacionados à identidade (assunto, chave pública, número de série, EKU etc.). Esta é a parte que está assinada.
signatureAlgorithm e signatureValue : esses campos compreendem a assinatura do TBS.
Certificate ::= SEQUENCE {
tbsCertificate TBSCertificate,
signatureAlgorithm AlgorithmIdentifier,
signatureValue BIT STRING }
Fig. 6: A sequência ASN.1 que define os certificados
Uma assinatura de certificado é uma estrutura incorporada ao certificado, que assina apenas a parte TBS do certificado. Por outro lado, uma impressão digital de certificado é um hash do certificado todo (incluindo a assinatura).
Portanto, se pudéssemos modificar qualquer parte do certificado que esteja fora do TBS sem invalidá-lo, modificaríamos a impressão digital sem alterar a assinatura. Se o analisador analisar a assinatura corretamente e o TBS estiver inalterado, o certificado ainda será considerado válido e assinado, mesmo que a estrutura completa do certificado tenha sido alterada (Figura 7).
Colisões MD5 de prefixo escolhido: uma breve visão geral
Digamos que você tenha duas strings arbitrárias, A e B, do mesmo comprimento. Assim, duas strings, C e D, podem ser calculadas com eficiência, de modo que
MD5(A || C) = MD5(B || D) |
, onde || indica concatenação de string.
Não é apenas o resultado de MD5 final que será o mesmo, mas também o estado interno de MD5 após anexar C ou D. Portanto, se você usar qualquer sufixo E, teria
MD5(A || C || E) = MD5(B || D || E) |
(contanto que o mesmo sufixo E seja adicionado em ambos os lados).
Espaço para blocos de colisão
Como invasores, precisaremos gerar um certificado que pareça válido, mas que também contenha espaço para blocos de colisão (as strings C e D na explicação acima). Isso nos permitirá criar nosso certificado mal-intencionado (com a mesma impressão digital MD5), que serviremos em seguida.
De acordo com a RFC 5280, seção 4.1.1.2, a estrutura de signatureAlgorithm é
AlgorithmIdentifier ::= SEQUENCE {
algorithm OBJECT IDENTIFIER,
parameters ANY DEFINED BY algorithm OPTIONAL }
o campo de parâmetros para o algoritmo RSA (com base na RFC 3279) "SHALL be the ASN.1 type NULL". Em outras palavras: a RSA não usa parâmetros de assinatura, em vez disso usa NULL como valor. É possível que o CryptoAPI ignore esse campo para assinaturas RSA?
Para inserir bytes de espaço reservado neste campo (como preparação para blocos de colisão), tentamos alterar o tipo ASN.1 de NULL para BIT STRING. Testar isso em relação ao CryptoAPI e ao OpenSSL, funciona , o certificado ainda é considerado válido. A assinatura não sofreu alteração ou quebra porque não modificamos o TBS. (É claro que a impressão digital MD5 muda.)
Colisões de impressão digital MD5 do certificado
Agora, podemos juntar as informações e fornecer uma receita para manipular um certificado já assinado existente para colidir com a impressão digital MD5 de um certificado mal-intencionado.
Escolha um certificado final assinado pela RSA legítimo, como o certificado TLS de um website (nosso "certificado-alvo").
Modifique quaisquer campos interessantes (assunto, extensões, EKU, chave pública etc.) na parte TBS do certificado para criar o certificado mal-intencionado. Nota: Não mexemos na assinatura, portanto, o certificado mal-intencionado é assinado incorretamente. Modificar a chave pública é importante nesse caso. Isso permite que o invasor assine como o certificado mal-intencionado.
Modifique os campos de parâmetros do campo signatureAlgorithm de ambos os certificados para que haja espaço suficiente para colocar blocos de colisão MD5 (C e D na explicação acima) começando no mesmo deslocamento dos dois certificados.
Trunque ambos os certificados na posição em que os blocos de colisão MD5 serão colocados.
Realize um cálculo de colisão MD5 de prefixo escolhido e copie o resultado nos certificados.
Concatene o valor de assinatura do certificado legítimo (sufixo E na explicação acima) aos dois certificados incompletos.
Um exemplo real
Com nossa compreensão sobre colisões MD5, agora podemos tentar explorar este CVE com um alvo real. Entre as diversas aplicação que verificamos, conseguimos encontrar um alvo vulnerável: Chrome v48. (Esta aplicação é vulnerável pelo simples fato de passar o sinalizador CERT_CHAIN_CACHE_END_CERT para CertGetCertificateChain.) Outras aplicações baseadas em Chromium dessa época também são vulneráveis a este CVE.
Para explorarmos essa vulnerabilidade, primeiro precisamos criar dois certificados que tenham a mesma impressão digital MD5. Fizemos isso usando o HashClash (Figura 8).
Em seguida, tivemos que encontrar uma maneira de injetar nosso certificado-alvo modificado no cache do Chrome. Isso foi difícil de fazer, pois é impossível servir um certificado sem conhecer sua chave privada.
No TLS 1.2, há duas etapas de verificação relevantes:
A mensagem de Server Key Exchange (Troca de chave de servidor) : esta mensagem só pode ser construída por alguém que conheça a chave privada do certificado, já que é assinada por ele
A mensagem de Server Handshake Finished (Handshake de servidor finalizado) : esta mensagem inclui uma verificação anti-adulteração de todas as mensagens de handshake anteriores
(O TLS 1.3 é diferente e não nos concentramos nele.)
Lembre-se, na primeira fase do ataque, queremos injetar o certificado modificado no cache do certificado final do Chrome.
Usando um script Python como um proxy, executamos um ataque de machine-in-the-middle (MITM):
Nosso servidor MITM mal-intencionado conversa com o servidor real e reflete as primeiras mensagens de handshake TLS com a vítima.
Na mensagem de Server Certificate (Certificado de servidor), nosso servidor MITM mal-intencionado modifica a mensagem do servidor real e substitui o certificado-alvo real pelo certificado modificado.
A mensagem de Server Key Exchange pode ser refletida sem alterações.
Nosso servidor mal-intencionado não pode simplesmente encaminhar a mensagem de Server Handshake Finished, porque o handshake foi de fato adulterado. Assim, encerramos a conexão.
Para verificar a mensagem de Server Key Exchange, o Chrome deve carregar o certificado modificado com o CryptoAPI e, portanto, ele seria injetado no cache. O Chrome não trata a conexão interrompida como um problema de segurança TLS, poderia ser apenas um problema de rede aleatório. O Chrome tenta se reconectar, e desta vez, em vez de refletir as mensagens do website real, o servidor mal-intencionado vai servir um website com o certificado mal-intencionado. O Chrome ignorará o processo de verificação completo porque acredita que o certificado já está no cache. O resultado será uma visita contínua a um website aparentemente legítimo da Microsoft (Figuras 9 e 10). O fluxo de exploração completo pode ser visto em nosso vídeo.
Detecção
Fornecemos um OSQuery para detectar versões vulneráveis de crypt32.dll, a biblioteca vulnerável (Figura 11). Os clientes do Akamai Guardicore Segmentation podem usar o recurso Insight juntamente com essa consulta para pesquisar ativos vulneráveis.
Lembre-se de que, para que um ativo seja vulnerável, ele precisa ter uma versão não corrigida de crypt32.dll e executar uma aplicação vulnerável. (Até hoje, descobrimos que apenas o Chrome v48 é vulnerável.)
WITH product_version AS (
WITH os_minor AS (
WITH os_major AS (
SELECT substr(product_version, 0, instr(product_version, ".")) as os_major, substr(product_version, instr(product_version, ".")+1) as no_os_major_substr
FROM file
WHERE path = "c:\windows\system32\crypt32.dll"
)
SELECT substr(no_os_major_substr, instr(no_os_major_substr, ".")+1) as no_os_minor_substr, substr(no_os_major_substr, 0, instr(no_os_major_substr, ".")) as os_minor, os_major
FROM os_major
)
SELECT
CAST(substr(no_os_minor_substr, instr(no_os_minor_substr, ".")+1) AS INTEGER) AS product_minor,
CAST(substr(no_os_minor_substr, 0, instr(no_os_minor_substr, ".")) AS INTEGER) AS product_major,
CAST(os_minor AS INTEGER) AS os_minor,
CAST(os_major AS INTEGER) AS os_major
FROM os_minor
)
SELECT
CASE
WHEN os_major = 6 AND os_minor = 3 THEN "not supported"
WHEN (
(product_major = 20348 AND product_minor >= 887)
OR
(product_major = 17763 AND product_minor >= 3287)
OR
(product_major = 14393 AND product_minor >= 5291)
OR
(product_major >= 19041 AND product_minor >= 1889)
)
THEN
"patched"
ELSE
"not patched"
END is_patched
FROM product_version
Conclusão
Os certificados desempenham um papel importante na verificação de identidades online, tornando essa vulnerabilidade lucrativa para os invasores. Mas, embora tenha sido marcada como crítica, a vulnerabilidade recebeu apenas uma pontuação CVSS de 7,5. Acreditamos que isso se deve ao escopo limitado de aplicações e componentes vulneráveis do Windows nos quais os pré-requisitos da vulnerabilidade são atendidos.
Dito isso, ainda existem muitos códigos que usam essa API e podem estar expostos a essa vulnerabilidade, garantindo um patch mesmo para versões descontinuadas do Windows, como o Windows 7.
Recomendamos que você corrija seus servidores e pontos de extremidade do Windows com o patch de segurança mais recente lançado pela Microsoft. Para desenvolvedores, outra opção para mitigar essa vulnerabilidade é usar outros WinAPIs para verificar novamente a validade de um certificado antes de usá-lo, como CertVerifyCertificateChainPolicy. Lembre-se de que as aplicações que não usam o mecanismo de cache do certificado final não são vulneráveis.
Nosso código PoC está disponível em nosso repositório do GitHub. Você também pode se manter atualizado com todas as publicações do Grupo de inteligência sobre a segurança da Akamai por meio de nossa conta do Twitter.