Resumen
Una aplicación que usa libcurl puede restringir en qué autoridades confía registrando un callback con CURLOPT_SSL_CTX_FUNCTION: dentro de él cambia el almacén de certificados del contexto TLS, por ejemplo para aceptar solo una CA privada. Este fallo, registrado como CVE-2026-82208 (debilidad: validación incorrecta de certificados), hacía que esa restricción se perdiera sin ningún aviso cuando curl estaba compilado con wolfSSL y la caché de CAs estaba activa.
El problema está en Curl_wssl_setup_x509_store(), en lib/vtls/wolfssl.c. La función usa una bandera, wssl->x509_store_setup, para saber si el almacén X509 ya está configurado. Pero esa bandera solo se pone a TRUE dentro de wssl_populate_x509_store(), que únicamente se ejecuta cuando la caché no tiene el almacén (fallo de caché). Cuando sí lo tiene (acierto de caché), se instala el almacén cacheado y la bandera se queda en false.
Con la bandera sin marcar, la conexión sigue este camino:
- Antes de llamar al callback,
wssl_connect_step1()invocaCurl_wssl_setup_x509_store(). Se instala el almacén cacheado (A) y la bandera sigue enfalse. - Se ejecuta el callback de
CURLOPT_SSL_CTX_FUNCTION, que sustituye A por el almacén que quiere la aplicación (B). - Ya en el handshake, como la bandera sigue en
false,Curl_wssl_setup_x509_store()se vuelve a ejecutar. Detecta queCTX_get_cert_store() != cached_store(porque el callback lo cambió) y vuelve a instalar A, pisando la decisión de la aplicación.
El resultado es que el certificado del servidor se valida contra el almacén cacheado y no contra el que eligió el callback.
Versiones afectadas: el fallo entró con el commit 0f2876b2c33f (julio de 2024, "wolfSSL: CA store share fix", relacionado con #14278) y está presente desde la 8.9.1 hasta la 8.21.0.
Condiciones necesarias para que ocurra:
- Backend TLS wolfSSL.
CURLOPT_CA_CACHE_TIMEOUTdistinto de cero (caché de CAs activa).- Un callback
CURLOPT_SSL_CTX_FUNCTIONque reemplace el almacén de certificados. - Que los dos almacenes (el cacheado y el del callback) confíen en cosas distintas.
Pasos de reproducción
- Compila una versión afectada de curl (de la 8.9.1 a la 8.21.0) contra wolfSSL, configurada sin ruta de CAs por defecto compilada, para que un fichero de CAs pueda entrar en la caché.
- Genera dos autoridades de certificación independientes: CA A y CA B.
- Emite un certificado de servidor TLS para el nombre de host de prueba, con el subject alternative name correspondiente, firmado por CA A.
- Levanta un servidor HTTPS en ese nombre de host usando ese certificado.
- Escribe un cliente pequeño con libcurl que haga dos transferencias con el mismo multi handle, fije
CURLOPT_CAINFOapuntando a CA A y ponga un valor distinto de cero enCURLOPT_CA_CACHE_TIMEOUT. - Haz la primera transferencia sin callback de contexto SSL. Debe funcionar, y con ello la caché de CAs de curl queda cargada con el almacén que contiene CA A.
- Para la segunda transferencia, usa una conexión nueva y registra
CURLOPT_SSL_CTX_FUNCTION. En el callback, sustituye el almacén de certificados del contexto wolfSSL por uno recién creado que contenga solo CA B. - Desactiva en ambas transferencias la reutilización de conexiones y la caché de sesiones TLS, para que la segunda obligue a un handshake nuevo con verificación de certificado.
- Lanza la segunda transferencia contra el mismo servidor. En una versión vulnerable se completa con éxito, aunque el almacén del callback solo contiene CA B y el certificado del servidor solo encadena con CA A.
- Repite todo con
CURLOPT_CA_CACHE_TIMEOUTa cero. Ahora la segunda transferencia falla en la verificación del certificado, lo que demuestra que la aceptación indebida depende del camino de la caché de CAs. - Como comprobación de la corrección, repite la secuencia con caché sobre una compilación que incluya el commit
ed0338befdo una versión posterior. La segunda transferencia debe fallar, porque curl ya no reinstala CA A después de que el callback elija CA B.
El comportamiento correcto es que el almacén elegido por el callback mande siempre: la transferencia con el almacén B debe rechazar el certificado, se haya llenado antes la caché o no.
Impacto
Una aplicación que usa CURLOPT_SSL_CTX_FUNCTION para endurecer su política de confianza (por ejemplo, anclar las conexiones a una CA privada) la veía ignorada en silencio mientras la caché de CAs estuviera activa. Un atacante en posición de intermediario (MITM) que presente un certificado válido para el nombre de host y aceptado por el almacén cacheado, aunque el almacén del callback lo rechace, podía hacerse pasar por el servidor legítimo. Con ello podía interceptar o modificar un tráfico que la aplicación creía protegido.
Remediación
Según el reporte, las compilaciones que incluyen el commit ed0338befd (o versiones posteriores) ya no reinstalan el almacén cacheado después del callback.
La solución que propuso el investigador es marcar la bandera en la propia Curl_wssl_setup_x509_store(), antes de volver, en lugar de hacerlo solo en wssl_populate_x509_store():
wssl->x509_store_setup = TRUE;
Así la bandera queda puesta tanto si la caché acierta como si falla, y la segunda llamada durante el handshake ya no sobrescribe el almacén elegido por el callback.