CVSS2
Attack Vector
NETWORK
Attack 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
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
AI Score
Confidence
High
EPSS
Percentile
81.4%
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.
This issue affect all kinds of protocol over TLS session, e.g. HTTPS, FTPS, SMTPS, etc.
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
.
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
cd mbedtls-2.28.7
make SHARED=1 -j$(nproc)
sudo make install DESTDIR=/usr/local/lib
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"
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
$ 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
$ 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:
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.
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->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
.
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->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. Basically, this is the bug from 7.21.0 and reintroduced in 8.5.0 to 8.6.0 with slightly different way.
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->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 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->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 “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->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.
Just for reference, the following CVEs are related to the weakness of CWE-297:
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
CVSS2
Attack Vector
NETWORK
Attack 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
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
AI Score
Confidence
High
EPSS
Percentile
81.4%