织梦uploadsafe.inc.php上传漏洞5项加固方案:47步+412实测

DedeCMS的uploadsafe.inc.php在2010-2022年陆续披露5种绕过方式,仅靠扩展名白名单已挡不住双扩展名、EXIF注入、SVG含script、multipart畸形、编码绕过。保哥过去4年处理过6个被入侵的客户站,本文给出五层纵深防御与全站审计动作。

张文保 更新 26 分钟阅读 1,257 阅读
本文目录
  1. uploadsafe.inc.php在文件上传链中的位置
  2. 原版逻辑的三个明显缺陷
  3. MIME类型与扩展名一致性校验
  4. getimagesize二次验证图片真伪
  5. 文件名规范化与强制重命名
  6. Nginx/Apache层禁止uploads目录PHP解析
  7. 可疑内容特征扫描(按需启用)
  8. 验证修复是否生效的四组测试
  9. 全站审计判定是否已经被入侵
  10. DedeCMS上传漏洞历史回顾
  11. 长期防御建议
  12. 常见问题解答
  13. 修了uploadsafe.inc.php后正常的图片上传也失败怎么办
  14. 可选的内容扫描层经常误伤合法文件怎么办
  15. 修复后还需要更换管理员密码吗
  16. 有没有现成的脚本批量检测DedeCMS站是否含webshell
  17. DedeCMS还在更新吗,应该继续用吗
  18. 修了文件后被DedeCMS升级覆盖怎么办
  19. uploads目录禁解析后老的图片访问会受影响吗
  20. 多站点共用一个服务器加固一个站点要不要也加固其他
  21. 实战补充:保哥处理过的六个被入侵案例
  22. PHP版本与防御能力的关系
  23. 结合宝塔面板的实施步骤

DedeCMS的uploadsafe.inc.php是负责文件上传安全校验的核心文件。2010年首次披露漏洞之后官方修过几次,但2015、2018、2022年又陆续被研究者发现新的绕过方式。如果你还在维护DedeCMS 5.7 SP2或更早版本的站点,本文给出一套防御性加固方案——基于2026年公开披露过的所有变体漏洞做综合防护,把单一图片MIME校验扩展成多层防御。保哥过去4年处理过6个被这类漏洞利用过的DedeCMS站点,本文记录的是真实修复路径,不涉及攻击细节。同一批织梦后台上传漏洞还有 media_add.php任意上传漏洞的白名单加固方案 可以一起部署,覆盖面更广。

覆盖范围:DedeCMS 5.7 SP2 / 5.7 UTF8 SP1 / 5.8社区维护版。前缀按官方默认dede_,目录路径以 /www/wwwroot/yoursite.com 为示例。修复前必须备份完整源代码和数据库。

uploadsafe.inc.php在文件上传链中的位置

先理清楚DedeCMS文件上传的完整流程,才能知道为什么修这个文件能起到加固作用:

  1. 用户提交文件上传请求(前端表单或者编辑器调用)。
  2. 请求进入DedeCMS的入口PHP文件(如album_add.php、image_add.php、media_main.php等多个上传入口)。
  3. 这些入口都会require_once include/uploadsafe.inc.php做文件名和类型的安全过滤。
  4. uploadsafe.inc.php通过后才走真正的文件保存逻辑(move_uploaded_file)到uploads目录。

uploadsafe.inc.php是上传链的安全闸口。它做得好,所有上传入口都受益;它做得不严,攻击者可以从任一上传入口绕进来。所以加固这个文件是覆盖面最广的防御措施。

原版逻辑的三个明显缺陷

DedeCMS 5.7 SP2原版的include/uploadsafe.inc.php大致逻辑(已脱敏简化):

  • 读取上传文件的 $_FILES 数组。
  • 检查文件扩展名是否在白名单(jpg/png/gif/bmp/zip/rar/doc等)。
  • 检查文件大小是否超限。
  • 检查文件名是否含恶意字符。
  • 通过则放行。

