必须用 domdocument 显式禁用外部实体并清除高危 svg 元素、属性及脚本内容,辅以 mime 类型与文件头双重校验,否则仍存在 xxe 和 xss 风险。

用 DOMDocument 禁用外部实体后解析 SVG
直接用 simplexml_load_string 解析用户上传的 SVG 是危险的,它默认允许加载外部 DTD 和实体,可能触发 XXE。必须用 DOMDocument 并显式关闭这些功能。
关键操作是:创建 DOMDocument 实例后,立即调用 loadXML() 前设置两个安全开关:
-
libxml_disable_entity_loader(true)(PHP 8.0+ 已废弃,改用libxml_set_external_entity_loader(null)) -
$doc->loadXML($svg_content, LIBXML_NOENT | LIBXML_DTDLOAD | LIBXML_NONET)中去掉LIBXML_DTDLOAD,只保留LIBXML_NOENT | LIBXML_NONET
漏掉任一配置,都可能导致外部实体被解析或恶意 DTD 加载。
扫描并移除高危 SVG 元素和属性
即使 XML 解析安全了,SVG 内部仍可嵌入脚本执行能力。不能只靠黑名单过滤文件类型,必须检查内容本身。
需遍历所有节点,对以下项做清除处理:
- 元素名匹配
script、foreignObject、animate、set、handler的节点,直接DOMNode::removeChild() - 属性名以
on开头的(如onclick、onload),一律removeAttribute() -
xlink:href或href属性值以javascript:、data:、vbscript:开头的,清空或拒绝整文件 -
style属性中含expression(或url(且后面跟非白名单域名的,也需剔除
检查是否残留动态执行痕迹
攻击者会绕过静态标签过滤,把脚本逻辑藏在属性值或 CDATA 里。比如:<circle fill="url(#x)"><script>eval(atob('...'))</script></circle> 这类结构不会被简单标签扫描捕获。
建议补充两步校验:
- 提取所有文本节点和属性值,用正则扫描是否含
eval\(、assert\(、document\.write、innerHTML\s*=\s*等模式(注意加i修饰符) - 对
块内容单独提取并检查,它们常被忽略但能执行任意 JS - 若发现 base64 编码片段(如
base64_decode\(|atob\(|btoa\(),且上下文为属性值或事件 handler,基本可判定为混淆后门
别忘了 MIME 类型和文件头双重校验
仅靠扩展名或 $_FILES['type'] 判断 SVG 是无效的。攻击者可上传一个真实 PNG 文件,但内容末尾拼接 SVG 片段,或把 PHP 木马保存为 avatar.svg.php。
必须组合验证:
- 用
finfo_open(FILEINFO_MIME_TYPE)检查真实类型是否为image/svg+xml—— 注意,某些旧版 libmagic 会把含 script 的 SVG 误报为text/plain,所以不能单靠这个拒绝 - 同时读取文件前 1024 字节,确认是否以
<?xml或<svg> 开头(不区分大小写),且不含 PHP 开标签 <code><?php、 - 如果
finfo返回text/x-php或application/x-httpd-php,哪怕扩展名是 .svg,也应立刻拦截
真正难防的不是明面上的 <script></script>,而是用 Unicode 零宽字符分割关键词、用注释包裹 payload、或靠服务端解析器差异触发的间接执行 —— 这些必须靠沙箱运行+日志监控才能覆盖,纯 PHP 静态扫描总有盲区。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











