直接过滤SVG标签中的事件处理器是防止XSS攻击的关键,许多开发者错误地认为仅过滤script标签或常见HTML事件如onclick就足够了,但SVG内联事件如onbegin、onload等同样危险,攻击者可利用<animate>、<set>等元素配合事件执行恶意代码。解决方案是采用白名单过滤策略,严格限制允许的SVG标签和属性,并彻底移除所有事件处理器,同时结合输出编码和内容安全策略(CSP)进行多层防御。

SVG事件处理器的独特风险与常见攻击向量

SVG作为XML格式,支持丰富的事件处理器,这些事件不仅包括常见的onload、onclick,还有SVG特有的onbegin、onend、onrepeat等。攻击者常将恶意代码嵌入事件属性中,例如:

<svg><animate onbegin="alert('xss')" attributeName="x" dur="1s"/></svg>

此类代码在解析时会直接执行。更隐蔽的攻击会利用<set>、<animateTransform>等元素,配合事件触发跨站脚本。此外,SVG允许内联JavaScript,如

<svg><script>alert('xss')</script></svg>

即使过滤了事件,未处理script标签仍会导致漏洞。数据URI和外部实体引用也可能引入恶意内容,如

<svg><image href="data:image/svg+xml,<svg onload='alert(1)'>"></svg>

这些向量使得传统HTML过滤库往往失效。

白名单过滤方案的具体实现步骤

首先,建立严格的SVG标签和属性白名单,例如允许的标签包括svg、g、path、circle等图形元素,但排除script、animate、set等高风险标签。属性白名单应只包含fill、stroke、d等安全属性,明确禁止所有on开头的事件处理器。实现时,可使用DOM解析器提取SVG内容,遍历所有节点和属性进行过滤。以下是Python中使用lxml库的示例:

from lxml import etree

def sanitize_svg(svg_content):
    allowed_tags = {'svg', 'path', 'circle', 'rect'}
    allowed_attrs = {'fill', 'stroke', 'd', 'cx', 'cy'}
    parser = etree.XMLParser(resolve_entities=False)
    tree = etree.fromstring(svg_content, parser)
    for element in tree.iter():
        if element.tag not in allowed_tags:
            element.getparent().remove(element)
        else:
            for attr in list(element.attrib):
                if attr.startswith('on') or attr not in allowed_attrs:
                    del element.attrib[attr]
    return etree.tostring(tree)

此代码移除了非白名单标签和所有事件属性。对于JavaScript内容,需额外过滤CDATA区块和内联脚本。注意:禁用实体解析可防止XXE攻击。

输出编码与CSP的协同防护

过滤后,输出编码能进一步降低风险。在Web页面中嵌入SVG时,应使用HTML实体编码对非安全字符进行处理,但需注意SVG与HTML编码的差异。例如,

<img src="data:image/svg+xml,%3Csvg%3E%3C/svg%3E">

通过URL编码可防止注入。同时,部署内容安全策略(CSP)是必备措施,设置如

Content-Security-Policy: default-src 'self'; script-src 'none'; object-src 'none';

可阻止内联脚本和外部资源加载。对于SVG,建议将CSP与sandbox属性结合,限制其执行能力。此外,服务器应设置正确的MIME类型(如image/svg+xml),避免浏览器错误解析。

自动化工具与手动审计的结合实践

尽管自动化工具如DOMPurify可过滤SVG,但其默认配置可能遗漏SVG特定事件。建议自定义配置,例如:

const cleanSVG = DOMPurify.sanitize(svgInput, {
    USE_PROFILES: { svg: true, svgFilters: true },
    FORBID_TAGS: ['script', 'animate', 'set'],
    FORBID_ATTR: ['on.*']
});

工具需配合正则表达式检查残留事件,如

/on\w+\s*=/gi

进行二次验证。手动审计则关注SVG结构复杂性,例如检查<handler>元素或XLink引用(如xlink:href),这些可能绕过过滤。定期更新白名单以应对新攻击向量(如SVG2新增事件)也至关重要。

行业案例分析:过滤失效的教训与优化

某社交平台曾因允许用户上传SVG头像而遭遇XSS攻击,攻击者使用

<svg><a xlink:href="javascript:alert('xss')"><text>点击</text></a></svg>

利用xlink:href执行脚本。平台最初仅过滤了on事件,忽略了XLink属性。修复方案是扩展白名单,禁止xlink:href和href中的javascript协议,并添加属性值验证。另一案例中,SVG视图(<view>)结合<use>元素触发了跨域脚本,这提示过滤需考虑元素关联性。行业最佳实践是采用零信任原则,默认禁止所有动态行为,仅开放必要功能。

总结:构建多层防御体系

单一过滤无法完全防御SVG中的XSS,必须结合结构化白名单、输出编码、CSP和沙箱机制。开发中应优先使用纯图形SVG库生成内容,避免用户输入原始SVG代码。对于必须处理动态SVG的场景,建议在服务端过滤后,前端通过<iframe sandbox>隔离渲染。定期安全测试(如模糊测试)和监控日志可及时发现漏洞。最终,安全是一个持续过程,随着SVG标准演进,防护策略也需同步更新,确保应用免受事件处理器滥用的威胁。