CVE-2024-6197 Curl and Libcurl: Use-after-Free on the Stack

Curl and libcurl - 863x300

On July 24th 2024, Curl maintainers announced a new stack buffer Use After Free (UAF) vulnerability – CVE-2024-6197. This type of vulnerability is very uncommon since UAF issues usually occur on the heap and not on the stack.

While the vulnerability can be easily exploited for causing denial of service, in this blog we will show why we believe that it is almost impossible to exploit this vulnerability to achieve remote code execution in any real-world setup.

Which versions of Curl are affected?

CVE-2024-6197 affects both the Curl command-line tool and Libcurl.

Affected versions: Curl and Libcurl from 8.6.0 up to and including 8.8.0

Attack prerequisites for CVE-2024-6197

Curl and libcurl - Image1 v3
An unsuspecting user connects to a malicious server which returns a fake certificate

The vulnerability can be exploited when all of the following conditions are true:

  1.  The victim uses curl (either the curl library or the curl CLI tool) to connect to a malicious TLS server. The malicious server offers a crafted TLS certificate, which triggers the vulnerability. The vulnerability can also be exploited in a MitM scenario, however in this scenario a DoS is already possible without exploiting any software vulnerability, and therefore there is no added security impact by this CVE in a MitM scenario.
  2. The curl binary is built with support for GnuTLS, wolfSSL, Schannel, Secure Transport, or mbedTLS. To check if and instance of Curl is vulnerable, run curl –version and check whether any of the TLS backends mentioned above are listed. Builds using other TLS backends or popular Linux distributions such as Debian, Ubuntu and Alpine are not vulnerable by default as most of them are built with OpenSSL by default.

Curl and libcurl - Image2The curl –version command can help determine whether a specific version is vulnerable

3. Curl’s certificate information flag CURLINFO_CERTINFO must be set. This flag enables Libcurl to gather detailed information about the SSL certificate chain used in a connection, which can be retrieved after a transfer using curl_easy_getinfo with the CURLINFO_CERTINFO option.

From our research, we observed that curl is only vulnerable when CURLINFO_CERTINFO is used explicitly (although other, more rare exploitation scenarios may be possible). For example:


curl_easy_setopt(curl, CURLOPT_CERTINFO, 1L);

The curl CLI tool is vulnerable when invoked with specific CLI flags which cause the CURLINFO_CERTINFO flag to be enabled:


// The --write-out flag will enable CURLINFO_CERTINFO by default
curl https://example.com --write-out ‘%certs’ 

Attack Overview

Libcurl’s ASN.1 parser includes the function utf8asn1str(), which is designed to parse ASN.1 UTF-8 strings, which can be found in certificates. In some cases, the function might call on a 4-byte local stack buffer which can trigger a stack use after free vulnerability.

Most malloc implementations, including glibc, identify this mistake and immediately terminate the process. However, some implementations might accept the input pointer and add that memory to their list of available chunks. This can result in the overwriting of stack memory. The content that overwrites this memory is determined by the free() implementation, and typically consists of memory pointers and a set of flags.

To exploit CVE-2024-6197 for remote code execution, the following steps must be taken:

  1. The attacker needs to set up a malicious TLS server with a X.509 certificate that triggers the invalid free().
  2. An application using Libcurl or the curl CLI needs to connect to the malicious TLS server. This causes the malicious certificate to get parsed and the stack address to be put into the freelist of the allocator.
  3. The attacker interacts with the client in a way that causes the stack address to be returned by a malloc() call and used to store data from the attacker.
  4. Depending on the stack layout, the attacker will potentially be able to overwrite local variables, pointers and more.

In-Depth Details

Crafting the malicious certificate

The HTTPS (X.509) certificate format is defined by RFC 5280. The definitions are expressed in ASN.1, which is a language used to define formats and data structures. ASN.1 is associated with many encodings, such as BER and DER (Distinguished Encoding Rules) which is a binary encoding for X.509 certificates and private keys.

Most HTTPS certificates are generally encoded in DER which is a TLV encoding (type-length-value or tag-length-value).

