Reports
MEDIA[XSS]#3594137

XSS almacenado en la vista previa de adjuntos de Roundcube, explotable con cookie tossing desde otro subdominio

La acción display-attachment de Roundcube servía en línea los adjuntos HTML subidos al redactar un correo, sin CSP. Combinado con cookie tossing desde un subdominio vulnerable, permitía tomar la cuenta de otro usuario.

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 flujo de redacción de correos de Roundcube, el cliente webmail, reportado dentro del programa de Nextcloud. Cuando un usuario adjunta un fichero a un mensaje que está escribiendo, puede previsualizarlo con la acción mail/attachment_display (_action=display-attachment), que acaba llamando a rcube_uploads::display_uploaded_file().

Esa ruta, con adjuntos HTML, solo pone la cabecera Content-Type y devuelve el fichero tal cual. No hace lo que sí hace el visor general de adjuntos (mail/get.php), que envuelve el HTML en un contenedor seguro y envía una CSP con script-src 'none'. El resultado: un xss.html subido como adjunto se ejecuta en el origen de Roundcube al abrir su vista previa.

Por sí solo no basta para atacar a otro usuario, porque el endpoint exige que la sesión tenga una clave concreta del borrador (la comprueba init() en attachment_upload.php), y esa clave se genera al azar en cada correo nuevo. El atacante no puede preparar un enlace que funcione directamente con la sesión de la víctima.

El investigador salva ese obstáculo con cookie tossing: cualquier servicio que viva en el mismo sitio (otro subdominio del mismo dominio) puede escribir cookies para todo el dominio. Si ese otro servicio tiene un XSS, el atacante fija en el navegador de la víctima sus propias cookies de sesión de Roundcube, limitadas con Path= a una ruta concreta, y la redirige a la vista previa de su adjunto malicioso. El script se ejecuta en el origen de Roundcube mientras el resto de peticiones siguen usando la sesión legítima de la víctima.

Pasos de reproducción

  1. Montar una instancia local de Roundcube en una IP, por ejemplo 127.0.0.1.
  2. Simular que Roundcube y otro servicio comparten dominio añadiendo al fichero hosts (/etc/hosts en Unix, C:\Windows\System32\drivers\etc\hosts en Windows):
127.0.0.1 mail.target.local
127.0.0.1 xss.target.local
  1. Entrar como víctima en http://mail.target.local.
  2. Como atacante, rellenar USERNAME y PASSWORD en el script de prueba del investigador (poc.py, adjunto al reporte) y ejecutarlo. El script inicia sesión, abre un borrador, sube xss.html como adjunto y genera el JavaScript que habrá que ejecutar en el otro subdominio:
$ python3 poc.py
{'roundcube_sessid': '[REDACTADO]', 'roundcube_sessauth': '[REDACTADO]'}
compose_id='183727919869aecb6499f76'
{"action":"upload","exec":"this.add2attachment_list(\"rcmfile11773063013009066400\",{\"html\":\"<a href=\\\"#load\\\" class=\\\"filename\\\" onclick=\\\"return rcmail.command('load-attachment','rcmfile11773063013009066400', this, event)\\\"><span class=\\\"attachment-name\\\">xss.html</span><span class=\\\"attachment-size\\\">(30 B)</span></a><a href=\\\"#delete\\\" onclick=\\\"return rcmail.command('remove-attachment','rcmfile11773063013009066400', this, event)\\\" title=\\\"Delete\\\" class=\\\"delete\\\" aria-label=\\\"Delete xss.html\\\"></a>\",\"name\":\"xss.html\",\"mimetype\":\"text/html\",\"classname\":\"text html\",\"complete\":true},\"u128763271\");\nthis.auto_save_start(false);\n"}
att_id='11773063013009066400'
----------------------------------------------------------------------------------------------------
document.cookie='roundcube_sessid=[REDACTADO]; Path=/index.php/xss; Domain=.target.local'
document.cookie='roundcube_sessauth=[REDACTADO]; Path=/index.php/xss; Domain=.target.local'
location.href = 'http://mail.target.local/index.php/xss?_task=mail&_action=display-attachment&_id=183727919869aecb6499f76&_file=rcmfile11773063013009066400';
  1. Copiar el JavaScript que aparece bajo la línea de guiones, abrir http://xss.target.local y pegarlo en la consola de DevTools. En un ataque real ese código entraría por un XSS auténtico de la aplicación que corre en xss.target.local.
  2. Al pulsar Intro, el navegador es redirigido y aparece la alerta del XSS en el origen de Roundcube (mail.target.local): el fallo de otro origen se ha usado para ejecutar código en Roundcube.

Los dos puntos clave del payload son Domain=.target.local, que hace que el subdominio de Roundcube reciba las cookies del atacante, y Path=/index.php/xss, que limita esas cookies a la URL de la vista previa. Para el resto de rutas, incluida /, el navegador sigue mandando las cookies originales de la víctima, que conserva su sesión.

Impacto

Un atacante con cuenta en el mismo Roundcube que encuentre cualquier XSS en otro servicio del mismo sitio puede tomar la cuenta de otro usuario: leer todo su correo (y con ello hacerse con otras cuentas mediante restablecimientos de contraseña), enviar mensajes en su nombre y cambiar su configuración.

En la práctica, la seguridad de Roundcube pasa a depender de la de todas las demás aplicaciones alojadas bajo el mismo dominio.

Remediación

El investigador propuso aplicar a display-attachment las mismas reglas de CSP que ya usan los otros endpoints que muestran adjuntos, para que el HTML subido no pueda ejecutar JavaScript. El reporte figura como resuelto.

Qué aprender de este caso

  • Si una aplicación tiene varios caminos para mostrar un fichero (adjunto recibido, adjunto en borrador, vista previa), compara las cabeceras de cada uno: aquí get.php enviaba script-src 'none' y display_uploaded_file() no. El camino menos usado suele ser el que se queda sin protección.
  • Un XSS que parece limitado a la propia sesión no es inofensivo si las cookies de sesión pueden sobrescribirse desde un subdominio hermano. Revisa si las cookies llevan el prefijo __Host- y prueba a fijarlas con Domain= del dominio padre y un Path= más específico.
  • Para servir contenido subido por el usuario, la defensa robusta es no ejecutarlo en el origen de la aplicación: Content-Disposition: attachment, CSP sandbox o script-src 'none', o un dominio aparte solo para ficheros.
  • Alojar un webmail en el mismo dominio registrable que otros servicios hace que un XSS en cualquiera de ellos se convierta en un riesgo para el correo; usar cookies __Host- impide que un subdominio las sobrescriba.