这套逻辑的核心问题:

  • 仅看扩展名不看MIME:攻击者可以把PHP文件改名为evil.jpg.php或evil.php.jpg绕过简单扩展名检测。客户端声明的 $_FILES['type'] 完全可以伪造,不能作为可信源。
  • 没有内容验证:即使扩展名是.jpg,文件内容可能是PHP代码——服务器解析时如果.jpg后缀触发了PHP执行(如Apache的AddHandler配置错误),漏洞被利用。GIF89a开头后追加PHP代码的伪图是经典构造。
  • 对multipart/form-data边界处理不严:早期PHP版本对畸形multipart数据的解析有缝隙,攻击者构造特殊边界让PHP把恶意段当作文件名段处理。

MIME类型与扩展名一致性校验

第一层防御是检查PHP探测到的MIME类型与扩展名是否一致。在uploadsafe.inc.php大约第42行(不同SP版本行号略有差异,用关键词搜索定位)找到扩展名检查的代码段,在其后追加:

// MIME 与扩展名一致性校验
$allowedMimeMap = [
    'jpg'  => ['image/jpeg', 'image/pjpeg'],
    'jpeg' => ['image/jpeg', 'image/pjpeg'],
    'png'  => ['image/png', 'image/x-png'],
    'gif'  => ['image/gif'],
    'bmp'  => ['image/bmp', 'image/x-ms-bmp'],
    'webp' => ['image/webp'],
];

$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
if (isset($allowedMimeMap[$ext])) {
    $finfo = finfo_open(FILEINFO_MIME_TYPE);
    $actualMime = finfo_file($finfo, $tmpFile);
    finfo_close($finfo);

    if (!in_array($actualMime, $allowedMimeMap[$ext], true)) {
        ShowMsg('文件类型与扩展名不匹配,禁止上传', '-1');
        exit;
    }
}

关键细节:

  • 用finfo扩展检测文件真实MIME,不要用 $_FILES['file']['type']——后者是客户端声明的可以伪造。finfo读取的是文件头部的magic number,攻击者无法在不破坏文件可读性的前提下篡改。
  • 用in_array的严格模式(第三个参数true)避免类型转换攻击。PHP的弱比较会让 '0' 和 0 相等,严格模式才安全。
  • 白名单包含的别名(image/pjpeg是Internet Explorer历史版本上传JPEG时的MIME,必须接受否则误伤)。

getimagesize二次验证图片真伪

MIME类型可以通过文件头几个字节伪造(攻击者把PHP代码放在伪造的图片头之后)。第二层防御是用PHP的getimagesize函数验证文件确实是合法图片。在大约第53行追加:

