CVE-2025-4947: QUIC certificate check skip with wolfSSL
What the report claims
The report claims that when using WolfSSL as the TLS backend and connecting to an IP address over HTTP/3, curl skips verification of the certificate's CN or SAN because peer->sni is NULL, leading to improper host validation.
Compare with curl's recorded outcome
Known vulnerability
A real vulnerability: CVE-2025-4947 QUIC certificate check skip with wolfSSL
Shown for comparison after analysis. The pipeline did not see this label.
ProveIt's finding
The available evidence does not settle the report. A maintainer should investigate.
See the withdrawn model reasoning
The following reasoning failed the citation check and does not support the final finding.
- The WolfSSL verification code in Curl_vquic_tls_verify_peer (lib/vquic/vquic-tls.c:179-191) only calls wolfSSL_X509_check_host when peer->sni is non-NULL.
- When connecting to an IP address, SNI is typically not sent, so peer->sni would be NULL, causing the entire hostname/IP verification to be skipped.
- The connection then succeeds (returns CURLE_OK) without any certificate host matching, allowing a MITM with a mismatched certificate to go undetected.
- This differs from OpenSSL backend behavior (which verifies IP addresses via X509_check_ip) and HTTP/1.1 code paths, confirming the inconsistency the reporter observed.
- This is a security issue: an attacker who can redirect traffic to a server with a valid certificate for a different host/IP can intercept QUIC connections to IP addresses when WolfSSL is used.
What still needs checking
- Confirmation that peer->sni is indeed NULL when connecting to an IP address in the QUIC code path (requires checking the SNI handling code not provided).
How this analysis ran 5 claims · 6 citations verified
Evidence check: judge quoted an external source that does not say that (https://docs.openssl.org/3.5/man3/SSL_set1_host)
Checked against curl master on 2025-05-17 (0013bbdfb8c8). Test evaluation split. Citation verification checks that a referenced line was in the evidence; it does not prove the model interpreted it correctly.
Inspect the withdrawn draft reply
This draft belongs to the withdrawn judgment. It may contain unsupported claims.
Thank you for the detailed report. The code in lib/vquic/vquic-tls.c:181-189 confirms that WolfSSL's certificate hostname verification is only performed when peer->sni is non-NULL. When connecting to an IP address, SNI is typically absent, so peer->sni is likely NULL, causing the verification to be skipped entirely. This allows a certificate mismatch to go undetected for QUIC connections to IP addresses with WolfSSL. We will investigate the fix, which should verify against the configured hostname/IP (not just SNI) similar to how OpenSSL backend handles it.
Inspect all claims and source code 5 claims · 1 code excerpts
This model judgment was withdrawn. The claim assessments below are preserved for inspection and may be wrong.
Report claims
CN or SAN in the certificate is not verified when connecting to an IP address over HTTP/3 with WolfSSL backend
Read the reasoning
The code at lines 181-189 shows that when USE_WOLFSSL is defined, the certificate hostname verification (wolfSSL_X509_check_host) is only executed if peer->sni is non-NULL. If peer->sni is NULL (as when connecting to an IP address), the entire verification block is skipped.
The verification code is located in Curl_vquic_tls_verify_peer() under the USE_WOLFSSL block
Read the reasoning
The WolfSSL-specific verification code is located in Curl_vquic_tls_verify_peer at lines 179-191.
Specifying an IP address instead of a domain name causes peer->sni to be NULL
Read the reasoning
The provided code does not show where peer->sni is set or why it would be NULL for IP addresses. This depends on QUIC/TLS handshake code not included in the source excerpt. The claim is plausible (SNI is for domain names, not IP addresses) but cannot be verified from the given code alone.
The connection succeeds without proper host validation, allowing a certificate mismatch to go undetected
Read the reasoning
When peer->sni is NULL, the function returns CURLE_OK (line 162, 196) without performing any hostname or IP address verification against the peer certificate, allowing a mismatched certificate to be accepted.
Run curl --http3 https://<IP_address> where the IP is obtained from a domain name, using a curl build with WolfSSL backend
Read the reasoning
The reproduction steps describe a plausible scenario but cannot be verified against the provided code alone; they require a live WolfSSL build and network test.
What the report says about other projects
confirmed OpenSSL's SSL_set1_host sets the expected DNS hostname or IP address for certificate verification; when a nonempty host is specified, verification automatically checks the peer hostname via X509_check_host. When an IP address and/or multiple hostnames are configured, the peer is considered verified when any of these matches.
“SSL_set1_host() sets in the verification parameters of *s* the expected DNS hostname or IP address to *host*... When an IP address and/or multiple hostnames are configured, the peer is considered verified when any of these matches.” source
confirmed X509_check_host checks if the certificate Subject Alternative Name (SAN) or Subject CommonName (CN) matches the specified hostname, which must be encoded in the preferred name syntax. X509_check_ip checks IP address matching.
“X509_check_host() checks if the certificate Subject Alternative Name (SAN) or Subject CommonName (CN) matches the specified hostname... int X509_check_ip(X509 *x, const unsigned char *address, size_t addresslen, unsigned int flags);” source
Read the original report
Source code
| 156 | CURLcode Curl_vquic_tls_verify_peer(struct curl_tls_ctx *ctx, |
| C2: Function Curl_vquic_tls_verify_peer definition starts here | |
| 157 | struct Curl_cfilter *cf, |
| 158 | struct Curl_easy *data, |
| 159 | struct ssl_peer *peer) |
| 160 | { |
| 161 | struct ssl_primary_config *conn_config; |
| 162 | CURLcode result = CURLE_OK; |
| 163 | |
| 164 | conn_config = Curl_ssl_cf_get_primary_config(cf); |
| 165 | if(!conn_config) |
| 166 | return CURLE_FAILED_INIT; |
| 167 | |
| 168 | #ifdef USE_OPENSSL |
| 169 | (void)conn_config; |
| 170 | result = Curl_oss_check_peer_cert(cf, data, &ctx->ossl, peer); |
| 171 | #elif defined(USE_GNUTLS) |
| 172 | if(conn_config->verifyhost) { |
| 173 | result = Curl_gtls_verifyserver(data, ctx->gtls.session, |
| 174 | conn_config, &data->set.ssl, peer, |
| 175 | data->set.str[STRING_SSL_PINNEDPUBLICKEY]); |
| 176 | if(result) |
| 177 | return result; |
| 178 | } |
| 179 | #elif defined(USE_WOLFSSL) |
| C2: #elif defined(USE_WOLFSSL) block starts here | |
| 180 | (void)data; |
| 181 | if(conn_config->verifyhost) { |
| C4: conn_config->verifyhost is checked but the actual verification is gated on peer->sni | |
| 182 | if(peer->sni) { |
| C1: wolfSSL_X509_check_host is only called inside 'if(peer->sni)' block | |
| C4: If peer->sni is NULL, no certificate hostname/IP check occurs at all | |
| 183 | WOLFSSL_X509* cert = wolfSSL_get_peer_certificate(ctx->wssl.ssl); |
| 184 | if(wolfSSL_X509_check_host(cert, peer->sni, strlen(peer->sni), 0, NULL) |
| C1: The hostname verification uses peer->sni as the name to check against | |
| 185 | == WOLFSSL_FAILURE) { |
| 186 | result = CURLE_PEER_FAILED_VERIFICATION; |
| 187 | } |
| 188 | wolfSSL_X509_free(cert); |
| 189 | } |
| 190 | |
| 191 | } |
| 192 | #endif |
| 193 | /* on error, remove any session we might have in the pool */ |
| 194 | if(result) |
| 195 | Curl_ssl_scache_remove_all(cf, data, peer->scache_key); |
| 196 | return result; |
| 197 | } |