Skip to main content

Breaking down the ’critical’ OpenSSL vulnerability

wordpress-sync/feature-openssl-blue

4 de novembro de 2022

0 minutos de leitura

On November 1st 2022, the OpenSSL team released an advisory detailing two high severity vulnerabilities — CVE-2022-3602 and CVE-2022-3786. This was pre-announced as a critical bug, but later downgraded to high for the actual release. This could still be problematic though, OpenSSL is one of the predominant encryption libraries and is underpinning a significant portion of the internet's TLS protected communications.

In this post we’ll break down the two OpenSSL vulnerabilities — with a focus on the technical details for CVE-2022-3602 — and look at whether or not the level of attention this received is warranted, if this is really a heartbleed 2.0, and how concerned we should actually be.

The advisory

The advisory which outlines these bugs describes two buffer overflow / overrun vulnerabilities in the X.509 certificate validation, specifically within the name constraint validation. These issues affect OpenSSL 3.0.0 to 3.0.6, and users of OpenSSL 3 should update to 3.0.7. However, it’s important to not panic. According to Censys, as of October 2022, a few more than 7,000 hosts out of nearly 1.8 million (0.4%) run a version greater than or equal to version 3.0.0. The most deployed version of OpenSSL within the vulnerable range is 3.0.1, with around 50% of all hosts. Additionally OpenSSL is not the only option for SSL/TLS implementations — with alternatives such as NSS, BoringSSL, SChannel, and LibreSSL. Additionally, there are certain conditions for this specific bug, which even further reduce the number of impacted systems from those running vulnerable versions of OpenSSL 3.

So, how do we approach understanding this situation? Let's start with the commit that fixed the more serious issue (CVE-2022-3602), which has the potential to lead to Remote Code Execution and see what information we can obtain.

CVE-2022-3602

Interestingly, the commit is only changing a single line within the codebase!

wordpress-sync/blog-openssl-breakdown-single-line-change

Based on the above change, this looks a lot like a classic “off by one” bug in ossl_punycode_decode, which is confirmed in the commit message — “An off by one error in the punycode decoder allowed for a single unsigned int overwrite of a buffer, which could cause a crash and possible code execution.

CVE-2022-3786

A secondary buffer overflow in the ossl_a2ulabel function, with a severity of high, was disclosed and fixed in this commit. Unlike CVE-2022-3602, this issue was always rated as high severity, and wasn’t downgraded. Despite this flaw allowing an attacker to overflow and write to a larger region of memory, it is not possible for an attacker to control what data is written. In this case, only the . character (decimal 46) can be written, which restricts the exploit possibilities to memory corruption using the specified character, causing a Denial of Service.

After looking at the two issues, it’s clear that CVE-2022-3602 — which can allow for Remote Code Execution — is the more interesting vulnerability. So from here on, we will focus on it.

Off by one

We won’t go into detail on what off by one vulnerabilities are exactly and how to exploit them, as this is already covered well in other places (Fortify, Daniel Slater), but some basic knowledge is required to understand the OpenSSL bug in question. Off by one vulnerabilities occur when length checking conditions are implemented incorrectly. An easy way to visualize this is to think about a fixed size buffer and a for loop that failed to take the zero-index into account.

wordpress-sync/blog-openssl-breakdown-off-by-one

In the above example, we have two things that go wrong. First, we no longer get our NULL byte to terminate our string from the program arguments when the size exceeds 32 — allowing the following printf to read beyond the bounds of buffer. More importantly however, we are writing a single arbitrary byte outside the bounds of buffer. Generally, the off by one can result in the following scenarios:

  • Overwrite the saved frame pointer of the previous function, thus allowing application flow to be redirected when the function returns

  • Overwriting an adjacent variable on the stack next to the destination buffer

Back to OpenSSL CVE-2022-3206

The patch for CVE-2022-3206 reveals a fairly similar off by one to our textbook example above. The ossl_punycode_decodefunction incorrectly uses the greater than operator instead of greater than or equal to at line 184 — which results in a loop termination condition not returning at the correct condition, and writing an additional unsigned integer outside the bounds of the allocated space.

So, when is this vulnerable function invoked? ossl_punycode_decode provides punycode decoding functionality, which allows unicode characters to be represented in the limited ASCII character subset supported for domain names — e.g. ŠÑÝĶ.io is encoded to xn--iday5n4f.io. In order for untrusted input to be decoded by this function the following need to apply:

  • A malicious CA or intermediary certificate must include a name constraint field with punycode.

  • The leaf certificate must contain a SubjectAlternateName (SAN) otherName field, which specifies a SmtpUTF8Mailbox string.

When these conditions are satisfied, the punycode from the name constraint field will be parsed by the vulnerable ossl_punycode_decodefunction.

Server-Side Attacks