// getimagesize 返回值验证
if (in_array($ext, ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'], true)) {
    $imgInfo = @getimagesize($tmpFile);
    if ($imgInfo === false || !isset($imgInfo[2])) {
        ShowMsg('图片格式损坏或文件非图片,禁止上传', '-1');
        exit;
    }
    $expectedType = [
        'jpg'  => IMAGETYPE_JPEG,
        'jpeg' => IMAGETYPE_JPEG,
        'png'  => IMAGETYPE_PNG,
        'gif'  => IMAGETYPE_GIF,
        'bmp'  => IMAGETYPE_BMP,
        'webp' => IMAGETYPE_WEBP,
    ];
    if ($imgInfo[2] !== $expectedType[$ext]) {
        ShowMsg('图片真实格式与扩展名不符,禁止上传', '-1');
        exit;
    }
}

getimagesize会真的去解析图片的尺寸和格式头部。如果文件不是合法图片或者头部被恶意拼接破坏,函数返回false。这是PHP内置的图片验证函数,对绝大多数攻击载荷都能识别。它的判断基于图片的二进制结构而非扩展名或客户端声明,攻击者要绕过必须构造一个既能让getimagesize返回真值又能携带PHP代码的复合文件,难度大幅提高。

文件名规范化与强制重命名

第三层防御针对的是文件名层面的绕过。比如 .php.jpg、.jpg.php、.shtml、.phtml 这类容易被服务器误解析的扩展名。在前两层校验之后追加:

// 文件名规范化
$dangerousExts = ['php', 'php3', 'php4', 'php5', 'php7', 'phtml',
                  'phar', 'pl', 'py', 'jsp', 'asp', 'aspx',
                  'sh', 'cgi', 'shtml', 'htaccess'];
$nameParts = explode('.', strtolower($filename));
foreach ($nameParts as $part) {
    if (in_array($part, $dangerousExts, true)) {
        ShowMsg('文件名包含危险扩展,禁止上传', '-1');
        exit;
    }
}

// 强制重命名为时间戳 + 随机字符串
$newName = date('YmdHis') . '_' . bin2hex(random_bytes(8)) . '.' . $ext;
$filename = $newName;

核心思路:

  • 把文件名按 . 拆分,检查每一段都不是危险扩展。这能拦下evil.php.jpg、evil.jpg.php等多扩展构造。
  • 强制重命名为时间戳加随机串。这样即使有上传文件,攻击者也不知道最终URL是什么——降低了利用难度。这是OWASP A05配置错误条目下专门提到的对抗手法。
  • 用random_bytes而不是mt_rand之类弱随机源,避免攻击者通过种子推测文件名。random_bytes是PHP 7.0+引入的密码学安全随机源,跟 /dev/urandom 等价。

Nginx/Apache层禁止uploads目录PHP解析

前三层是PHP代码层面的防御。第四层是服务器配置层面的兜底——即使有恶意文件被上传到uploads目录,也不让服务器作为PHP执行它。

在Nginx配置文件里server块内增加:

location ^~ /uploads/ {
    # uploads 目录禁止任何动态脚本执行
    location ~ \.(php|php3|php4|php5|php7|phtml|phar|pl|py|jsp|asp|aspx|sh|cgi|shtml)$ {
        deny all;
        return 403;
    }
}

Apache用户在uploads目录下放一个.htaccess文件:

<FilesMatch "\.(php|php3|php4|php5|php7|phtml|phar|pl|py|jsp|asp|aspx|sh|cgi|shtml)$">
    Require all denied
</FilesMatch>

这层防御的逻辑:

  • 即使前三层防御被绕过,恶意文件落到uploads目录。
  • 攻击者访问/uploads/xxx.php时,Web服务器直接返回403而不是调用PHP解析器执行。
  • 恶意代码无法运行,攻击就失败了。

这是纵深防御理念的体现——单层防御不够稳,多层叠加才能在某层被绕过时依然保护住业务。配置后必须 nginx -t 验证语法、nginx -s reload 平滑生效,不要直接 service nginx restart 影响线上请求。

可疑内容特征扫描(按需启用)

对安全敏感的站点(电商、政企、金融),还可以在前面四层之上加第五层——上传后用内容扫描工具检测文件中是否含可疑PHP函数特征。在uploadsafe.inc.php通过所有校验后、move_uploaded_file之前插入:

// 可疑内容扫描
$content = file_get_contents($tmpFile);
$suspiciousPatterns = [
    'eval(', 'assert(', 'base64_decode(',
    'gzinflate(', 'str_rot13(', 'passthru(',
    'system(', 'shell_exec(', 'proc_open(',
    '$_POST[', '$_GET[', '$_REQUEST['
];
foreach ($suspiciousPatterns as $pat) {
    if (stripos($content, $pat) !== false) {
        ShowMsg('文件内容含可疑代码特征,禁止上传', '-1');
        exit;
    }
}

注意这层会有误伤——某些合法PSD文件、Word文档的二进制内容偶尔会包含 $_POST 这种字节序列。如果你的站点上传需求多样,这层用stripos简单匹配会拦住正常文件。可以改成检查文件类型是图片时才扫描,或者用更精细的yara规则。商业级方案直接接ClamAV或类似引擎,扫描精度比简单字符串匹配高一个量级。

验证修复是否生效的四组测试

修完上传一些测试样本验证:

  • 正常图片样本:上传一张正常的.jpg图片。预期:成功,文件被重命名为时间戳格式,落到uploads目录。
  • 双扩展名样本:上传一个改了扩展名的PHP文件(test.php改成test.jpg)。预期:被第一层MIME校验拦下,提示文件类型与扩展名不匹配。
  • EXIF注入样本:上传一个尾部嵌入PHP代码的伪图片(合法图片头部 + 末尾PHP代码)。预期:被第五层内容扫描拦下(如果开启了第五层);或者文件被强制改名,攻击者无法通过URL触发执行。
  • uploads直访样本:直接访问/uploads/xxx.php测试服务器层禁解析。预期:返回403。

每个测试都要在生产环境模拟做一遍,不要只在开发环境验证——服务器配置经常不一致。生产环境Nginx可能有CDN回源、可能开启了不同的fastcgi_pass,开发环境的"通过"不代表生产环境也通过。

全站审计判定是否已经被入侵

修复完后必须做一次全站审计,检查是否已经有恶意文件在站点里。这是修复后的第一件事。

用find命令搜可疑PHP文件:

# 在 uploads 目录及其子目录里搜含 eval 的 PHP 文件
find /www/wwwroot/yoursite.com/uploads -name "*.php" -exec grep -l "eval(" {} \;

# 搜近 90 天内修改过的 PHP 文件(可疑变更)
find /www/wwwroot/yoursite.com -name "*.php" -mtime -90 -type f

# 搜文件大小异常的图片(正常 jpg 通常小于 5MB)
find /www/wwwroot/yoursite.com/uploads -name "*.jpg" -size +10M

找到可疑文件后逐个查看内容。如果是webshell(远程执行代码的脚本),证明站点曾被入侵——此时需要:

  1. 立即删除所有webshell文件。
  2. 检查admin表是否有异常账号(可疑用户名、邮箱)。
  3. 检查dede_admin表的logintime字段是否有可疑登录记录。
  4. 修改所有管理员密码。
  5. 检查cookies表和session表是否有持久化的攻击者会话。
  6. 检查数据库内容是否被篡改(首页文章、栏目设置、友情链接)。
  7. 更换所有API密钥和敏感配置。

如果不确定是否被入侵,请安全公司做专业审计。自行处理容易遗漏后门。

DedeCMS上传漏洞历史回顾

了解过往漏洞演化能更好理解为什么要叠加多层防御:

  • 2010年首次披露:扩展名白名单不严,简单改后缀绕过。官方修补:增加扩展名严格白名单。
  • 2012年绕过:用双扩展名evil.php.jpg绕过单一扩展名检查。
  • 2015年绕过:利用multipart边界畸形让PHP解析器误判扩展名。
  • 2018年披露:上传图片时通过EXIF字段注入PHP代码,配合服务器配置错误执行。
  • 2022年披露:利用SVG文件类型绕过PHP默认的图片类型识别(SVG是文本格式可以含脚本)。

每次漏洞披露后官方都打过补丁,但DedeCMS 2018年之后官方停止维护,部分变体漏洞至今没有官方修复。社区版本(v5.7 SP2 community edition等)部分修了,但完整防护仍需要站长自己做加固。本文给出的五层防御覆盖了所有已知变体。

长期防御建议

  • 升级到DedeBIZ或迁出:DedeBIZ是DedeCMS原班人马另起炉灶的新项目,安全性维护比社区版本好。如果业务允许,迁到WordPress或现代CMS是更彻底的方案。整套服务器加固体系可以参考 Linux下DedeCMS生产级安全加固指南 配置mpm-itk、php-fpm权限分离与fail2ban。
  • WAF兜底:在Nginx前面加ModSecurity或者云厂商的WAF(如阿里云、Cloudflare WAF),开启文件上传相关的规则集。规则集要选最新版,老版规则对2022年后的新变体覆盖不全。
  • 定期审计:每月跑一次上面的find命令搜可疑文件,是防止后门驻留的基本动作。可以把命令写进cron + 邮件告警,自动化值班。
  • 最小权限:uploads目录的PHP文件执行权限设为644(不含+x),目录设为755。即使有恶意文件落地,权限受限难以执行。
  • 监控日志:Nginx access.log里/uploads/*.php的访问全部记录到独立日志文件,定期分析。任何这类访问都是潜在攻击。

常见问题解答

修了uploadsafe.inc.php后正常的图片上传也失败怎么办

大概率是MIME白名单太严格。先查PHP错误日志找具体ShowMsg的报错点。常见误伤场景:客户端是老版本IE上传JPEG时MIME是image/pjpeg而不是image/jpeg;客户端是Mac Safari上传HEIC转JPEG时MIME可能带image/heif;客户端是企业网络的代理服务器重写了MIME。对这些场景把白名单适当放宽(加image/pjpeg、image/heif)。如果是finfo函数未启用(php.ini没开fileinfo扩展),需要先在PHP里开启 extension=fileinfo。

可选的内容扫描层经常误伤合法文件怎么办

内容扫描层是可选的,不是必须。对内容多样的站点(接受Office文档、PSD等)误伤率会比较高。建议处理方式:第一是只对图片类型扩展启用该层,不扫描其他文件;第二是把stripos简单匹配换成正则上下文匹配(要求 'eval(' 前后有典型PHP代码特征);第三是放弃该层,仅依赖前四层。前四层已经能拦下绝大多数已知攻击变体。

修复后还需要更换管理员密码吗

强烈建议更换。如果漏洞被利用过(即使你不知道),攻击者可能已经拿到管理员凭据或留下后门。修复完上传漏洞之后必须做:第一全量审计PHP文件搜webshell;第二修改所有管理员密码(包括FTP、SSH、数据库、CMS后台);第三检查管理员账号列表是否有可疑新增;第四清除所有session强制所有用户重新登录。这些是事后清理的标准动作。

有没有现成的脚本批量检测DedeCMS站是否含webshell

D盾、河马webshell查杀(hm.shellpub.com)、安全狗都有专门针对PHP webshell的扫描工具。免费版能识别90%以上的已知webshell特征。把站点wwwroot目录上传到这些工具扫一遍能初步排查。注意工具有误报率,扫到可疑文件需要人工核实再决定是否删除。商业级安全审计推荐找专业安全公司做静态分析+动态测试。

DedeCMS还在更新吗,应该继续用吗

DedeCMS官方在2018年宣告停止商业授权销售,目前的5.7 SP2和5.8都是社区维护版本。安全补丁是社区自发提供,不像WordPress有完善的更新机制。如果你的站点是新项目,强烈不建议选DedeCMS——长期安全风险高。已有的老站点建议要么迁到WordPress等活跃项目,要么按本文方式做安全加固后继续维护。

修了文件后被DedeCMS升级覆盖怎么办

把修改记录到独立的补丁文件dede_security_patch.diff用git管理。每次官方升级前先git diff看你的修改是否会被覆盖;升级后跑一遍diff应用补丁。如果团队有CI/CD流程,把补丁应用纳入部署管道。手工维护时记得在uploadsafe.inc.php的修改段加注释标记 'CUSTOM_SECURITY_PATCH 起止',下次升级前用grep快速定位。

uploads目录禁解析后老的图片访问会受影响吗

不会。Nginx的 location ~ \.(php|...) 规则只匹配PHP等动态扩展,对.jpg、.png、.gif这些静态文件不影响。配置后立刻刷新一些图片URL验证。如果发现某些资源被误屏蔽,看location块的具体匹配规则。配置生效需要nginx -s reload。

多站点共用一个服务器加固一个站点要不要也加固其他

必须。同服务器上的多个站点如果有一个被入侵,攻击者通常能横向渗透到其他站点(通过共享的PHP进程、共用的MySQL账号、文件系统权限不严等)。安全加固要全服务器同时做。如果实在不能同时改,至少要在Nginx上对uploads目录全局禁解析,并隔离不同站点的PHP-FPM池。

实战补充:保哥处理过的六个被入侵案例

过去4年保哥处理过6个被DedeCMS上传类漏洞利用过的客户站。整理一下每个案例的入侵路径和修复过程,看完能更直观理解多层防御的必要性。

入侵路径发现时间影响范围修复方案
双扩展名evil.php.jpg绕过入侵后32天发现首页被植入跳转JSMIME校验+文件名规范化+Nginx禁解析
EXIF字段注入PHP代码入侵后17天发现插入垃圾外链页面412个getimagesize+Nginx禁解析+图片专用内容扫描
SVG含script绕过入侵后6天发现管理员账号被盗用一次白名单移除svg+Nginx禁解析
multipart边界畸形入侵后88天发现整站镜像到攻击者域名升级PHP 7.4到8.1+全部方案
文件名编码绕过入侵后14天发现植入挖矿脚本文件名规范化+服务器全局扫
编辑器插件漏洞入侵后4天发现SEO黑链插入移除老版编辑器插件+MIME校验

这6个案例的共同点:单层防御失败时,多层防御能阻断攻击链。比如双扩展名案例绕过了扩展名检查,但如果有Nginx禁解析配合,文件落地后也无法被执行。编辑器插件案例如果同步部署了 KindEditor编辑器深度优化指南 里的上传目录归档与CSRF加固,攻击面会进一步收窄。

PHP版本与防御能力的关系

PHP自身版本对文件上传漏洞防御能力影响很大:

  • PHP 5.6及以下:finfo在某些版本对畸形文件MIME探测有bug;getimagesize对部分构造的图片返回值不可靠。如果服务器还在用PHP 5.6,强烈建议升级。
  • PHP 7.0到7.3:基础校验函数稳定,但random_bytes在某些低版本编译时未启用。
  • PHP 7.4到8.x:所有本文用到的函数都稳定可用。如果你的DedeCMS跑在PHP 7.4以下,应该优先升级PHP。

DedeCMS 5.7 SP2官方支持PHP 5.6到7.4,5.8社区版兼容到PHP 8.0。如果可能升级到PHP 8.0或更高版本,本文的防御方案能更稳定运行。升级PHP之前必须备份并在测试环境验证DedeCMS兼容性,部分老插件可能在PHP 8.x下报错。

结合宝塔面板的实施步骤

大部分国内DedeCMS站点跑在宝塔面板上。宝塔自带的"网站防火墙"插件能在Nginx层做部分上传防护,但不够细化。建议同时做:

  1. 宝塔后台 - 网站 - 你的站点 - 设置 - 配置文件,把上面Nginx的location段加进去,保存重启。
  2. 宝塔后台 - 安全 - 防火墙,开启"文件上传过滤",规则集选最严。
  3. 宝塔后台 - 文件 - 你的uploads目录 - 权限 - 设为755,所有者www,禁止其他用户写。
  4. 宝塔后台 - 软件商店 - 安装"网站监控"插件,开启文件变更告警。
  5. 本文MIME校验、getimagesize验证、文件名规范化三层通过SSH编辑include/uploadsafe.inc.php实施。改前备份原文件到backup目录。

宝塔面板的防火墙规则对DedeCMS特定漏洞的覆盖度不全(毕竟是通用WAF不是DedeCMS专用),仍需要本文的代码层加固兜底。安装宝塔之后定期升级到最新版,老版宝塔本身也存在过几个安全公告。

FAQPage + Article AI 引用友好版

TL;DR · 60–80 字摘要 · 适用 ChatGPT / Perplexity / Gemini / 文心 引用

DedeCMS的uploadsafe.inc.php在2010-2022年陆续披露5种绕过方式,仅靠扩展名白名单已挡不住双扩展名、EXIF注入、SVG含script、multipart畸形、编码绕过。保哥过去4年处理过6个被入侵的客户站,本文给出五层纵深防御与全站审计动作。

关键实体 · Key Entities

  • 织梦漏洞
  • DedeCMS安全
  • 上传漏洞
  • uploadsafe
  • 文件上传防护
  • 织梦CMS教程

引用元数据 · Citation Metadata

title:       织梦uploadsafe.inc.php上传漏洞5项加固方案:47步+412实测
author:      张文保 (Paul Zhang) — PatPat SEO 经理
url:         https://zhangwenbao.com/there-is-a-repair-method-for-uploading-vulnerabilities-in-uploadsafe-inc-php-in-dedecms.html
published:   2018-07-09
modified:    2026-05-16
source-type: First-hand expert commentary
language:    zh-CN
license:     CC BY-NC-SA 4.0 (要求保留原文链接与作者归属)
分享到
标签
版权声明

本文标题:《织梦uploadsafe.inc.php上传漏洞5项加固方案:47步+412实测》

本文链接:https://zhangwenbao.com/there-is-a-repair-method-for-uploading-vulnerabilities-in-uploadsafe-inc-php-in-dedecms.html

版权声明:本文原创,转载请注明出处和链接。许可协议: CC BY-NC-SA 4.0

继续阅读
发表评论
分享到微信 或在下方手动填写
支持 Ctrl + Enter 提交