Tomer Peled, Yoni Rozenshein & Tricia Howard
Executive summary
- Akamai Security Research recently analyzed a critical vulnerability in Windows CryptoAPI that was disclosed by the National Security Agency (NSA) and the National Cyber Security Center (NCSC) to Microsoft.
- The vulnerability, assigned CVE-2022-34689, has a CVSS score of 7.5. It was patched in August 2022, but was publicly announced in the October 2022 Patch Tuesday.
- According to Microsoft, the vulnerability allows an attacker to masquerade as a legitimate entity.
- The root cause of the bug is the assumption that the certificate cache index key, which is MD5-based, is collision-free. Since 2009, MD5’s collision resistance is known to be broken.
- The attack flow is twofold. The first phase requires taking a legitimate certificate, modifying it, and serving the modified version to the victim. The second phase involves creating a new certificate whose MD5 collides with the modified legitimate certificate, and using the new certificate to spoof the identity of the original certificate’s subject.
- We have searched for applications in the wild that use CryptoAPI in a way that is vulnerable to this spoofing attack. So far, we found that old versions of Chrome (v48 and earlier) and Chromium-based applications can be exploited. We believe there are more vulnerable targets in the wild and our research is still ongoing.
- We found that fewer than 1% of visible devices in data centers are patched, rendering the rest unprotected from exploitation of this vulnerability.
- In this blog post, we provide a detailed explanation of the potential attack flow and consequences, as well as a proof of concept (PoC) that demonstrates the complete attack. We also provide an OSQuery for detecting vulnerable versions of the CryptoAPI library.
Background
Three months ago, in our October 2022 Patch Tuesday analysis, we shared a basic description of a critical spoofing vulnerability in Windows CryptoAPI — CVE-2022-34689. According to Microsoft, this vulnerability allows an attacker to “spoof their identity and perform actions such as authentication or code signing as the targeted certificate.”
CryptoAPI is the de facto API in Windows for handling anything related to cryptography. In particular, it handles certificates — from reading and parsing them to validating them against verified certificate authorities (CAs). Browsers also use CryptoAPI for TLS certificate validation — a process that results in the lock icon everyone is taught to check.
However, certificate verification is not unique for browsers, and is used by other TLS clients as well, such as PowerShell web authentication, curl, wget, FTP managers, EDRs, and many other applications. In addition, code-signing certificates are verified on executables and libraries, and driver-signing certificates are verified when loading drivers. As one can imagine, a vulnerability in the verification process of certificates is very lucrative for attackers, as it allows them to mask their identity and bypass critical security protections.
This is not the first time the National Security Agency disclosed a vulnerability in CryptoAPI. In 2020, they found and disclosed CurveBall (CVE-2020-0601). Exploiting either CurveBall or CVE-2022-34689 results in identity spoofing; but while CurveBall affected many applications, CVE-2022-34689 has more prerequisites and thus has a more limited scope of vulnerable targets.
Vulnerability details
To analyze the vulnerability, we first tried to locate the patched code. We used BinDiff — a popular binary-diffing tool — to observe the various code changes to CryptoAPI. In crypt32.dll, only one function has changed: CreateChainContextFromPathGraph. As part of this function, there is a comparison of two certificates: one that is received as input and another that resides in the receiving application’s certificate cache (more on this cache later).
Inspection of the changes revealed that memcmp checks were added to the function in two locations (Figure 1).
Fig. 1: Code added to CreateChainContextFromPathGraph in the patch (highlighted)
Before the patch, the function determined whether a received certificate is already in the cache (and therefore verified) only based on its MD5 thumbprint. After the patch, the memcmp addition requires that the actual contents of the two certificates match completely.
At this point, we theorized that if an attacker could serve a malicious certificate whose MD5 collides with one that is already in the victim’s certificate cache, they would be able to bypass the vulnerable check and have their malicious certificate trusted (Figure 2).
Fig. 2: High-level attack flow
CryptoAPI’s certificate cache
CryptoAPI can use a cache for received end certificates to improve performance and efficiency. This mechanism is disabled by default. To enable it, the application developer needs to pass certain parameters to CertGetCertificateChain, the Windows API function that eventually leads to the vulnerable code (Figure 3).
Fig. 3: The CertGetCertificateChain function declaration
CertGetCertificateChain receives several interesting parameters:
- hChainEngine — a configurable object used to control the way certificates are validated
- pCertContext — the input certificate’s context, a data structure built using the input certificate by the WinAPI function CertCreateCertificateContext
- dwFlags — the flags that specify further configuration
- ppChainContext — the output object that contains (among other fields) the trust status; namely, the verification verdict of the chain
To enable the caching mechanism for end certificates, the developer needs to either set the flag CERT_CHAIN_CACHE_END_CERT in dwFlags, or create a custom chain engine and set the flag CERT_CHAIN_CACHE_END_CERT in its dwFlags field.
To understand how the cache is implemented and used, let’s take a look at the function FindIssuerObject that pulls the certificate from the cache. Broadly speaking, the function behaves as follows:
- It calculates the input certificate’s bucket index in the cache based on the four least-significant bytes of its MD5 thumbprint.
- If it exists in cache, the function compares the entire MD5 thumbprint of the cached certificate and the input certificate.
- If the thumbprints match (cache hit), the input certificate is trusted and returned. From now on, the application uses the input certificate attributes (such as the public key, issuer, etc.) and not the cached certificate.
- If the thumbprints do not match (cache miss), it goes to the next certificate in the bucket, compares its MD5 thumbprint, and repeats.
Microsoft inherently trusts the validity of cached certificates, and doesn’t perform any additional validity checks after an end certificate is found in the cache. This, by itself, is a reasonable working assumption. However, the code makes a further assumption that two certificates are identical if their MD5 thumbprints match. This is an incorrect assumption that can be exploited, and was the genesis of the patch.
To support our hypothesis, we wrote a small application that uses CertGetCertificateChain and debugged the certificate verification flow in crypt32.dll. Using WinDbg, we simulated a scenario in which the MD5 thumbprint of our own (self-signed) certificate matches a legitimate certificate that was already in the cache. As shown in Figure 4, our crafted certificate was trusted.
Fig. 4: Logs showing that the cached certificate and our own crafted certificate were both trusted by CryptoAPI
By only bypassing one check we could make Windows believe our malicious certificate was a legitimate one.
How the vulnerability can be exploited
Constructing a certificate with an MD5 thumbprint that exactly matches a given MD5 value is called a preimage attack, and this is computationally infeasible even today. However, it is possible to efficiently generate two certificates with two chosen prefixes that will end up having the same MD5 thumbprints; this type of attack is called a chosen prefix collision.
Choosing this path, we will need to somehow provide two certificates to the victim application. One certificate will be correctly signed, verified, and cached (we’ll refer to it as the “modified target certificate”). It will be generated in a way that facilitates a chosen prefix collision attack. The second certificate (which we will call the “malicious certificate”) will contain the spoofed identity. It will collide with the MD5 thumbprint of the first certificate (Figure 5).
Fig. 5: The malicious certificate’s MD5 thumbprint will collide with that of the modified target certificate
Certificate spoofing via MD5 collisions
MD5 collisions take us back about 14 years, to a time when Beyoncé released “Single Ladies,” Obama was first elected president, and MD5 collisions were first used to spoof SSL certificates. There is one major difference between that first attack and the scenario we deal with today: the previous scenario attacked MD5 signatures, but in the current vulnerability we are dealing with MD5 thumbprints. Let’s understand the difference.
According to RFC 5280, section 4.1, a certificate is an ASN.1 sequence with two sections (Figure 6):
- tbsCertificate (or “to-be-signed” certificate) — This is the part that contains all identity-related details (subject, public key, serial number, EKU, etc.). This is the part that is signed.
- signatureAlgorithm and signatureValue — These fields comprise the signature of the TBS.
Certificate ::= SEQUENCE {
tbsCertificate TBSCertificate,
signatureAlgorithm AlgorithmIdentifier,
signatureValue BIT STRING }
Fig. 6: The ASN.1 sequence that defines certificates
A certificate signature is therefore a structure embedded inside the certificate, which only signs the TBS part of the certificate. On the other hand, a certificate thumbprint is a hash of the entire certificate (including the signature).
So, if we could modify any part of the certificate that’s outside the TBS without invalidating the certificate, then we would modify the thumbprint without changing the signature. If the parser parses the signature correctly and the TBS is unchanged, then the certificate would still be considered valid and signed, even though the full certificate structure has changed (Figure 7).
Fig. 7: Added data outside of the TBS does not affect the validity of the certificate
MD5 chosen prefix collisions — a brief overview
Say you have two arbitrary strings, A and B, of the same length. Then, two strings, C and D, can be calculated efficiently, such that
MD5(A || C) = MD5(B || D)
where || indicates string concatenation.
Moreover, it is not just the final MD5 result that will be the same, but also the MD5 internal state after appending C or D. Therefore, if you take any suffix E, you would then have
MD5(A || C || E) = MD5(B || D || E)
(provided the same suffix E is added on both sides).
Making room for collision blocks
As attackers, we’ll need to generate a certificate that seems valid but also contains room for collision blocks (the strings C and D in the explanation above). This will enable us to create our malicious certificate (with the same MD5 thumbprint), which we will serve next.
According to RFC 5280, section 4.1.1.2, the structure of signatureAlgorithm is
AlgorithmIdentifier ::= SEQUENCE {
algorithm OBJECT IDENTIFIER,
parameters ANY DEFINED BY algorithm OPTIONAL }
The parameters field for the RSA algorithm (based on RFC 3279) “SHALL be the ASN.1 type NULL”. In other words: RSA doesn’t use signature parameters but instead takes NULL as value. Is it possible that CryptoAPI ignores this field for RSA signatures?
To insert placeholder bytes to this field (as preparation for collision blocks), we tried to change its ASN.1 type from NULL to BIT STRING. Testing this against CryptoAPI as well as OpenSSL, it works — the certificate is still considered valid. The signature is unchanged and unbroken because we didn’t modify the TBS. (Of course, the MD5 thumbprint does change.)
Certificate MD5 thumbprint collisions
Now, we can piece things together and provide a recipe for manipulating an existing, already-signed certificate to collide with a malicious certificate’s MD5 thumbprint.
- Take a legitimate RSA-signed end certificate, such as a website’s TLS certificate (our “target certificate”).
- Modify any interesting fields (subject, extensions, EKU, public key, etc.) in the TBS part of the certificate to create the malicious certificate. Note: We don’t touch the signature, so the malicious certificate is incorrectly signed. Modifying the public key is important here — this allows the attacker to sign as the malicious certificate.
- Modify the parameters field of the signatureAlgorithm field of both certificates, so that there is enough space to put MD5 collision blocks (C and D in the explanation above) starting in the same offset of both certificates.
- Truncate both certificates at the position where MD5 collision blocks are to be placed.
- Perform an MD5 chosen prefix collision computation and copy the result into the certificates.
- Concatenate the legitimate certificate’s signature value (suffix E in the explanation above) to both incomplete certificates.
A real-world example
With our understanding of MD5 collisions, we can now attempt to exploit this CVE with a real target. Among the numerous applications we checked, we were able to find a vulnerable target: Chrome v48. (This application is vulnerable simply because it passes the flag CERT_CHAIN_CACHE_END_CERT to CertGetCertificateChain.) Other Chromium-based applications from that time are also vulnerable to this CVE.
In order for us to exploit this vulnerability we first needed to create two certificates that have the same MD5 thumbprint, which we did using HashClash (Figure 8).
Fig. 8: Generating two certificates with the same MD5 thumbprint using chosen prefix collision
We then had to find a way to inject our modified target certificate to Chrome’s cache. This was tricky to do since it is impossible to serve a certificate without knowing its private key.
In TLS 1.2, there are two relevant verification stages:
- The Server Key Exchange message — this message can only be constructed by someone who knows the certificate’s private key, since it's signed by the certificate
- The Server Handshake Finished message — this message includes an anti-tamper verification of all previous handshake messages
(TLS 1.3 is different and we did not focus on it.)
Remember, at the first phase of the attack we want to inject the modified certificate into Chrome's end certificate cache.
Using a Python script as a proxy, we carry out a machine-in-the-middle (MITM) attack:
- Our malicious MITM server talks with the real server and reflects the first messages of the TLS handshake to the victim.
- In the Server Certificate message, our malicious MITM server modifies the real server's message and replaces the real target certificate with the modified certificate.
- The Server Key Exchange message can be reflected without changes.
- Our malicious server cannot simply forward the Server Handshake Finished message, because the handshake was indeed tampered with. Thus, we terminate the connection.
In order to verify the Server Key Exchange message, Chrome must load the modified certificate with CryptoAPI and, therefore, it would be injected into the cache. Chrome doesn't treat the broken connection as a TLS security issue — it could be just a random network issue. Chrome tries to reconnect, and this time, instead of reflecting messages from the real website, the malicious server will serve a website with the malicious certificate. Chrome will skip the full verification process because it thinks the certificate is already in the cache. The result will be a seamless site visit to a seemingly legitimate Microsoft website (Figures 9 and 10). The full exploitation flow can be seen in our video.
Fig. 9: Full attack flow on Chrome v48
Fig. 10: The vulnerable Chrome trusts our malicious certificate
Detection
We provide an OSQuery to detect vulnerable versions of crypt32.dll, the vulnerable library.
Keep in mind that for an asset to be vulnerable it needs to have an unpatched version of crypt32.dll and run a vulnerable application. (To this day, we’ve only found Chrome v48 to be vulnerable.)
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
Copy
Conclusion
Conclusion
Certificates play a major role in identity verification online, making this vulnerability lucrative for attackers. But although it was marked critical, the vulnerability was only given a CVSS score of 7.5. We believe this is due to the limited scope of vulnerable applications and Windows components in which the vulnerability prerequisites are met.
That being said, there is still a lot of code that uses this API and might be exposed to this vulnerability, warranting a patch even for discontinued versions of Windows, like Windows 7.
We advise you to patch your Windows servers and endpoints with the latest security patch released by Microsoft. For developers, another option to mitigate this vulnerability is to use other WinAPIs to double-check the validity of a certificate before using it, such as CertVerifyCertificateChainPolicy. Keep in mind that applications that do not use end-certificate caching are not vulnerable.
Our PoC code can be found in our GitHub repository. You can also keep up to date with all Akamai Security Research publications via our Twitter account.