SVG上传功能在各类内容管理平台、社交网站、设计工具中十分常见,但是SVG文件本质是XML格式的文本文件,支持嵌入JavaScript脚本和XML外部实体声明,这就给XSS和XXE攻击留下了可乘之机,若没有做好安全防范措施,攻击者可以通过上传恶意SVG文件发起攻击,造成用户信息泄露、服务器文件被读取等严重后果。

SVG上传中XSS和XXE攻击的原理
XSS攻击原理
SVG规范允许在文件中嵌入<script>标签、事件属性(比如onload、onclick)以及javascript:协议的链接,当浏览器渲染这个SVG文件时,这些嵌入的脚本就会被执行。攻击者可以构造包含恶意脚本的SVG文件,上传后诱导其他用户访问该SVG资源,就能窃取用户的登录凭证、篡改页面内容或者发起钓鱼攻击。
比如下面是一段包含XSS payload的恶意SVG内容:
<svg xmlns="http://www.w3.org/2000/svg"> <script>alert(document.cookie)</script> </svg>
XXE攻击原理
SVG基于XML格式,若服务器在解析SVG文件时开启了外部实体解析功能,攻击者就可以在SVG中构造恶意外部实体声明,读取服务器本地的敏感文件,或者发起SSRF攻击访问内网服务。比如攻击者可以构造如下的SVG内容读取服务器的/etc/passwd文件:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE svg [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]> <svg xmlns="http://www.w3.org/2000/svg"> <text>&xxe;</text> </svg>
SVG上传安全漏洞的防范方案
1. 严格的文件校验
首先不要只依赖文件后缀名判断文件类型,需要结合文件的魔数(文件头特征)进行校验。SVG文件的开头通常是<?xml或者<svg开头,可以通过读取文件前几个字节判断是否符合SVG的特征。同时限制上传文件的大小,避免大文件占用服务器资源。
以下是一个简单的PHP文件类型校验示例:
function checkSvgFile($filePath) {
// 读取文件前100字节判断内容
$content = file_get_contents($filePath, false, null, 0, 100);
// 检查是否包含svg相关标识
if (strpos($content, '<svg') === false && strpos($content, '<?xml') === false) {
return false;
}
// 限制文件大小不超过2MB
if (filesize($filePath) > 2 * 1024 * 1024) {
return false;
}
return true;
}
2. 内容清洗过滤
对上传的SVG内容进行清洗,移除所有危险的标签和属性。需要过滤的标签包括<script>、<object>、<embed>、<iframe>等,过滤的属性包括所有以on开头的事件属性(onload、onclick等)、href属性中包含javascript:协议的内容、xlink:href中包含危险协议的内容。
可以使用成熟的安全库进行SVG清洗,比如PHP的enshrined/svg-sanitize库,以下是使用示例:
require_once 'vendor/autoload.php';
use enshrinedsvgSanitizeSanitizer;
$sanitizer = new Sanitizer();
// 读取上传的SVG内容
$svgContent = file_get_contents($_FILES['svg']['tmp_name']);
// 清洗SVG内容
$cleanSvg = $sanitizer->sanitize($svgContent);
if ($cleanSvg === false) {
// 清洗失败,文件不合法
echo '上传的SVG文件不合法';
} else {
// 保存清洗后的内容
file_put_contents('uploads/'.$_FILES['svg']['name'], $cleanSvg);
}
3. 禁用XML外部实体解析
在服务器端解析SVG文件时,一定要禁用XML外部实体解析功能,从根源上避免XXE攻击。不同语言的禁用方式如下:
- PHP:使用
libxml_disable_entity_loader(true)禁用外部实体加载,解析SVG前调用该函数。 - Java:使用
DocumentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false)和setFeature("http://xml.org/sax/features/external-parameter-entities", false)禁用外部实体。 - Python:使用
xml.etree.ElementTree解析时,避免使用fromstring解析不可信内容,或者使用defusedxml库替代原生XML解析库。
以下是PHP禁用外部实体解析的示例:
// 解析SVG前禁用外部实体 libxml_disable_entity_loader(true); // 加载SVG内容 $dom = new DOMDocument(); $dom->load($_FILES['svg']['tmp_name']); // 后续处理逻辑
4. 访问控制与隔离
上传的SVG文件不要直接存放在Web根目录下,最好放在独立的非Web可访问目录,通过专门的接口输出SVG内容,输出时设置正确的Content-Type为image/svg+xml,同时设置Content-Security-Policy响应头,限制SVG中脚本的执行。如果是用户上传的SVG,不要直接渲染,而是转换为静态图片(比如PNG)后再展示,彻底避免脚本执行的风险。
5. 服务器配置加固
在Nginx或Apache等Web服务器中,对SVG文件的访问做额外限制,比如禁止SVG文件中的脚本执行,设置X-Content-Type-Options: nosniff响应头,避免浏览器将SVG文件当做其他可执行类型解析。同时定期扫描上传目录,检测是否存在可疑的SVG文件。
总结
SVG上传的安全风险主要来自于其XML格式的特性,防范XSS和XXE攻击需要从文件校验、内容清洗、解析配置、访问控制多个层面共同发力,不能只做单一层面的防护。开发者在开发上传功能时,要充分意识到SVG的潜在安全风险,采用成熟的清洗库和正确的解析配置,才能有效避免安全漏洞的产生,保障业务和用户数据的安全。