Reports
BAJA[AUTENTICACIÓN]#3973135

curl seguía firmando con AWS SigV4 después de desactivarlo con CURLOPT_AWS_SIGV4 a NULL

En libcurl, poner CURLOPT_AWS_SIGV4 a NULL vaciaba los parámetros pero no desactivaba la autenticación SigV4, así que un handle reutilizado seguía enviando cabeceras Authorization firmadas a la siguiente URL.

Resumen
Resumen en castellano de un reporte público, no una traducción literal. El código y los comandos se mantienen como en el original.

Resumen

La documentación de libcurl dice que la opción CURLOPT_AWS_SIGV4 se desactiva dándole el valor NULL. En la práctica, esa llamada devolvía CURLE_OK pero solo hacía la mitad del trabajo: borraba la cadena de parámetros (proveedor, región y servicio) y dejaba activado el método de autenticación. Si la aplicación reutilizaba el mismo easy handle, la siguiente petición salía igualmente con una cabecera Authorization: AWS4-HMAC-SHA256, aunque fuese a otro destino.

El fallo tiene asignado el CVE-2026-86409 y HackerOne lo clasifica como divulgación de información. Afecta desde curl 7.75.0 hasta 8.21.0, y también a las versiones previas de 8.22.0 anteriores al commit c3c70440e8, que es el que lo corrige. El investigador lo probó en el commit c2676bf9e639e31e44b12a2cc82cf2046231b056.

Dónde estaba el error

El manejador de la opción, en lib/setopt.c (líneas 2061-2072), solo contemplaba el caso de que se pasara una cadena:

case CURLOPT_AWS_SIGV4:
    result = Curl_setstropt(data, STRING_AWS_SIGV4, ptr);
    if(CURL_EASY_STR(data, STRING_AWS_SIGV4))
      s->httpauth = CURLAUTH_AWS_SIGV4;
    break;

Con ptr a NULL, Curl_setstropt vacía STRING_AWS_SIGV4, pero como no hay rama else, el bit CURLAUTH_AWS_SIGV4 sigue puesto en data->set.httpauth. La opción vecina CURLOPT_HTTPSIG, en el mismo fichero, sí lo hace bien:

if(arg)
  s->httpauth = (uint32_t)CURLAUTH_HTTPSIG;
else
  s->httpauth &= ~(uint32_t)CURLAUTH_HTTPSIG;

La cadena de consecuencias en la siguiente transferencia es esta:

  1. lib/transfer.c:505 copia el data->set.httpauth obsoleto, todavía con SigV4, a data->state.authhost.want.
  2. La lógica de autenticación de lib/http.c elige SigV4 y llama a Curl_output_aws_sigv4().
  3. Como la cadena de parámetros está vacía, parse_sigv4_params() (lib/http_aws_sigv4.c:820) no da error: usa por defecto "aws:amz" y saca la región y el servicio del nombre de host de la nueva URL.

El resultado es una petición firmada con las credenciales que siguieran en CURLOPT_USERPWD, sin que la aplicación lo haya pedido.

