Reports
MEDIA[OTRAS INYECCIONES]#3913007

Inyección de cabeceras SMTP en Roundcube (Nextcloud) con un `\r` escrito como texto en el nombre del destinatario

Roundcube limpiaba los saltos de línea del destinatario y después aplicaba stripcslashes(), que convertía el texto «\r» en un retorno de carro real. Cualquier usuario autenticado podía añadir cabeceras como Bcc o Sender al correo enviado.

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

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:

  1. En la línea 760 se eliminan los caracteres CR y LF de la dirección.
  2. 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

  1. Iniciar sesión en Roundcube con cualquier usuario.
  2. Enviar una petición POST /?_task=mail&_action=send con un _token válido y este parámetro, en el que cada \r es 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]>
  1. program/include/rcmail_sendmail.php:112 lee el valor sin procesar y el filtro de CR/LF de la línea 760 no elimina nada.
  2. En la línea 790, $name = stripcslashes($name); convierte las tres secuencias en tres bytes 0x0D reales dentro del nombre visible.
  3. La línea 202 guarda el resultado en $headers['To'], la 384 lo pasa a Mail_mime y program/lib/Roundcube/rcube.php:1802 serializa las cabeceras dejando intactos los CR aislados.
  4. program/lib/Roundcube/rcube_smtp.php:321 llama a Net_SMTP::data(), cuyo quotedata() convierte cada CR en CRLF.
  5. 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: o Reply-To:, saltándose la comprobación de identidades del servidor (rcmail_sendmail.php:147-175) y la restricción de identities_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: o X-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\n o \n. Un CR aislado se les escapa, y capas como Net_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.