Resumen
El fallo está en el orden de dos operaciones dentro de rcmail_sendmail::email_input_format(), en program/include/rcmail_sendmail.php, la función que procesa los destinatarios al enviar un correo desde Roundcube:
- En la línea 760 se eliminan los caracteres CR y LF de la dirección.
- Veinticinco líneas más abajo, en la 790, se ejecuta
stripcslashes()sobre el nombre visible del destinatario.
Si el usuario escribe dentro del nombre entrecomillado los dos caracteres \ y r (una barra invertida y la letra «r», no un retorno de carro), el filtro de la línea 760 no encuentra nada que quitar, porque todavía no hay ningún carácter de control. Después, stripcslashes() interpreta esa secuencia como un escape de C y la convierte en un byte 0x0D real, cuando la limpieza ya se ha hecho.
Ninguna capa posterior lo corrige:
format_email_recipient()(program/lib/Roundcube/bootstrap.php:329-333) solo escapa las comillas dobles".Mail_mimePart::encodeHeader()solo pliega secuencias que cumplen/\r?\n[\s\t]*/, así que un CR aislado no encaja. Además, su comprobación de caracteres no ASCII,preg_match('#([^\s\x21-\x7E]){1}#'), trata el CR como espacio en blanco y no aplica codificación RFC 2047.- Por último,
Net_SMTP::quotedata()(vendor/pear/net_smtp/Net/SMTP.php:1489) normaliza cada CR aislado a CRLF completo, de modo que el texto inyectado sale por la conexión SMTP como líneas de cabecera independientes.
Pasos de reproducción
- Iniciar sesión en Roundcube con cualquier usuario.
- Enviar una petición
POST /?_task=mail&_action=sendcon un_tokenválido y este parámetro, en el que cada\res una barra invertida seguida de la letra «r». Las direcciones de las cabeceras inyectadas eran de ejemplo en el original; aquí se muestran como [REDACTADO]:
_to="x\rBcc: [REDACTADO]\rSender: [REDACTADO]\rX-Pad: p" <[email protected]>
program/include/rcmail_sendmail.php:112lee el valor sin procesar y el filtro de CR/LF de la línea 760 no elimina nada.- En la línea 790,
$name = stripcslashes($name);convierte las tres secuencias en tres bytes 0x0D reales dentro del nombre visible. - La línea 202 guarda el resultado en
$headers['To'], la 384 lo pasa aMail_mimeyprogram/lib/Roundcube/rcube.php:1802serializa las cabeceras dejando intactos los CR aislados. program/lib/Roundcube/rcube_smtp.php:321llama aNet_SMTP::data(), cuyoquotedata()convierte cada CR en CRLF.- El investigador acompañó una prueba ejecutable que recorre el código real del repositorio y de las dependencias de principio a fin:
php roundcubemail-master_VULNHUNT_RESULTS_2026-08-03-095945/exploit_tests/test_vuln_001_smtp_header_injection_recipient.php
El resultado fue PASS, con esta salida en la conexión:
To: "x\r\n
Bcc: [REDACTADO]\r\n
Sender: [REDACTADO]\r\n
X-Pad: p"\r\n
<[email protected]>\r\n
La prueba comprueba que las cabeceras Bcc: y Sender: inyectadas aparecen como líneas de cabecera propias, separadas por CRLF.
Impacto
El formulario de redacción construye las cabeceras a partir de un conjunto fijo de claves (rcmail_sendmail.php:198-269) para que el usuario no pueda escribirlas libremente. Con este fallo, cualquier usuario autenticado podía añadir cabeceras RFC 822 arbitrarias a mensajes que el servidor envía con sus propias credenciales SMTP. En concreto podía:
- Falsificar
From:,Sender:oReply-To:, saltándose la comprobación de identidades del servidor (rcmail_sendmail.php:147-175) y la restricción deidentities_level(program/actions/settings/identity_save.php:64-79). El correo suplantado saldría desde un servidor que cumple de verdad el SPF y el DKIM del dominio. - Añadir destinatarios ocultos con
Bcc:. - Falsificar cabeceras en las que confían otros servidores y filtros antispam, como
Received:,Authentication-Results:oX-Spam-Status:. - Insertar una línea vacía para cerrar antes de tiempo el bloque de cabeceras y controlar el cuerpo del mensaje tal como lo interpreta el servidor de correo.
El reporte original no detalla cómo se corrigió el fallo. En HackerOne figura con la debilidad «Buffer Over-read» y severidad «none», aunque lo descrito es una inyección de cabeceras; la severidad Media de esta ficha es una valoración propia a partir del impacto explicado.
Qué aprender de este caso
- Cualquier función que «desescape» texto (
stripcslashes(),stripslashes(), decodificación URL, unicode o HTML) puede crear caracteres de control nuevos. Al revisar código, busca esas llamadas que aparecen después de un filtro de CR/LF: el filtro tiene que ir al final, justo antes de construir la cabecera. - Mira bien los filtros que solo buscan
\r\no\n. Un CR aislado se les escapa, y capas comoNet_SMTP::quotedata()lo convierten luego en un salto de línea completo. - El nombre visible de una dirección (
"Nombre" <correo>) es un campo de texto libre que suele recibir menos control que la dirección. Conviene rechazar cualquier carácter de control en el valor final de cada cabecera, en lugar de confiar en que lo haga la librería MIME o SMTP.