Reports
BAJA[EXPOSICIÓN DE DATOS]#4041053

CVE-2026-94266: curl filtra la contraseña de .netrc en la URL efectiva tras una redirección relativa

Al seguir una redirección relativa en el mismo host, curl copiaba el usuario y la contraseña sacados de .netrc a la URL efectiva, visible en CURLINFO_EFFECTIVE_URL, %{url_effective} y la salida de -v.

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

Cuando curl usa un fichero .netrc para autenticarse, mete el usuario y la contraseña directamente en el objeto URL interno de la transferencia (data->state.uh) y nunca los quita de ahí. Ese mismo objeto se reutiliza después para calcular la URL de destino de una redirección. Si el servidor responde con un Location: relativo (por ejemplo /newpath), curl construye la nueva URL a partir de la anterior conservando todo lo que va antes de la primera barra de la ruta, es decir, esquema://usuario:contraseña@host:puerto. El resultado es que la contraseña acaba en claro en la "URL efectiva" de la transferencia.

El fallo se registró como CVE-2026-94266 en el programa de curl. HackerOne lo marca con severidad "none" y el propio investigador lo considera de severidad baja; nosotros lo clasificamos como baja.

El recorrido del dato, según el análisis del investigador sobre el commit d1823ebc17:

  1. url_set_data_creds_netrc() (lib/url.c, líneas 1325-1332) escribe usuario y contraseña de .netrc en data->state.uh:
      result = Curl_creds_merge(ncreds_out->user, ncreds_out->passwd,
                                *pcreds, CREDS_NETRC, pcreds);
      if(result)
        goto out;
      /* for updated strings, we update them in the URL */
      uc = curl_url_set(data->state.uh, CURLUPART_USER,
                        Curl_creds_user(*pcreds), CURLU_URLENCODE);
      if(!uc)
        uc = curl_url_set(data->state.uh, CURLUPART_PASSWORD,
                          Curl_creds_passwd(*pcreds),
                          CURLU_URLENCODE);
  1. Al seguir una redirección, Curl_http_follow() (lib/http.c:1244) llama a curl_url_set(data->state.uh, CURLUPART_URL, newurl, ...) sobre ese mismo objeto, que ya lleva las credenciales.
  2. Con un Location: relativo, redirect_url() (lib/urlapi.c:1334) calcula cutoff = strchr(protsep, '/') y copia tal cual la base hasta ese punto, incluida la parte usuario:contraseña@. CURLU_URLENCODE solo codifica con % los caracteres que quedan fuera del conjunto "no reservado" (letras, dígitos y -._~), así que una contraseña alfanumérica pasa sin cambios.
  3. La URL resultante se guarda con Curl_bufref_set(&data->state.url, follow_url, ...) (lib/http.c:1298) y se imprime en modo verboso con infof(data, "Issue another request to this URL: '%s'", follow_url) (lib/http.c:1306).
  4. CURLINFO_EFFECTIVE_URL devuelve data->state.url sin limpiar (lib/getinfo.c:84).

Dos matices importantes: con un Location: absoluto no hay fuga, porque set_url() reinicia el objeto por completo antes de analizar la nueva URL; y la URL efectiva del primer salto también sale limpia, porque data->state.url se rellena antes de inyectar las credenciales de .netrc. La contraseña aparece a partir de la primera redirección relativa.

Pasos de reproducción

  1. Crear un fichero .netrc de prueba (aquí llamado netrc_test) con credenciales para el host local:
machine 127.0.0.1
login netrcuser
password netrcSECRET123
  1. Levantar un servidor HTTP local que responda a /first con 302 Found y la cabecera Location: /newpath (una ruta relativa, en el mismo host).
  1. Lanzar curl siguiendo redirecciones, usando ese .netrc y pidiendo que imprima la URL efectiva:
curl -v -L --netrc-file netrc_test --netrc-optional \
  -w '\nEFFECTIVE_URL=%{url_effective}\n' http://127.0.0.1:25036/first
  1. Comprobar que la contraseña aparece tanto en la salida verbosa como en la URL efectiva:
* Issue another request to this URL: 'http://netrcuser:[email protected]:25036/newpath'
...
EFFECTIVE_URL=http://netrcuser:[email protected]:25036/newpath

El investigador obtuvo el mismo resultado desde la API de libcurl, configurando CURLOPT_NETRC=CURL_NETRC_OPTIONAL y CURLOPT_FOLLOWLOCATION=1 y leyendo después curl_easy_getinfo(CURLINFO_EFFECTIVE_URL):

CURLINFO_EFFECTIVE_URL = http://netrcuser:[email protected]:25036/newpath
RESULT: netrc password LEAKED into CURLINFO_EFFECTIVE_URL

Impacto

El usuario y la contraseña de .netrc terminan en valores que las aplicaciones suelen tratar como metadatos inofensivos:

  • CURLINFO_EFFECTIVE_URL
  • CURLINFO_REDIRECT_URL, incluso sin llegar a seguir la redirección
  • las variables %{url_effective} y %{redirect_url} de -w
  • la salida verbosa o de depuración

Basta con que el servidor devuelva un Location: relativo. Si la aplicación guarda esos valores en logs, los manda a un sistema de monitorización, los muestra al usuario o los reenvía a otro servicio, la contraseña sale del almacén de credenciales donde debía quedarse.

El servidor no recibe la contraseña por esta vía y las redirecciones absolutas a otro origen no la arrastran, así que hace falta alguien que pueda ver esos metadatos o logs. Por eso el investigador lo describe como un fallo real de confidencialidad pero de severidad baja, no como un robo directo de credenciales en remoto.

Remediación

El reporte figura como resuelto y con CVE asignado, pero no detalla el parche aplicado. La propuesta del investigador era eliminar la parte de usuario y contraseña de data->state.uh antes de usarlo para calcular la URL de la redirección o la URL efectiva, igual que curl ya hace al construir la cabecera Referer (curl_url_set(u, CURLUPART_USER/PASSWORD, NULL, 0), lib/http.c:1219-1221) y el destino de las peticiones a un proxy sin túnel (lib/http.c:2161-2173). Todo ello sin perder las credenciales que curl sigue necesitando para enviar la cabecera Authorization en el siguiente salto.

Qué aprender de este caso

  • Si una librería guarda credenciales dentro del mismo objeto URL que luego expone o reutiliza, revisa qué pasa al resolver referencias relativas: copiar "todo hasta la primera barra" de la URL base arrastra también la parte usuario:contraseña@.
  • Prueba por separado redirecciones absolutas y relativas: aquí la absoluta reiniciaba el objeto y salía limpia, y solo la relativa filtraba. Un test que use solo una de las dos no lo habría detectado.
  • Las credenciales que llegan por un canal aparte (como .netrc) deberían viajar en su propia estructura y no inyectarse en la URL; si se inyectan, cualquier getter o mensaje de log que devuelva "la URL" necesita quitar el userinfo, como ya hacía curl con el Referer.
  • En aplicaciones que usan libcurl, no registres CURLINFO_EFFECTIVE_URL ni CURLINFO_REDIRECT_URL tal cual en logs o respuestas: elimina el userinfo antes, por si alguna versión lo deja pasar.