Pasos de reproducción

  1. Compilar una versión afectada de curl (7.75.0 o posterior) con soporte de autenticación AWS SigV4.
  2. Levantar un servidor HTTP de pruebas que registre, en cada petición, la ruta y la cabecera Authorization completa.
  3. Crear un único easy handle de libcurl y configurarle un write callback para que la respuesta no interfiera en la prueba.
  4. Poner credenciales de prueba con CURLOPT_USERPWD (no hacen falta reales: el servidor solo anota la cabecera generada).
  5. Activar SigV4 dándole a CURLOPT_AWS_SIGV4 una cadena explícita con proveedor, región y servicio.
  6. Apuntar el handle al servidor de pruebas y hacer una primera petición.
  7. Comprobar que esa petición lleva Authorization: AWS4-HMAC-SHA256 con el identificador de clave y el ámbito de credencial configurados.
  8. En el mismo handle, poner CURLOPT_AWS_SIGV4 a NULL y verificar que curl_easy_setopt() devuelve CURLE_OK.
  9. Cambiar la URL del handle a otra ruta u otro destino, sin tocar CURLOPT_USERPWD.
  10. Hacer una segunda petición con el mismo handle y revisar las cabeceras registradas.
  11. Resultado vulnerable: la segunda petición sigue llevando una cabecera Authorization de SigV4, aunque según la API documentada la opción estaba desactivada.
  12. Para confirmar que la cadena de parámetros sí se borró, comprobar que la segunda firma usa el proveedor por defecto o deduce región y servicio del nuevo host, en lugar de los valores explícitos del paso 5.
  13. Como control, poner CURLOPT_HTTPAUTH a CURLAUTH_NONE y hacer una tercera petición.
  14. Comprobar que esta no lleva cabecera Authorization: lo que provocaba la segunda era la selección de autenticación que se había quedado colgada.
  15. Para validar el arreglo, repetir la secuencia con una revisión que incluya el commit c3c70440e8 (o que limpie el bit CURLAUTH_AWS_SIGV4 al recibir NULL).
  16. Verificar que la primera petición sale firmada y que las dos posteriores al reinicio con NULL ya no llevan cabecera de SigV4.

Lo esperado es que, tras poner CURLOPT_AWS_SIGV4 a NULL, ninguna petición vuelva a llevar firma SigV4 hasta que la aplicación la active de nuevo.

Impacto

Para que se dé el problema tienen que coincidir varias cosas: una aplicación que reutiliza el mismo handle, mantiene las credenciales en CURLOPT_USERPWD, confía en el reinicio con NULL que describe la documentación y después hace una petición a una URL en la que un atacante puede influir. En ese caso, el destino recibe una cabecera Authorization firmada con SigV4 que no debería haber recibido.

Esa cabecera revela el identificador de la clave de acceso y una firma ligada a esa petición concreta. Según el reporte, si el servicio y la operación son compatibles, la firma podría autorizar acciones que la aplicación no pretendía. Hay dos límites claros:

  • La clave secreta no viaja en la petición.
  • La firma cubre método, host, ruta, cabeceras, cuerpo y marca de tiempo, lo que limita su reutilización.

Remediación

El arreglo está en el commit c3c70440e8. La corrección que propone el reporte es añadir la rama que faltaba para quitar el bit de autenticación cuando la opción recibe NULL:

case CURLOPT_AWS_SIGV4:
    result = Curl_setstropt(data, STRING_AWS_SIGV4, ptr);
    if(CURL_EASY_STR(data, STRING_AWS_SIGV4))
      s->httpauth = CURLAUTH_AWS_SIGV4;
    else
      s->httpauth &= ~CURLAUTH_AWS_SIGV4;
    break;

El investigador propuso además un test de regresión: activar SigV4, reiniciar la opción a NULL, reutilizar el handle y comprobar que la siguiente petición sale sin Authorization.

El reporte se envió el 27 de agosto de 2026 y se hizo público el 14 de octubre de 2026.

Qué aprender de este caso

  • En librerías con opciones que activan un modo de autenticación, revisa qué pasa al "apagarlas": si la opción guarda por separado un parámetro (aquí la cadena SigV4) y un indicador de método (el bit en httpauth), es fácil que el reinicio limpie uno y olvide el otro.
  • Comparar manejadores hermanos del mismo fichero es una buena forma de encontrar este tipo de fallo: CURLOPT_HTTPSIG tenía su else y CURLOPT_AWS_SIGV4 no. Un if que pone un flag sin el else que lo quita es la señal a buscar.
  • Un valor por defecto silencioso agrava el problema: al no haber parámetros, curl usó "aws:amz" y dedujo región y servicio del host en vez de fallar. Si falta configuración obligatoria para firmar, es más seguro no firmar o devolver error.
  • Si tu aplicación reutiliza handles de libcurl entre destinos distintos, no confíes en desactivar una opción concreta: restablece la autenticación de forma explícita (CURLOPT_HTTPAUTH a CURLAUTH_NONE) o retira las credenciales antes de apuntar a una URL que no controlas.