3.7 Low
CVSS3
Attack Vector
NETWORK
Attack Complexity
HIGH
Privileges Required
NONE
User Interaction
NONE
Scope
UNCHANGED
Confidentiality Impact
NONE
Integrity Impact
LOW
Availability Impact
NONE
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N
4.3 Medium
CVSS2
Access Vector
NETWORK
Access Complexity
MEDIUM
Authentication
NONE
Confidentiality Impact
NONE
Integrity Impact
PARTIAL
Availability Impact
NONE
AV:N/AC:M/Au:N/C:N/I:P/A:N
0.003 Low
EPSS
Percentile
61.6%
(I don’t think that this can be easily exploitable, but I am submitting it as a security issue for precaution. I am not looking for a bounty.)
Commit 549310e907e82e44c59548351d4c6ac4aaada114 enables session resumption with TLS 1.3. Curl connections maintain two SSL contexts, one for the proxy and one for the destination. However, curl incorrectly stores session tickets issued by an TLS 1.3 HTTPS proxy under the non proxy context.
The issue is that the logic inside Curl_ssl_addsessionid
that chooses which context to store the tickets under is incorrect under TLS 1.3.
const bool isProxy = CONNECT_PROXY_SSL();
struct ssl_primary_config * const ssl_config = isProxy ?
&conn->proxy_ssl_config :
&conn->ssl_config;
const char *hostname = isProxy ? conn->http_proxy.host.name :
conn->host.name;
#define CONNECT_PROXY_SSL()\
(conn->http_proxy.proxytype == CURLPROXY_HTTPS &&\
!conn->bits.proxy_ssl_connected[sockindex])
One of the major differences between how TLS session tickets are issued between TLS 1.3 and prior versions of TLS is that TLS 1.3 issues session tickets in a post handshake message. What this means in practice is that TLS 1.3 tickets are delivered in the first call to SSL_read()
, rather than being issued as part of SSL_connect()
. Consequently, CONNECT_PROXY_SSL()
will see that the proxy has already been connected (since the call to SSL_connect()
to the proxy was completed), so the call to Curl_ssl_addsessionid
believes the isProxy
is false
, and it stores the ticket under the non proxy context.
After the CONNECT
call returns successfully, a connection to the original destination will be made through the established TCP tunnel. If the original destination uses https, another TLS handshake will be made. During this TLS handshake, the curl client offers the session ticket of the proxy to the destination.
If the proxy is malicious, at this point it could decide to terminate the TLS handshake to the upstream. Since the proxy has the corresponding session ticket key (it was the entity that issued the ticket, after all), it can complete the client -> destination TLS handshake through a resumption. Normally, this would result in a full man in the middle, as TLS certificates are not exchanged as part of a resumed connection. However, curl already performs some of its own certificate validation outside of OpenSSL in ossl_connect_step3
, which largely mitigates this vulnerability.
The certificate validation that curl performs includes steps such as (1) checking if the certificate was self signed and (2) ensuring that the certificate contains a subject that matches the destination. The certificate of the proxy is stored in the SSL_SESSION
that was used for resumption, so curl will attempt to perform these validations against the proxy certificate.
I’ve attached a reproducer in this report.
server_that_fails_on_ticket.c
is a simple TLS server (listening on port 12345) that will send an alert if it receives a session resumption attempt. Under normal circumstances, curl should never be sending a ticket when connecting through a proxy, since it has never connected to this destination before. With this bug, you should be able to observe that the server receives a ticket on the first connection regardless.https_proxy.c
is a extremely rudimentary implementation of a HTTPS proxy (listening on port 12346), that only uses TLS 1.3. If a special proxy header Mitm: 1
is passed, then the proxy will attempt to terminate the TLS connection itself, acting as a man in the middle.proxy_ca.pem
is the CA file that signs the proxy cert, haxx.se.pem
haxx.se.pem
is the TLS certificate that the proxy uses. Notice that it has the identities: localhost
andhaxx.se
.server_that_fails_on_ticket
. This will listen on port 12345https_proxy
. This will listen on port 12346curl --proxy-cacert proxy_ca.pem -x 'https://localhost:12346' 'https://localhost:12345'
https_proxy
. This will listen on port 12346curl --proxy-cacert proxy_ca.pem --proxy-header 'Mitm: 1' -x 'https://localhost:12346' 'https://haxx.se'
The MITM is only possible because haxx.se
is listed as one of the subjects in the proxy certificate. Curl’s certificate validation passes: (1) the proxy cert is not self signed and (2) the name haxx.se is present in the certificate is “presented” by the original destination.
In a very specific environment (perhaps a corporate environment where all access to the internet requires going through an HTTPS proxy), an attacker that can issue a trusted proxy certificate may be able to man in the middle connections established with libcurl, even if curl explicitly does not include the proxy CA in the trust store for normal destinations.
3.7 Low
CVSS3
Attack Vector
NETWORK
Attack Complexity
HIGH
Privileges Required
NONE
User Interaction
NONE
Scope
UNCHANGED
Confidentiality Impact
NONE
Integrity Impact
LOW
Availability Impact
NONE
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N
4.3 Medium
CVSS2
Access Vector
NETWORK
Access Complexity
MEDIUM
Authentication
NONE
Confidentiality Impact
NONE
Integrity Impact
PARTIAL
Availability Impact
NONE
AV:N/AC:M/Au:N/C:N/I:P/A:N
0.003 Low
EPSS
Percentile
61.6%