For this to affect servers running OpenSSL 3, the server must be configured to accept client certificates. Client certificate authentication is not used in the majority of scenarios, so most servers will be unaffected (due to most services typically using a form based approach only). If you are accepting client certificates the attack path may be viable. However, the punycode decoding occurs after certificate validation, so a valid certificate chain or a configuration where signature errors are ignored is required.

It could well be the case that a private CA or intermediary is an untrusted party and could be positioned to sign valid certificates that are trusted by the server — for example, this is fairly common when authenticating API requests from external web services. But if client certificates are issued by a trusted party, then exploitation should not be possible based on the current information available.

Client-Side Attacks

A TLS client using OpenSSL 3 could be affected when connecting to a malicious server. The same constraints for a valid certificate chain or certificate errors to be ignored apply here. While it can be common to ignore certificate errors in TLS clients, this is generally not the default.

I meet the attack requirements, how likely is exploitation?

Since there is a vulnerability here, OpenSSL libraries should be patched to 3.0.7 to fix this issue. In most cases, the outcome would be Denial of Service. However, exploitation to Remote Code Execution is likely to be non-trivial. These conditions allow for an arbitrary write of four bytes, which doesn’t provide an attacker with many opportunities. The security labs team at Datadog have already explored exploitation of this issue in detail, and even corrupting important memory regions to cause a Denial of Service is difficult on Linux based systems. Modern memory protection mechanisms such as PIE, padding, and stack canaries / cookies significantly reduce the likelihood of a working RCE exploit.

Detecting memory corruption bugs

When dealing with unmanaged languages such as C/C++ it's very easy for subtle defects to materialize in detrimental ways, although it doesn't look like this bug is a major concern — this could have been detected with dynamic fuzz testing.

To catch these OpenSSL bugs with a fuzzer, two approaches can be taken:

  1. Create a test harness for the vulnerable functions ossl_punycode_decode and ossl_a2ulabel within crypto/punycode.c and fuzz them directly. This approach is good in case one knows what they’re looking for.

  2. Create a client-server setup with a crafted certificate, and pass through the entire OpenSSL certificate parsing and validation. This is a general approach, which has a significant setup overhead compared to the first one.

Since it’s significantly easier, we opted for the first option. Here’s an example for the test file created to catch CVE-2022-3602 with libFuzzer — a coverage-guided, in-process fuzzing engine:

1#include <stdio.h>
2#include <openssl/x509.h>
3#include <openssl/x509v3.h>
4#include <openssl/err.h>
5#include "fuzzer.h"
6#include "internal/nelem.h"
7#include <crypto/punycode.h>
8
9#define MAX_OUTPUT_BUFF 1024
10
11static BIO *bio_out;
12
13int FuzzerInitialize(int *argc, char ***argv)
14{
15   OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL);
16   ERR_clear_error();
17   CRYPTO_free_ex_index(0, -1);
18   return 1;
19}
20
21int FuzzerTestOneInput(const uint8_t *buf, size_t len)
22{  
23   unsigned int output_buf[MAX_OUTPUT_BUFF];
24   unsigned int bsize = OSSL_NELEM(output_buf);
25   (void)ossl_punycode_decode(buf, len, output_buf, &bsize);
26   ERR_clear_error();
27   return 0;
28}
29
30void FuzzerCleanup(void)
31{
32    BIO_free(bio_out);
33}

After running it for a bit on ossl_punycode_decode from version 3.0.6, we found a crash in the vulnerable function:

1==9024==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffeb247ede0 at pc 0x5615d1499782 bp 0x7ffeb247da50 sp 0x7ffeb247d220
2WRITE of size 264 at 0x7ffeb247ede0 thread T0
3    #0 0x5615d1499781 in __asan_memmove (/home/user/openssl/fuzz/punycode+0xd7d781) (BuildId: 546d1cbf454c87a04c514633876bd5307eab38b2)
4    #1 0x5615d155c023 in ossl_punycode_decode /home/supriza/openssl/crypto/punycode.c:188:9
5    #2 0x5615d14d76de in FuzzerTestOneInput /home/supriza/openssl/fuzz/punycode.c:42:11

Applying the patch that fixes the issue prevents the fuzzer from crashing, further validating the finding.

If you would like to get familiar with libfuzzer to protect your own native code, or just experiment with OpenSSL fuzzing, you can find our fuzzing setup on GitHub. It would be interesting to demonstrate how this bug can be caught in a real, client-server SSL certificate validation scenario with a more complete fuzzing setup.

The bottom line

The recent pre-announcement of the OpenSSL 3 vulnerabilities certainly caused a lot of concern, but now that the details are available we are thankful this is not a “heartbleed 2.0” style bug like many anticipated. While there are still two high severity vulnerabilities here, these are not internet breaking bugs and we do not expect to see widespread exploitation. Organizations regular patch and vulnerability management procedures can be followed here to update to OpenSSL 3.0.7 or later within a timely manner. Additionally, tools such as Snyk can help teams identify vulnerable instances of OpenSSL (and many other security issues) for patching. Get started with a free forever Snyk account today.