Reports
BAJA[OTROS]#3913012

Roundcube: inyección de cabeceras SMTP persistente a través del campo "organización" de la identidad

El campo organization de las identidades de Roundcube se guardaba sin quitar saltos de línea y se copiaba tal cual a la cabecera Organization, lo que permitía añadir cabeceras como Bcc o From a todos los correos enviados.

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

En Roundcube, cada usuario puede definir identidades de envío con su nombre, organización, dirección, etc. El manejador que las guarda, program/actions/settings/identity_save.php (líneas 49-52), copia todas las columnas directamente del cuerpo POST. Solo valida email, reply-to y bcc, mediante rcube_utils::check_email() (líneas 82-97). Los campos name y organization no se validan ni se limpian de saltos de línea, y rcube_charset::clean(), la única transformación que aplica get_input_string(), conserva intactos los caracteres CR y LF.

Al enviar un correo, el valor guardado se asigna sin más a la cabecera Organization en program/include/rcmail_sendmail.php (líneas 216-218):

$headers['Organization'] = $identity_arr['organization'];

Organization es una cabecera no estructurada, y Mail_mimePart::encodeHeader() tiene un atajo para ese caso: si el valor es ASCII puro y strlen($name.': '.$value) <= 78, lo devuelve sin plegar ni codificar en RFC 2047. Su comprobación de caracteres no ASCII, preg_match('#([^\s\x21-\x7E]){1}#'), trata CR y LF como espacios (\s), así que no los detecta. El resultado es que el CRLF guardado llega al servidor SMTP como separador de cabeceras real, sin que Net_SMTP::quotedata() tenga que normalizar nada.

El reporte se envió al programa de Nextcloud (que integra Roundcube). Consta como resuelto, con severidad "none". Reportado el 3 de agosto de 2026 y divulgado el 18 de septiembre de 2026.

Pasos de reproducción

  1. Iniciar sesión en Roundcube con cualquier usuario.
  2. Guardar una identidad con POST /?_task=settings&_action=save-identity, un _token válido y este valor de organización:
_organization=Acme%0D%0ABcc:%[email protected]%0D%0AFrom:%[email protected]

PHP decodifica %0D%0A en bytes CR/LF reales.

  1. identity_save.php (líneas 49-52) copia _organization en $save_data['organization'] sin filtrarlo; el bucle de validación de correos (líneas 82-97) no lo revisa.
  2. rcube_user::update_identity() lo guarda byte a byte en la base de datos mediante un UPDATE parametrizado.
  3. Redactar y enviar cualquier mensaje con esa identidad: POST /?_task=mail&_action=send.
  4. rcmail_sendmail.php (línea 148 → líneas 652-664) lee la identidad y la asigna a $headers['Organization'] (líneas 216-218).
  5. program/lib/Roundcube/rcube.php:1802 serializa las cabeceras con Mail_mimePart::encodeHeader(), que no transforma el valor, y program/lib/Roundcube/rcube_smtp.php:321 las escribe en la conexión SMTP.
  6. El investigador lo confirmó con una prueba ejecutable contra el código real del repositorio y sus dependencias:
php roundcubemail-master_VULNHUNT_RESULTS_2026-08-03-095945/exploit_tests/test_vuln_002_smtp_header_injection_identity.php

Resultado: la prueba pasa, y lo que sale por la conexión es:

Organization: Acme\r\n
Bcc: [email protected]\r\n
From: [email protected]\r\n

Impacto

Como el valor queda guardado en la identidad, todos los mensajes que envíe la cuenta con ella llevan las cabeceras inyectadas hasta que alguien la edite:

  • Destinatarios ocultos en Bcc.
  • Cabeceras From: o Sender: falsificadas.
  • Señales de confianza falsas que interpretan los MTA y filtros antispam posteriores.

Todo ello sale con las credenciales SMTP de envío del propio servidor.

El caso más grave, según el investigador, son las instalaciones que fijan identities_level a 1 o 3 precisamente para impedir que los usuarios cambien su dirección de remitente. Ese control solo bloquea el campo email (identity_save.php, líneas 69-71) y deja organization editable, así que el usuario puede inyectar su propia cabecera From: y saltarse la restricción por completo.

Remediación

El reporte figura como resuelto, pero no describe el cambio aplicado.

Nota para el revisor: el programa asignó severidad "none", que no existe en el sitio; se ha puesto Baja. La debilidad que figura en HackerOne ("Buffer Under-read") no encaja con el fallo, que es una inyección CRLF en cabeceras (CWE-93); se ha dejado el CWE vacío al no constar uno correcto. El título original menciona también el campo name, pero el cuerpo solo demuestra organization. El reporte trata de Roundcube aunque se envió al programa de Nextcloud. La ruta del script de prueba apunta a resultados de una herramienta automática del investigador que no se incluyen. No consta recompensa.