Lucene search

K
hackeroneFrankyuehH1:2416725
HistoryMar 14, 2024 - 11:49 a.m.

curl: CVE-2024-2466: TLS certificate check bypass with mbedTLS

2024-03-1411:49:49
frankyueh
hackerone.com
35
curl library
mbedtls
tls certificate bypass
spoofing attacks
unauthorized access
https
ftps
smtps
libcurl versions
mbedtls build
curl cli
server certificate check
sni extension
exploitable issue

5.3 Medium

CVSS3

Attack Vector

NETWORK

Attack Complexity

HIGH

Privileges Required

NONE

User Interaction

REQUIRED

Scope

UNCHANGED

Confidentiality Impact

NONE

Integrity Impact

HIGH

Availability Impact

NONE

CVSS:3.0/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:N

6.6 Medium

AI Score

Confidence

High

5.8 Medium

CVSS2

Access Vector

NETWORK

Access Complexity

MEDIUM

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

NONE

AV:N/AC:M/Au:N/C:P/I:P/A:N

0.008 Low

EPSS

Percentile

80.8%

Summary:

Curl library has a security vulnerability where the certificate name check is bypassed when connecting to a host via its IP address. This could potentially introduce spoofing attacks or unauthorized access due to unverified server certificate.

This issue only affects the Curl with MbedTLS.

  • Affected versions: from libcurl 8.5.0 to and including 8.6.0 (current master versions at the time of writing)
  • Not affected versions: libcurl 8.4.0 and earlier

This issue affect all kinds of protocol over TLS session, e.g. HTTPS, FTPS, SMTPS, etc.

Steps To Reproduce:

(Preparation) Download and build the Curl with MbedTLS:

Skip this step if you already have the Curl (>= 8.5.0) with MbedTLS.

Before building the code, make sure you have environment to build the code in Linux, sudo apt install build-essential.

  1. Get and extract the code:
wget https://curl.se/download/curl-8.6.0.tar.gz -O curl-8.6.0.tar.gz
wget https://github.com/Mbed-TLS/mbedtls/archive/refs/tags/v2.28.7.tar.gz -O mbedtls-2.28.7.tar.gz
tar zxf curl-8.6.0.tar.gz
tar zxf mbedtls-2.28.7.tar.gz
  1. Build MbedTLS:
cd mbedtls-2.28.7
make SHARED=1 -j$(nproc)
sudo make install DESTDIR=/usr/local/lib
  1. Build Curl with MbedTLS:
cd curl-8.6.0
export LD_LIBRARY_PATH=/usr/local/lib
export PATH=/usr/local/lib:$PATH
./configure --with-mbedtls=/usr/local --without-libpsl
make -j$(nproc) CFLAGS="-I/usr/local/include" LDFLAGS="-L/usr/local/lib"

Reproduce the issue via Curl CLI:

If you build the Curl with MbedTLS from above, you should export the LD_LIBRARY_PATH and PATH to use the built Curl.

export LD_LIBRARY_PATH=/usr/local/lib
export PATH=/usr/local/lib:$PATH
  1. Check the version of the Curl:
$ curl --version
curl 8.6.0 (x86_64-pc-linux-gnu) libcurl/8.6.0 mbedTLS/2.28.7 zlib/1.2.11 libidn2/2.2.0
Release-Date: 2024-01-31
Protocols: dict file ftp ftps gopher gophers http https imap imaps ipfs ipns mqtt pop3 pop3s rtsp smb smbs smtp smtps telnet tftp
Features: alt-svc AsynchDNS HSTS HTTPS-proxy IDN IPv6 Largefile libz NTLM SSL threadsafe UnixSockets
  1. Get IP from an example https server and use it to connect:
$ host -t A www.example.org
www.example.org has address 93.184.216.34
$ curl https://93.184.216.34
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
        <head>
                <title>404 - Not Found</title>
        </head>
        <body>
                <h1>404 - Not Found</h1>
        &lt;/body&gt;
&lt;/html&gt;

You could see that the Curl is able to connect to the server via its IP address. This not an expected behavior because the server certificate is not verified against the host name.

The expect result should be an error message like this:

curl: (60) SSL: no alternative certificate subject name matches target host name '93.184.216.34'
More details here: https://curl.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

I just demonstrated the issue with the Curl CLI, but this issue could be exploited in any application that uses the Curl library with MbedTLS and if the connection is made via IP address.

Supporting Material/References:

Cause of the issue:

This issue is caused by the skipping call of the mbedtls_ssl_set_hostname function in mbed_connect_step1. The mbedtls_ssl_set_hostname function is used to set the server name for the SNI extension and also for the server certificate name check. If the mbedtls_ssl_set_hostname function is not called, the server certificate name check will be bypassed.

The vulnerable of code snippet from mbed_connect_step1 (lib/vtls/mbedtls.c) as following:

  if(connssl-&gt;peer.sni) {
    if(mbedtls_ssl_set_hostname(&backend-&gt;ssl, connssl-&gt;peer.sni)) {
      /* mbedtls_ssl_set_hostname() sets the name to use in CN/SAN checks and
         the name to set in the SNI extension. So even if curl connects to a
         host specified as an IP address, this function must be used. */
      failf(data, "Failed to set SNI");
      return CURLE_SSL_CONNECT_ERROR;
    }
  }

If connssl-&gt;peer.sni is not set, the mbedtls_ssl_set_hostname function will not be called and the server certificate name check will be bypassed.

The connssl-&gt;peer object contains the peer information, including the sni, hostname, dispname and type.

typedef enum {
  CURL_SSL_PEER_DNS,
  CURL_SSL_PEER_IPV4,
  CURL_SSL_PEER_IPV6
} ssl_peer_type;

struct ssl_peer {
  char *hostname;        /* hostname for verification */
  char *dispname;        /* display version of hostname */
  char *sni;             /* SNI version of hostname or NULL if not usable */
  ssl_peer_type type;    /* type of the peer information */
};

Before the handshake of TLS session, the peer information is created from Curl_ssl_peer_init function. The sni is optional and only set if the peer type is CURL_SSL_PEER_DNS. If peer type is CURL_SSL_PEER_IPV4 or CURL_SSL_PEER_IPV6, the sni will be null. The code snippet from Curl_ssl_peer_init function (lib/vtls/vtls.c):

    peer-&gt;sni = NULL;
    peer-&gt;type = get_peer_type(peer-&gt;hostname);
    if(peer-&gt;type == CURL_SSL_PEER_DNS && peer-&gt;hostname[0]) {
      /* not an IP address, normalize according to RCC 6066 ch. 3,
       * max len of SNI is 2^16-1, no trailing dot */
      size_t len = strlen(peer-&gt;hostname);
      if(len && (peer-&gt;hostname[len-1] == '.'))
        len--;
      if(len &lt; USHRT_MAX) {
        peer-&gt;sni = calloc(1, len + 1);
        if(!peer-&gt;sni) {
          Curl_ssl_peer_cleanup(peer);
          return CURLE_OUT_OF_MEMORY;
        }
        Curl_strntolower(peer-&gt;sni, peer-&gt;hostname, len);
        peer-&gt;sni[len] = 0;
      }
    }

Therefore, if the Curl connect to a IP address host, the sni will be null and the mbedtls_ssl_set_hostname function will not be called. The server certificate name check will be bypassed and the connection will be established without the server certificate name verification.

Noticeable, this issue is similar to CVE-2016-3739. Basically, this is the bug from 7.21.0 and reintroduced in 8.5.0 to 8.6.0 with slightly different way.

Detailed code change timeline about this issue:

First, the fix for CVE-2016-3739 is this commit. This fix is make sure that the mbedtls_ssl_set_hostname function will be called always, even the host name is not for SNI extension usage, e.g. IP address or SSLv3. Because the mbedtls_ssl_set_hostname function is not called, MbedTLS will not check the server certificate name against the host name and complete the SSL handshake.

This bug is due to the confusion of documentation about the API. Here is the discussion about it: link.

The code fix for CVE-2016-3739:

-  if(!Curl_inet_pton(AF_INET, conn-&gt;host.name, &addr) &&
-#ifdef ENABLE_IPV6
-     !Curl_inet_pton(AF_INET6, conn-&gt;host.name, &addr) &&
-#endif
-     sni && mbedtls_ssl_set_hostname(&connssl-&gt;ssl, conn-&gt;host.name)) {
-    infof(data, "WARNING: failed to configure "
-          "server name indication (SNI) TLS extension\n");
+  if(mbedtls_ssl_set_hostname(&connssl-&gt;ssl, conn-&gt;host.name)) {
+    /* mbedtls_ssl_set_hostname() sets the name to use in CN/SAN checks *and*
+       the name to set in the SNI extension. So even if curl connects to a
+       host specified as an IP address, this function must be used. */
+    failf(data, "couldn't set hostname in mbedTLS");
+    return CURLE_SSL_CONNECT_ERROR;

A comment was even be added to avoid the confusion usage for the mbedtls_ssl_set_hostname function.

Later on, this commit brings back the “SNI” usage and only call the mbedtls_ssl_set_hostname function when snihost is set. Fortunately, if snihost is not set, an error will be returned instead of continuing the SSL handshake without the server name check.

Apparently, This commit reintroduced the confusion about the mbedtls_ssl_set_hostname function usage back again, and just avoid the issue by unrelated error handling of SNI host name conversion.

-  if(mbedtls_ssl_set_hostname(&backend-&gt;ssl, hostname)) {
-    /* mbedtls_ssl_set_hostname() sets the name to use in CN/SAN checks *and*
-       the name to set in the SNI extension. So even if curl connects to a
-       host specified as an IP address, this function must be used. */
-    failf(data, "couldn't set hostname in mbedTLS");
-    return CURLE_SSL_CONNECT_ERROR;
+  {
+    char *snihost = Curl_ssl_snihost(data, hostname, NULL);
+    if(!snihost || mbedtls_ssl_set_hostname(&backend-&gt;ssl, snihost)) {
+      /* mbedtls_ssl_set_hostname() sets the name to use in CN/SAN checks and
+         the name to set in the SNI extension. So even if curl connects to a
+         host specified as an IP address, this function must be used. */
+      failf(data, "Failed to set SNI");
+      return CURLE_SSL_CONNECT_ERROR;
+    }

Eventually, this commit “keep peer name information together”, it separated the sni and hostname in the peer structure, the peer information is created from another place and sni become optional. So the mbedtls_ssl_set_hostname function now is only called if sni is set, if not set it will continue the handshake and bypass host name verification. Therefore, the bug is reintroduced.

-  {
-    char *snihost = Curl_ssl_snihost(data, hostname, NULL);
-    if(!snihost || mbedtls_ssl_set_hostname(&backend-&gt;ssl, snihost)) {
+
+  if(connssl-&gt;peer.sni) {
+    if(mbedtls_ssl_set_hostname(&backend-&gt;ssl, connssl-&gt;peer.sni)) {
       /* mbedtls_ssl_set_hostname() sets the name to use in CN/SAN checks and
          the name to set in the SNI extension. So even if curl connects to a
          host specified as an IP address, this function must be used. */

The possible solution is to always call the mbedtls_ssl_set_hostname function, even if sni is not set. If sni is null, it could use peer.hostname to pass to the mbedtls_ssl_set_hostname function.

Furthermore, we must have to avoid this kind of “for SNI only” confusion in the future again. Apparently, a simple block of comment in the code is not enough to avoid it.

Curl History CVE related to CWE-297:

Just for reference, the following CVEs are related to the weakness of CWE-297:

Impact

The weakness of this issue quote from SWE-297: Improper Validation of Certificate with Host Mismatch:

> Even if a certificate is well-formed, signed, and follows the chain of trust, it may simply be a valid certificate for a different site than the site that the product is interacting with. If the certificate’s host-specific data is not properly checked - such as the Common Name (CN) in the Subject or the Subject Alternative Name (SAN) extension of an X.509 certificate - it may be possible for a redirection or spoofing attack to allow a malicious host with a valid certificate to provide data, impersonating a trusted host. In order to ensure data integrity, the certificate must be valid and it must pertain to the site that is being accessed.
>

Apparently, even the certificate is valid, without the server name check the attacker could use a “valid certificate” for a different site to “impersonate” a trusted host.

Common Consequences:

Reference from CWE-297: Improper Validation of Certificate with Host Mismatch:

Scope Impact
Access Control Technical Impact: Gain Privileges or Assume Identity
The data read from the system vouched for by the certificate may not be from the expected system.
Authentication Other Technical Impact: Other
Trust afforded to the system in question - based on the malicious certificate - may allow for spoofing or redirection attacks.

Likelihood Of Exploit: High

5.3 Medium

CVSS3

Attack Vector

NETWORK

Attack Complexity

HIGH

Privileges Required

NONE

User Interaction

REQUIRED

Scope

UNCHANGED

Confidentiality Impact

NONE

Integrity Impact

HIGH

Availability Impact

NONE

CVSS:3.0/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:N

6.6 Medium

AI Score

Confidence

High

5.8 Medium

CVSS2

Access Vector

NETWORK

Access Complexity

MEDIUM

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

NONE

AV:N/AC:M/Au:N/C:P/I:P/A:N

0.008 Low

EPSS

Percentile

80.8%