Loading HuntDB...

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

Medium
C
curl
Submitted None
Reported by frankyueh

Vulnerability Details

Technical details and impact analysis

Improper Validation of Certificate with Host Mismatch
## 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: ```shell 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 ``` 2. Build MbedTLS: ```shell cd mbedtls-2.28.7 make SHARED=1 -j$(nproc) sudo make install DESTDIR=/usr/local/lib ``` 3. Build Curl with MbedTLS: ```shell 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. ```shell export LD_LIBRARY_PATH=/usr/local/lib export PATH=/usr/local/lib:$PATH ``` 1. Check the version of the Curl: ```shell $ 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 ``` 2. Get IP from an example https server and use it to connect: ```shell $ 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> </body> </html> ``` 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: ```shell 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: ```c if(connssl->peer.sni) { if(mbedtls_ssl_set_hostname(&backend->ssl, connssl->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->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->peer` object contains the peer information, including the `sni`, `hostname`, `dispname` and `type`. ```c 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`): ```c peer->sni = NULL; peer->type = get_peer_type(peer->hostname); if(peer->type == CURL_SSL_PEER_DNS && peer->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->hostname); if(len && (peer->hostname[len-1] == '.')) len--; if(len < USHRT_MAX) { peer->sni = calloc(1, len + 1); if(!peer->sni) { Curl_ssl_peer_cleanup(peer); return CURLE_OUT_OF_MEMORY; } Curl_strntolower(peer->sni, peer->hostname, len); peer->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](https://curl.se/docs/CVE-2016-3739.html). 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](https://github.com/curl/curl/commit/6efd2fa529a189bf41736a610f). 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](https://github.com/Mbed-TLS/mbedtls/issues/466).* The code fix for CVE-2016-3739: ```diff - if(!Curl_inet_pton(AF_INET, conn->host.name, &addr) && -#ifdef ENABLE_IPV6 - !Curl_inet_pton(AF_INET6, conn->host.name, &addr) && -#endif - sni && mbedtls_ssl_set_hostname(&connssl->ssl, conn->host.name)) { - infof(data, "WARNING: failed to configure " - "server name indication (SNI) TLS extension\n"); + if(mbedtls_ssl_set_hostname(&connssl->ssl, conn->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](https://github.com/curl/curl/commit/2218c3a57e86c4ef68c5fa1e2f29e4a9a915d667#diff-fe5c86799b3988e8e1f8680edf2f24a6680b4410e851af5a0e72a1882a5631fcR565-R570) 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. ```diff - if(mbedtls_ssl_set_hostname(&backend->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->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](https://github.com/curl/curl/commit/fa714830e92cba7b16b9d3f2cc92a72ee9d821fa#diff-fe5c86799b3988e8e1f8680edf2f24a6680b4410e851af5a0e72a1882a5631fcR642-R644) "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. ```diff - { - char *snihost = Curl_ssl_snihost(data, hostname, NULL); - if(!snihost || mbedtls_ssl_set_hostname(&backend->ssl, snihost)) { + + if(connssl->peer.sni) { + if(mbedtls_ssl_set_hostname(&backend->ssl, connssl->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:* - <https://curl.se/docs/CVE-2013-4545.html> - <https://curl.se/docs/CVE-2013-6422.html> - <https://curl.se/docs/CVE-2014-0139.html> - <https://curl.se/docs/CVE-2014-1263.html> - <https://curl.se/docs/CVE-2014-2522.html> - <https://curl.se/docs/CVE-2014-8151.html> - <https://curl.se/docs/CVE-2016-3739.html> ## Impact The weakness of this issue quote from [SWE-297: Improper Validation of Certificate with Host Mismatch](https://cwe.mitre.org/data/definitions/297.html): > 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](https://cwe.mitre.org/data/definitions/297.html): | 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

Related CVEs

Associated Common Vulnerabilities and Exposures

The (1) mbed_connect_step1 function in lib/vtls/mbedtls.c and (2) polarssl_connect_step1 function in lib/vtls/polarssl.c in cURL and libcurl before 7.49.0, when using SSLv3 or making a TLS connection to a URL that uses a numerical IP address, allow remote attackers to spoof servers via an arbitrary valid certificate.

cURL and libcurl 7.1 before 7.36.0, when using the OpenSSL, axtls, qsossl or gskit libraries for TLS, recognize a wildcard IP address in the subject's Common Name (CN) field of an X.509 certificate, which might allow man-in-the-middle attackers to spoof arbitrary SSL servers via a crafted certificate issued by a …

curl and libcurl 7.27.0 through 7.35.0, when running on Windows and using the SChannel/Winssl TLS backend, does not verify that the server hostname matches a domain name in the subject's Common Name (CN) or subjectAltName field of the X.509 certificate when accessing a URL that uses a numerical IP address, …

curl and libcurl 7.27.0 through 7.35.0, when using the SecureTransport/Darwinssl backend, as used in in Apple OS X 10.9.x before 10.9.2, does not verify that the server hostname matches a domain name in the subject's Common Name (CN) or subjectAltName field of the X.509 certificate when accessing a URL that …

The darwinssl_connect_step1 function in lib/vtls/curl_darwinssl.c in libcurl 7.31.0 through 7.39.0, when using the DarwinSSL (aka SecureTransport) back-end for TLS, does not check if a cached TLS session validated the certificate when reusing the session, which allows man-in-the-middle attackers to spoof servers via a crafted certificate.

The GnuTLS backend in libcurl 7.21.4 through 7.33.0, when disabling digital signature verification (CURLOPT_SSL_VERIFYPEER), also disables the CURLOPT_SSL_VERIFYHOST check for CN or SAN host name fields, which makes it easier for remote attackers to spoof servers and conduct man-in-the-middle (MITM) attacks.

cURL and libcurl 7.18.0 through 7.32.0, when built with OpenSSL, disables the certificate CN and SAN name field verification (CURLOPT_SSL_VERIFYHOST) when the digital signature verification (CURLOPT_SSL_VERIFYPEER) is disabled, which allows man-in-the-middle attackers to spoof SSL servers via an arbitrary valid certificate.

Report Details

Additional information and metadata

State

Closed

Substate

Resolved

Submitted

Weakness

Improper Validation of Certificate with Host Mismatch