Curl and libcurl - Image3Encoding with DER results in a string indicating the Tag, Length and Value

As you read bytes that are encoded with DER, first you encounter a type, called in ASN.1 a “tag”. This is a byte, or series of bytes, that tells you what is being encoded:  This could be an INTEGER, a UTF8String or other predefined value. Next you encounter the “length” value, which is a number that tells you how many bytes of data you’re going to need to read in order to get the “value”. Then, of course, comes the bytes containing the “value” itself.

In Libcurl’s utf8asn1str; function that is designed to parse and process ASN.1 UTF-8 strings from certificates, there is a local stack buffer initialization. The buffer is freed when the wc variable is larger than 0x00200000 as can be seen in [1]:


while(!result && (from < end)) {
      char buf[4]; /* decode buffer */
      int charsize = 1;
      unsigned int wc = 0;

      switch(size) {
      case 4:
        wc = (wc << 8) | *(const unsigned char *) from++;
        wc = (wc << 8) | *(const unsigned char *) from++; // [2]
        FALLTHROUGH();
      case 2:
        wc = (wc << 8) | *(const unsigned char *) from++;
        FALLTHROUGH();
      default: /* case 1: */
        wc = (wc << 8) | *(const unsigned char *) from++; } if(wc >= 0x00000080) {
        if(wc >= 0x00000800) {
          if(wc >= 0x00010000) {
            if(wc >= 0x00200000) { // [1]
              free(buf);
              /* Invalid char. size for target encoding. */
              return CURLE_WEIRD_SERVER_REPLY;
            }
 

The wc variable can only be set to a value that is larger than 0xff00 when the size variable is exactly 4, as can be seen in [2]

The size variable is initialized based on the type of string in the certificate and returns a value of 4 if the type equals UniversalString which is an old encoding method that is almost never used in certificates.

For size = 4, the following conditions must  be satisfied:

  • The string type must be UniversalString (0x1c)
  • The string should contain values that surpass the 0x00200000 limitation.

It’s important to note that creating a certificate with a UniversalString is not an easy task, as most certificate creation tools such as OpenSSL do not support encoding strings to UniversalString.

Abusing the Use-after-Free

Curl-and-libcurl-Image4.pngA successful RCE attack can be launched by writing into the recently “freed” stack location

Calling free(buf) will crash the program on most Linux distros. In Linux distributions with heap implementations that do not crash, calling malloc, or any other heap allocation function, soon after the buffer is freed will erroneously return a stack pointer as the return value from malloc. This could allow the attacker to write into the stack location that was “freed” earlier. For a successful RCE, attackers will have to find a suitable malloc call in curl, that can be triggered by communication from a malicious TLS server, and then make sure they can control the content that will be written into the “allocated” space.

Putting it all together

To successfully exploit CVE-2024-6197, the following steps are required:

  • Finding a Linux distribution that will not crash after free(buf) can be quite challenging as most Linux distributions we checked crashed the program when free() is called on a stack variable. For example, Ubuntu 22.04 and Ubuntu 24.04 crash, while Alpine 3.10 does not, due to their different C implementations.
  • Setting up a TLS server with a crafted certificate that needs to be encoded with at least one UniversalString, where at least 2 of the characters in the UniversalString need to pass the wc >= 0x00200000 condition.

Finding a code flow in curl that can be triggered via a TLS response from a TLS server controlled by the attacker, that can reach malloc() soon after the erroneous free() is called.

How exploitable is CVE-2024-6197 in the real world?

Curl-and-libcurl-Image5.pngDemonstrating DoS via CVE-2024-6197

During our research, we tried to find a Linux distribution that would not crash (e.g. Debian, Ubuntu and Red-Hat) after freeing a stack buffer. Eventually, we identified Alpine 3.10 as a distro that does not crash under these conditions, due to the fact that Alpine uses the musl libc implementation. Up to and including musl version 1.2.1, musl didn’t have any checks on the address that it frees.

We had to build Curl with a vulnerable TLS backend, as it was installed on Alpine 3.10 with a non-vulnerable backend by default (OpenSSL). Our research proved that even though those versions did not result in a crash (DoS), using free(buf) did result in an error that was not caught, and the stack address was not added to the freelist bin as we expected. Therefore, an RCE-based attack cannot be achieved in this case.

Curl-and-libcurl-Image6.png

Uncaught ENOMEM error (RAX) from munmap

Since curl is an ubiquitous project it might be possible to exploit this vulnerability in the future as more sophisticated attacks are developed. However, calling free() on a stack buffer cannot be achieved in most Linux distributions without crashing – meaning that remote code execution is highly unlikely.

It should also be taken into consideration that this vulnerability was introduced quite recently in Curl version 8.6.0, which was released on January 31st, 2024. As such, it’s highly unlikely that this version will be present in any Linux distribution or embedded system that does not have mitigation against freeing stack addresses. Thus, further diminishing the likelihood of remote code execution.

For all of the reasons cited above, we believe the vast majority of curl users will not be affected by this vulnerability.

What is the true severity of CVE-2024-6197?

As shown in previous sections, this issue can be triggered only when several specific conditions are met. Moreover, RCE could not be achieved when exploiting this CVE due to the code flow and the mitigations of glibc and musl in most Linux distributions.

The most significant damage that we expect to see is DoS in Curl clients, which is not a real issue because crashing a forked client process has a negligible security impact.

Resolving CVE-2024-6197

Upgrading to the newly released Curl 8.9.0 is the most straightforward way to resolve this vulnerability. Make sure to identify any versions that might be at risk, from 8.6.0 up to and including 8.8.0, and update them to this newly fixed version.

In addition to the upstream patched version, several Linux distributions have already published fixed versions of curl, most notably, Ubuntu, Debian, Alpine, SUSE, and Red Hat JBoss Services (Red Hat Enterprise Linux was not affected).

Mitigations against Stack-based Use-After-Free

Glibc version 2.3.4, released in 2004, introduced a mitigation that neutralizes this issue as a Remote Code Execution (RCE) vulnerability. The relevant code snippet is as follows:


/* Or whether the next chunk is beyond the boundaries of the arena.  */
if (__builtin_expect (contiguous (av)
			&& (char *) nextchunk
			>= ((char *) av->top + chunksize(av->top)), 0))
{
errstr = "double free or corruption (out)";
	goto errout;
}

This code examines the next heap chunk to be freed and verifies that it is within one of the heap arenas. If it is outside of those arenas, the application aborts. When the vulnerability is triggered and the stack pointer is freed, this check fails, causing the program to crash.

Another check that can catch the issue is the chunk alignment check, as can be seen in the code snippet below:


 /* Little security check which won't hurt performance: the
     allocator never wrapps around at the end of the address space.
     Therefore we can exclude some size values which might appear
     here by accident or by "design" from some intruder.  */
  if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0)
      || __builtin_expect (misaligned_chunk (p), 0))
    {
      errstr = "free(): invalid pointer";
    errout:
      if (!have_lock && locked)
        (void) mutex_unlock (&av->mutex);
      malloc_printerr (check_action, errstr, chunk2mem (p), av);
      return;
    }

This code verifies that the chunk address is aligned to a memory page, and if it fails, the program will crash as well.

Is the JFrog Platform vulnerable to CVE-2024-6197?

After conducting internal research, we can confirm that the JFrog DevOps platform is not vulnerable to Curl CVE-2024-6197.

Resolving CVE-2024-6197 with JFrog Xray and JFrog Advanced Security

As always, JFrog Security Essentials (Xray) and JFrog Advanced Security can be used to identify every occurrence of Curl across your entire codebase and compiled artifacts, including Docker containers, repository packages, and even standalone binaries.

JFrog Platform: JFrog Contextual Analysis for Curl’s CVE-2024-6197 (Click image for full-size)

Keep up with the latest news about vulnerabilities by following us on X and visiting The JFrog Security Research site. For more information about our security solutions feel free to take an online tour or book a one-on-one demo.