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
- Iniciar sesión en Roundcube con cualquier usuario.
- Guardar una identidad con
POST /?_task=settings&_action=save-identity, un_tokenvá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.
identity_save.php(líneas 49-52) copia_organizationen$save_data['organization']sin filtrarlo; el bucle de validación de correos (líneas 82-97) no lo revisa.rcube_user::update_identity()lo guarda byte a byte en la base de datos mediante unUPDATEparametrizado.- Redactar y enviar cualquier mensaje con esa identidad:
POST /?_task=mail&_action=send. rcmail_sendmail.php(línea 148 → líneas 652-664) lee la identidad y la asigna a$headers['Organization'](líneas 216-218).program/lib/Roundcube/rcube.php:1802serializa las cabeceras conMail_mimePart::encodeHeader(), que no transforma el valor, yprogram/lib/Roundcube/rcube_smtp.php:321las escribe en la conexión SMTP.- 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:oSender: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 camponame, pero el cuerpo solo demuestraorganization. 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.