Attacker Value
Unknown
(0 users assessed)
Exploitability
Unknown
(0 users assessed)
User Interaction
None
Privileges Required
None
Attack Vector
Network
3

CVE-2022-3786

Disclosure Date: November 01, 2022
Add MITRE ATT&CK tactics and techniques that apply to this CVE.

Description

A buffer overrun can be triggered in X.509 certificate verification, specifically in name constraint checking. Note that this occurs after certificate chain signature verification and requires either a CA to have signed a malicious certificate or for an application to continue certificate verification despite failure to construct a path to a trusted issuer. An attacker can craft a malicious email address in a certificate to overflow an arbitrary number of bytes containing the `.’ character (decimal 46) on the stack. This buffer overflow could result in a crash (causing a denial of service). In a TLS client, this can be triggered by connecting to a malicious server. In a TLS server, this can be triggered if the server requests client authentication and a malicious client connects.

Add Assessment

No one has assessed this topic. Be the first to add your voice to the community.

CVSS V3 Severity and Metrics
Base Score:
7.5 High
Impact Score:
3.6
Exploitability Score:
3.9
Vector:
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Attack Vector (AV):
Network
Attack Complexity (AC):
Low
Privileges Required (PR):
None
User Interaction (UI):
None
Scope (S):
Unchanged
Confidentiality (C):
None
Integrity (I):
None
Availability (A):
High

General Information

Vendors

  • fedoraproject,
  • nodejs,
  • openssl

Products

  • fedora 36,
  • fedora 37,
  • node.js,
  • node.js 18.12.0,
  • node.js 19.0.0,
  • openssl

References

Advisory

Additional Info

Technical Analysis

Description

On November 1, 2022, the OpenSSL project released version 3.0.7 to address a pair of vulnerabilities – CVE-2022-3602 and CVE-2022-3786 – that they labeled as “critical” in their announcements, then downgraded to “high” when they were released. In their blog post, the OpenSSL developers explain that they downgraded the risk because exploitation seems unlikely:

During the week of prenotification, several organisations performed testing and gave us feedback on the issue, looking at the technical details of the overflow and stack layout on common architectures and platforms. […] the stack layout was such that the 4 bytes overwrote an adjacent buffer that was yet to be used and therefore there was no crash or ability to cause remote code execution.

Even though the OpenSSL team knew that the vulnerability was going to be downgraded before they released the patch, they did nothing in the hours leading up to the release to defuse the collective panic. We’d like to see future projects be more open with impact changes, particularly when upcoming releases are going to be less impactful than originally announced.

CVE-2022-3786 refers to an arbitrary-length overflow when parsing Punycode domain names utilizing OpenSSL’s Punycode library, which is part of libcrypto.so and only accessible through certificate-validation functions after certificate validation. In a trusted certificate, this can potentially affect any client application running a vulnerable version of OpenSSL, or any server application that is configured to validate client certificates. Even on affected hosts, the likelihood of meaningful exploitation is low for reasons detailed below.

Affected products are:

  • OpenSSL 3.0.0 – 3.0.6 (fixed in OpenSSL 3.0.7; other OpenSSL versions are not affected)

Technical analysis

CVE-2022-3786 is a parsing error in the ossl_a2ulabel function in punycode.c, where period characters (. or 0x2e) can be infinitely appended to a buffer, which typically leads to denial of service conditions (i.e., crashing the process by flooding it with periods). In rare cases, if a useful memory address is stored right after the buffer, and changing the last byte two of the address to 0x2e is useful, and nothing important gets overwritten in the process, a partial address overwrite (like this one) might lead to a exploitable memory corruption. That would have to be very carefully tailored to the target, and the memory layout of the target would need to be just right, making it unlikely to be widely exploited.

The diff for this issue is rather long, but let’s take a look at the vulnerable code to see what’s going on! A brief proof of concept that demonstrates the issue by directly calling the vulnerable function can be found in our Github repo, which overwrites the return address with 0x2e bytes. We’ll use that in our examples below, but note that it’s a very contrived example. The only known path to real-world exploitation is through the certificate-validation function, which requires a signed and trusted certificate.

The function signature for ossl_a2label is:

int ossl_a2ulabel(const char *in, char *out, size_t *outlen);

It’s designed to decode possibly-Punycode-encoded DNS names; for example, www.xn--n28haaaaaaaaaaaaa.com would become www.😉😉😉😉😉😉😉😉😉😉😉😉😉😉.com. The code is mostly contained in a while (1) loop, which processes each label (ie, the portions of the name between periods) until it runs out:

    while (1) {
        char *tmpptr = strchr(inptr, '.');
        
        // [...]
        
        if (tmpptr == NULL)
            break;
    }

    return result;

If the label does not start with xn-- (ie, it’s a standard label), it’s basically copied directly into the output buffer. A length check sets the result variable to 0 and stops copying memory when we run out of output space, but importantly this does not exit the parsing loop:

        // [...]

        if (strncmp(inptr, "xn--", 4) != 0) {
            size += delta + 1;

            if (size >= *outlen - 1)
                result = 0;

            if (result > 0) {
                memcpy(outptr, inptr, delta + 1);
                outptr += delta + 1;
            }
        } else {
            // [...]
        }

If the label does start with xn--, the else executes, and it decodes using the ossl_punycode_decode function (note that that function is vulnerable to CVE-2022-3602, which means we can write 4 bytes past the end of the stack-based buffer buf, but the next element on the stack does not appear to be a useful target):

        } else {
            unsigned int bufsize = LABEL_BUF_SIZE;
            unsigned int i;

            if (ossl_punycode_decode(inptr + 4, delta - 4, buf, &bufsize) <= 0)
                return -1;

            for (i = 0; i < bufsize; i++) {
                unsigned char seed[6];
                size_t utfsize = codepoint2utf8(seed, buf[i]);
                if (utfsize == 0)
                    return -1;

                size += utfsize;
                if (size >= *outlen - 1)
                    result = 0;

                if (result > 0) {
                    memcpy(outptr, seed, utfsize);
                    outptr += utfsize;
                }
            }

            // [...]
        }

Once again, when it reaches the end of the output buffer, it sets result to 0, then stops writing to the output buffer (but, again, doesn’t stop processing). The following code, however, does write to the buffer:

            if (tmpptr != NULL) {
                *outptr = '.';
                outptr++;
                size++;
                if (size >= *outlen - 1)
                    result = 0;
            }
        }

        // [...]

As long as tmpptr isn’t NULL (meaning, we haven’t reached the final label in the original string), it appends a period, even if result has been set to 0! That means that as long as there are Punycode-encoded labels to decode, we can keep adding periods. Note that the resulting string will look like it’s the correct length. In our contrived PoC, we use the string "a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.xn--.xn--.xn--.xn--.xn--.xn--.xn--[...]", which appears to truncate when viewed as a string:

(gdb) x/s $rbp-0x20
0x7fffffffdea0: "a.b.c.d.e.f.g......."

But that’s because a 4-byte NULL value is appended to the buffer at the end, before more period characters (0x2e) are written as far past the buffer as the exploit desires:

(gdb) x/64xb $rbp-0x20
0x7fffffffdea0: 0x61    0x2e    0x62    0x2e    0x63    0x2e    0x64    0x2e
0x7fffffffdea8: 0x65    0x2e    0x66    0x2e    0x67    0x2e    0x2e    0x2e
0x7fffffffdeb0: 0x2e    0x2e    0x2e    0x2e    0x00    0x00    0x00    0x00
0x7fffffffdeb8: 0x2e    0x2e    0x2e    0x2e    0x2e    0x2e    0x2e    0x2e
0x7fffffffdec0: 0x2e    0x2e    0x2e    0x2e    0x2e    0x2e    0x2e    0x2e
0x7fffffffdec8: 0x2e    0x2e    0x2e    0x2e    0x2e    0x2e    0x2e    0x2e
[...]

Including overwriting the return address (again, this is contrived, not a real-world attack):

(gdb) cont

[...]

Program received signal SIGSEGV, Segmentation fault.
0x000000000040127e in main (argc=1, argv=0x7fffffffdfd8) at cve-2022-3786-periods.c:33
33      }
(gdb) x/i $rip
=> 0x40127e <main+200>: ret
(gdb) x/xg $rsp
0x7fffffffdec8: 0x2e2e2e2e2e2e2e2e

Exploiting this issue in real-world applications will be very dependent on how the vulnerable functions are called. At this point, nobody in the community (to our knowledge) has found an application where this overwrite leads to security implications. That doesn’t mean it’s impossible, however; this vulnerability permits us to perform a partial pointer overwrite, where we can change any number of bytes at the end of a memory address to 0x2e, but only if the memory address is after the Punycode-output buffer and nothing else important is overwritten in between.

In the worst case scenario, a buffer overflow such as this could potentially lead to reading, writing, or executing code from the wrong place in memory. In practice, however, it’s unlikely that any targets are vulnerable to this.

Guidance

Organizations that are running an affected version of OpenSSL should update to 3.0.7 when practical, prioritizing operating system-level updates and public-facing shared services with direct dependencies on OpenSSL. Emergency patching is not indicated.

References