织梦后台 media_add.php 任意上传漏洞的修复与白名单加固

dedecms后台media_add.php存在文件任意上传漏洞,黑客可上传php等危险脚本文件。本文通过在第69行添加文件扩展名正则过滤代码,禁止上传php、asp、jsp等可执行文件类型。

张文保 更新 11 分钟阅读 1,357 阅读

保哥前阵子在帮一个做机械加工的客户清后门,溯源到最后又一次落到了织梦那个老朋友——dede/media_add.php。这是织梦后台软件附件管理的入口,问题已经被披露超过八年,至今还有大量遗留站点没修。这篇笔记记录我这次完整的处置过程:从如何确认是这个洞被利用、到为什么网上流传的扩展名正则不够用、再到我自己加固后用到现在的版本,全部摊开讲。

这个洞的本质是什么

dede/media_add.php 是织梦后台用来上传“软件”类型附件的脚本,它的设计假定凡是能进到这一步的用户都已经登录后台,所以对文件名校验非常宽松,第 69 行附近原始代码大概是:

$fullfilename = $cfg_basedir.$filename;

$filename 直接来自 POST 里的用户输入,没做任何扩展名检查就拼到服务器物理路径上,然后 move_uploaded_file 把临时文件搬过去。问题来了——只要有一个能进后台的账号,无论是默认弱口令、还是通过前面那个 inc_archives_functions.php cookie 泄漏拿到的越权 token,攻击者都可以传一个 shell.php,落地后直接通过 web 访问执行。

更阴险的是,这个洞经常和 CSRF 配合打。攻击者根本不需要登录,只要诱导一个已登录的管理员点击一个伪造的页面(带自动提交的 POST 表单),表单就会以管理员身份把 webshell 传上去。这种利用链在 2018-2021 年的针对性攻击里非常常见,保哥那几年至少处理过二十多起。

客户站点的攻击痕迹

这次客户站点的入侵指标(IOC)非常清晰:

  1. uploads/soft/ 下出现了 2024xxxx-shell.php,文件大小只有 800 字节左右,明显是一句话;
  2. nginx access.log 里有几十条 POST /dede/media_add.php 的记录,但 referer 字段是空的——正常后台操作 referer 必然带着 /dede/media_main.php
  3. 数据库 dede_uploads 表里多了几条 mediatype=3title 是乱码的记录;
  4. 上传时间窗口和后续 /uploads/soft/2024xxxx-shell.php?cmd=ls 的访问完全对得上。

保哥的处置顺序还是老规矩:先快照取证、再止血、最后修复并复盘。这次直接进到修复环节。

网上流传的修复版本以及它的不足

搜索引擎里能找到的标准修复方案是这样:

if (preg_match('#\.(php|pl|cgi|asp|aspx|jsp|php5|php4|php3|shtm|shtml)[^a-zA-Z0-9]+$#i', trim($filename))) {
    ShowMsg("你指定的文件名被系统禁止!", 'javascript:;');
    exit();
}
$fullfilename = $cfg_basedir.$filename;

这个修复的思路是黑名单——把已知危险扩展名列出来,命中就拒。能挡住绝大多数自动化扫描器,但保哥从实战角度有几个不放心:

第一,黑名单永远是漏的。这条正则没有覆盖 phtmlpharphtphp7phpshtml 配 SSI、htaccess 配 AddType 等等。攻击者只要换一个 PHP 解释器认得、但黑名单没列的扩展名,比如某些环境里把 .pht.phar 也交给 PHP 处理,整个防御就被绕过。

第二,正则末尾的 [^a-zA-Z0-9]+$ 看似严谨——意思是扩展名后面必须跟非字母数字才算命中——但这个写法对 shell.php. 这种 Windows 下会被自动去掉末尾点的文件名是有效的,对 shell.php%00.jpg 这种空字节截断也有效,但对 Apache 的 mod_mime 多扩展名解析(shell.php.x)反而失效,因为末尾不是危险扩展名结束。

第三,没有限制大小写绕过——其实正则带了 i 修饰符,这点 OK。但没有限制空格、Tab、换行这种文件名里塞奇怪字符的攻击。

保哥实际部署的加固版本

我给客户落地的版本采用白名单思路,因为软件附件这个场景本身就只应该允许压缩包和文档:

// dede/media_add.php 第 69 行附近
$_filename = trim($filename);
$_ext = strtolower(pathinfo($_filename, PATHINFO_EXTENSION));
$_allowed_exts = array('zip', 'rar', '7z', 'gz', 'tar', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx');

if (!in_array($_ext, $_allowed_exts, true)) {
    ShowMsg("附件类型不在允许列表内,已被系统拒绝!", 'javascript:;');
    exit();
}

// 二次防御:检查文件名是否包含点、空字节、控制字符
if (preg_match('#[\x00-\x1f]#', $_filename) || substr_count($_filename, '.') > 1) {
    ShowMsg("文件名包含非法字符或多重扩展名!", 'javascript:;');
    exit();
}

$fullfilename = $cfg_basedir.$_filename;

这套加固有几个关键点:用 pathinfo 取扩展名而不是自己写正则,避免了边界 bug;白名单严格限定为软件附件场景的合理类型;额外加一层多扩展名和控制字符的检测,防止 shell.zip.php 这种组合拳;最后对原始 $filename 也做了 trim,避免前后空格绕过。

Web 服务器层的纵深防御

光靠 PHP 代码层是不够的,保哥强烈建议在 web server 配置层再加一道。不管你的站跑在 nginx 还是 Apache,给上传目录禁用 PHP 执行是最简单粗暴也最有效的方案:

nginx 配置:

location ~* ^/uploads/.*\.(php|phtml|phar|pht|php5|php7|jsp|asp|aspx|cgi|pl|py)$ {
    deny all;
    return 403;
}

Apache 在 uploads 目录下放一个 .htaccess:

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

这层防御的好处是:哪怕 PHP 代码层因为新的绕过技巧失守,最后落地的 webshell 也根本无法被解析执行,攻击者拿到的只是一个静态文件。这是真正意义上的纵深防御。

收尾的几件事

修完这一个文件远远不是终点。保哥的常规收尾:

  • 后台 URL 改名。把 /dede/ 改成一段无规律字符串(同步改 data/common.inc.php 里的 cfg_admin_dir),让自动化扫描器找不到入口。这一步能把后台被尝试爆破的次数降到几乎为零。
  • 后台二次验证。给 /dede/ 整个目录加一层 nginx basic auth 或者 IP 白名单,运维同事固定 IP 访问。
  • uploads 目录权限。设为 755 即可,禁用 chmod 777 这种偷懒做法。
  • webshell 全量排查。除了 D 盾 之外,再用 find . -name '*.php' -mtime -90 -ls 列出最近三个月修改过的 PHP 文件人工核对,自动化工具有时候识别不了变形得很厉害的样本。
  • 日志归集。把 nginx 和 PHP 的日志往日志服务器或者 ELK 推一份,本地日志可能被攻击者擦除。

FAQ

Q1:我们的业务确实需要让用户传 PHP 类的源码包,怎么办?

保哥的建议是:让用户传 zip/tar.gz 格式,里面再装 PHP 文件。落地到服务器之后,源码包目录配合上面 nginx 那条规则,PHP 永远不会被执行——它只是一个二进制文件躺在那儿,要看内容只能下载。这样既满足业务又不破坏安全边界。

Q2:白名单里要不要加图片格式?

这个文件 media_add.php 本身是织梦定义的“软件附件”入口,图片应该走 media_add.php?mediatype=1 的图片分支或者专门的图集模块。混着让一个入口什么都收,反而增加攻击面。保哥的建议是不同 mediatype 走不同白名单,代码里用 switch 分一下就好。

Q3:改完之后后台上传一直失败,提示“附件类型不在允许列表内”,可文件明明是 zip 的?

保哥踩过这个坑。pathinfo 在某些 PHP 版本下对 .tar.gz 这种双扩展只会返回 gz,是 OK 的;但如果文件名带中文又是 GBK 编码的旧站点,pathinfo 在某些环境会取不到扩展名。解决办法是上传前用 mb_convert_encoding($_filename, 'UTF-8', 'GBK,UTF-8') 统一成 UTF-8 再处理。

Q4:还有哪些织梦后台脚本和这个洞类似,必须同步加固?

保哥的清单:dede/file_manage_control.php(文件管理任意操作)、dede/album_add.php(图集附件)、dede/soft_add.php(软件主体上传)、include/dialog/select_soft_post.php(编辑器附件选择)、include/uploadsafe.inc.php(上传公共入口)。这五个文件配合一起加固,整个上传通道才算闭环。

分享到
标签
版权声明

本文标题:《织梦后台 media_add.php 任意上传漏洞的修复与白名单加固》

本文链接:https://zhangwenbao.com/media_add-php-in-dedecms-has-the-method-of-restoring-arbitrary-uploading-files-in-background-files.html

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

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