Roundcube es el cliente de webmail de código abierto más ampliamente desplegado en el mundo. ¡Recientemente encontramos una vulnerabilidad peligrosa en la aplicación! Es un XSS almacenado que, encadenado con una técnica de 'cookie tossing', otorga a un atacante acceso completo a la bandeja de entrada de una víctima y, desde allí, a cualquier cuenta que utilice esa dirección de correo electrónico para autenticación o recuperación de contraseña.
Descubrimos esto ejecutando nuestros agentes de pentesting de IA contra una instancia local de Roundcube. Todos los hallazgos fueron reportados de manera responsable a Nextcloud, los mantenedores de Roundcube, a través de HackerOne (XSS revelado en #3594137) y parcheado en la versión 1.6.14.
En este artículo, repasaremos lo que hicimos, cómo nuestros agentes encontraron la vulnerabilidad y cómo una simple inyección HTML podría comprometer completamente la bandeja de entrada de un usuario.
El punto de inyección
Todo ataque tiene un punto de partida. Veamos uno que uno de nuestros agentes detectó para auditar.
Roundcube gestiona el contenido controlado por el usuario de varias formas. Los cuerpos de los correos electrónicos son la superficie más examinada. Estos se desinfectan en gran medida porque se muestran en línea con el resto de la aplicación.
Los archivos adjuntos HTML se renderizan a través de un endpoint separado en mail/get.php, con una Content Security Policy configurada como script-src 'none' para bloquear la ejecución de JavaScript.
Un tercer endpoint, más oculto, gestiona los archivos adjuntos en línea que aún no se han enviado, visibles temporalmente mientras se redacta un correo electrónico. Este es el endpoint que examinaremos. La acción display-attachment gestiona este tipo de solicitudes:
class rcmail_action_mail_attachment_display extends rcmail_action_mail_attachment_upload {
...
public function run($args = []) {
self::init();
$rcmail = rcmail::get_instance();
$file = $rcmail->get_uploaded_file(self::$file_id);
self::display_uploaded_file($file);
La función self::display_uploaded_file() es donde ocurre la magia. Como rcube_uploads.php muestra, este tipo de archivos adjuntos se devuelven directamente con su tipo de contenido y cuerpo originales, sin desinfección, sandboxing ni Content Security Policy aplicada.
header('Content-Type: ' . $file['mimetype']);
header('Content-Length: ' . $file['size']);
if (isset($file['data']) && is_string($file['data'])) {
echo $file['data'];
} elseif (!empty($file['path'])) {
readfile($file['path']);
}
¿Cómo llegamos a este endpoint, se preguntará? Mientras redacta un nuevo correo electrónico en Roundcube, puede adjuntar un archivo al correo electrónico temporal, específicamente un archivo HTML. Le daremos contenido malicioso:
<script>alert(origin)</script>
Después de la subida, al hacer clic en el archivo adjunto se abre una ventana emergente que lo renderiza utilizando la acción get, protegida por una CSP robusta. Este es el camino seguro. Lo interesante es lo que sucede cuando swap _action=get por _action=display-attachment:

Tome la URL original para esta visualización:
/?_task=mail&_frame=1&_file=rcmfile21774532162043767100&_id=193102765369c53621200f8&_action=get&_extwin=1Intercambiando _action=get por _action=display-attachment y eliminando algunos parámetros innecesarios, obtiene:
/?_task=mail&_file=rcmfile21774532162043767100&_id=193102765369c53621200f8&_action=display-attachmentEsta URL renderiza el mismo contenido, ¡pero sin la CSP! Así, el JavaScript dentro de nuestro HTML se ejecuta, alertando el origen actual y confirmando XSS:

Esto es un Self-XSS interesante, pero ¿es realmente un problema? Si somos realistas, un usuario normal no va a subir nuestro payload XSS por sí mismo y seguir viéndolo de esta manera tan particular…
Al examinar attachment_upload.php, se observa que una compose_data_ clave de sesión debe establecerse para su usuario actual, y solo ese usuario puede recuperar el adjunto.
public static function init()
{
self::$COMPOSE_ID = rcube_utils::get_input_string('_id', rcube_utils::INPUT_GPC);
self::$COMPOSE = null;
self::$SESSION_KEY = 'compose_data_' . self::$COMPOSE_ID;
if (self::$COMPOSE_ID && !empty($_SESSION[self::$SESSION_KEY])) {
self::$COMPOSE = &$_SESSION[self::$SESSION_KEY];
}
if (!self::$COMPOSE) {
exit('Invalid session var!');Dado que se trata de un adjunto temporal vinculado únicamente a su sesión actual, no hay forma de preparar un payload y que una víctima lo active iniciando sesión en la cuenta del atacante, como vimos con Mailcow. La totalidad de la roundcube_sessid cookie del atacante tendría que copiarse a la víctima. Otra tarea que suena imposible.
Explotación de Self-XSS con Cookie Tossing
El Self-XSS que encontramos parece inexplotable a primera vista. El adjunto está ligado a la sesión, por lo que no hay forma de preparar un payload para una víctima sin entregarle también la cookie de sesión del atacante. Pero el problema aún no ha terminado. Aquí es donde entra en juego el 'cookie tossing'.
En los navegadores, las cookies tienen algunas peculiaridades interesantes, una de las cuales es el Domain=atributo.
> Solo el dominio actual puede establecerse como valor, o un dominio de orden superior, a menos que sea un sufijo público. Establecer el dominio hará que la cookie esté disponible para este, así como para todos sus subdominios.
Las cookies pueden establecerse no solo para el dominio actual, sino también para un dominio padre. Una cookie establecida por sub.example.com con Domain=example.com pasa a estar disponible para cada subdominio bajo example.com, incluyendo otros como other.example.com que no tuvieron nada que ver con su establecimiento. Este tipo de ataque se denomina Cookie Tossing, donde un subdominio escribe cookies que otro subdominio leerá.
Esto significa que todo lo que necesitamos para explotar nuestra vulnerabilidad es control sobre un subdominio en el mismo dominio que el dominio de Roundcube objetivo. A partir de ahí, una vulnerabilidad XSS separada en algo como xss.target.local puede establecer la propiedad document.cookie para escribir cookies con el atributo Domain=target.local atributo. Una vez que esas cookies están configuradas, el navegador de la víctima las enviará a mail.target.local, donde Roundcube cargará la sesión del atacante en lugar de la de la víctima.
Esa sesión tiene el archivo adjunto HTML malicioso listo y esperando. Al dirigir a la víctima a la URL del archivo adjunto, se activa la carga útil XSS dentro del origen de Roundcube, en el navegador de la víctima, sin necesidad de interacción adicional.
En resumen, lo que un atacante debe hacer para explotarlo es:
- Iniciar sesión en su propia cuenta, crear un nuevo correo electrónico y adjuntar un archivo HTML malicioso
- Copiar el enlace para ver (renderizar) el archivo adjunto y las cookies
- Desde un subdominio vulnerable a XSS, establecer los valores de las cookies usando
document.cookiecon el atributoDomain=apuntando al dominio objetivo. - Redirigir a la víctima al enlace del archivo adjunto. El XSS se activa en el origen de Roundcube.
Así es como se ve en la práctica la carga útil XSS del subdominio, configurando las cookies de sesión y redirigiendo a la víctima a la URL del archivo adjunto
document.cookie='roundcube_sessid=1798cbb4c1d7c7f9ca26069b52aac1aa; Domain=target.local'
document.cookie='roundcube_sessauth=GfNmiyX5brPm4l814QUx62l5gsJKBXfU-1773063000; Domain=target.local'
location.href = 'http://mail.target.local/?_task=mail&_action=display-attachment&_id=183727919869aecb6499f76&_file=rcmfile11773063013009066400';Desde el XSS del subdominio, el resto de la explotación de Roundcube no requiere más interacción del usuario. Toda la seguridad de Roundcube ahora depende de cada subdominio del mismo sitio.

Acceso completo
La ventana emergente de alerta en la captura de pantalla anterior confirma que el XSS se está ejecutando en el origen de Roundcube, pero por sí solo no demuestra un impacto real. Todavía hay un problema. En este punto, hemos cargado la sesión del atacante en el navegador de la víctima, por lo que cualquier acción realizada se hará en la cuenta del atacante en lugar de la de la víctima. Es excelente para entregar nuestra carga útil, pero no tan bueno para acceder a cosas a las que normalmente no podríamos.
Si observamos el navegador, las cookies en realidad no se reemplazan cuando establecemos una diferente Domain=. ¡Ambas se envían!
Cookie: roundcube_sessauth=VICTIM; roundcube_sessauth=ATTACKER
Cuando ambas cookies están presentes, el servidor selecciona la del atacante, ya que aparece en último lugar en la cabecera. En el payload XSS, queremos que se seleccionen las cookies del atacante, mientras que en todos los demás endpoints posteriores, queremos las cookies de la víctima. Afortunadamente, las cookies tienen otro atributo que resuelve perfectamente este problema: Path=.
Al establecer una ruta única como Path=/index.php/xss, que sigue apuntando a la página de inicio, las cookies se enviarán solo cuando esa ruta coincida con el destino de la solicitud. Así, para nuestras solicitudes:
/index.php/xssenvíaroundcube_sessauth=VICTIM; roundcube_sessauth=ATTACKER-> se devuelve el payload del atacante- / envía
roundcube_sessauth=VICTIM-> se devuelven los correos electrónicos de la víctima
Solo tenemos que modificar el exploit de JavaScript para establecer este nuevo atributo, y navegar a /index.php/xss después para asegurar que las cookies del atacante se envíen en esta solicitud para nuestro payload, pero después de eso, nuestro XSS es libre de acceder a la cuenta de la víctima.
document.cookie='roundcube_sessid=1798cbb4c1d7c7f9ca26069b52aac1aa; Domain=target.local; Path=/index.php/xss'
document.cookie='roundcube_sessauth=GfNmiyX5brPm4l814QUx62l5gsJKBXfU-1773063000; Domain=target.local; Path=/index.php/xss'
location.href = 'http://mail.target.local/index.php/xss?_task=mail&_action=display-attachment&_id=183727919869aecb6499f76&_file=rcmfile11773063013009066400';En las DevTools, podemos ver cómo se configuran ahora las cookies duplicadas:

Aunque las condiciones necesarias para explotar esto (cuenta en Roundcube + XSS de subdominio) son un poco complejas, el impacto potencial de un exploit exitoso es enorme.
El correo electrónico es la forma de autenticación más confiable, ya que muchos sitios web dependen de «enlaces mágicos» para iniciar sesión o recuperar contraseñas. Cuando un atacante tiene acceso a sus correos electrónicos, puede activar este tipo de restablecimientos de contraseña en todos los sitios web a los que tenga el correo electrónico conectado. Luego, puede leer el correo electrónico que envía este servicio para confirmar y obtener acceso a muchas más cuentas.
Remediación
Actualice Roundcube a la versión 1.6.14 o posterior (1.6.x), o 1.5.14 o posterior (1.5.x LTS). Todas las vulnerabilidades reportadas están parcheadas en esta versión. Si utiliza Aikido, las instancias vulnerables de Roundcube se marcan automáticamente en su feed de monitorización de superficie como un hallazgo de riesgo medio.

¿Aún no está en Aikido? Cree una cuenta gratuita para empezar, no se requiere tarjeta de crédito.
Conclusión
Aunque inicialmente parecía una vulnerabilidad XSS trivial, su explotación requirió un poco más de conocimiento sobre cookies y sesiones. Los subdominios del mismo sitio a menudo reciben permisos ligeramente superiores por parte del navegador que los sitios web completamente separados, otra razón para asegurar que todos sus activos estén protegidos.
Esta es una tarea difícil, pero como se muestra aquí, Aikido Attack puede realizar pentests de forma autónoma en aplicaciones web para encontrar este tipo de vulnerabilidades en toda su infraestructura.
Los mantenedores de Roundcube parchearon esta vulnerabilidad en la versión 1.6.14 añadiendo una script-src 'none' Política de Seguridad de Contenido, al igual que el resto de los adjuntos ya tenían. Esto imposibilita la ejecución de JavaScript al devolver HTML arbitrario.
Extra: Inyección CRLF de IMAP a través de CSRF
Durante el mismo pentest, otro agente encontró una segunda vulnerabilidad que nos pareció particularmente interesante. Después de reportarla, resultó ser un duplicado que el Martila Security Research Team también había encontrado. Aun así, nos pareció lo suficientemente interesante como para explicar brevemente los detalles de esta vulnerabilidad aquí.
El /?_task=mail&_action=search el endpoint pasa el parámetro suministrado por el cliente _filter directamente al comando IMAP SEARCH en rcube_imap_generic.php:
if (!empty($criteria)) {
$params .= ($params ? ' ' : '') . $criteria;
} else {
$params .= 'ALL';
}
[$code, $response] = $this->execute($return_uid ? 'UID SEARCH' : 'SEARCH', [$params]);Aunque la función parece separar el comando de sus argumentos, la implementación de execute() simplemente los concatena sin sanitización:
foreach ($arguments as $arg) {
$query .= ' ' . self::r_implode($arg);
}
Al inyectar un Retorno de Carro y Salto de Línea (CRLF, %0D%0A) caracteres), un atacante puede evadir los parámetros de SEARCH e inyectar comandos IMAP adicionales dentro de la sesión IMAP del usuario autenticado.
Con esto, no solo se pueden buscar correos electrónicos, sino también añadir carpetas, mover correos o eliminar toda la bandeja de entrada, ya que todos estos son comandos IMAP sin procesar.
A continuación se muestra un ejemplo de filtro configurado en ALL%0D%0AX007%20CREATE%20EvilFolder:
Al visitarlo, se envían los siguientes datos a IMAP, con el comando CREATE inyectado que se ejecuta después de la búsqueda:
X006 SEARCH ALL
X007 CREATE EvilFolderEl efecto de esto se puede ver entonces en la interfaz de usuario de Roundcube:

Dado que se trata de una simple solicitud GET, visitar el enlace anterior es todo lo que se necesita para ejecutar los comandos. Con ello, un solo clic en un enlace podría eliminar permanentemente todos los correos electrónicos (X001 UID STORE 1:* FLAGS \Deleted seguido de X002 EXPUNGE), lo que resultaría en una gran pérdida de datos.
Esta vulnerabilidad ya está parcheada mediante la eliminación de \r\n caracteres de las consultas de búsqueda.

