DedeCMS v5.7 注册用户任意文件删除漏洞修复实战记录

DedeCMS v5.7的/member/inc/archives_check_edit.php存在注册用户任意文件删除漏洞,会员可借此删除任意网站文件。修复方法是定位$litpic赋值后增加strpos路径校验,禁止包含..跨目录跳转和非用户目录路径,拦截恶意文件删除请求。

张文保 更新 12 分钟阅读 2,183 阅读

保哥这几年帮客户处理 DedeCMS(织梦)的安全事故太多了,光是任意文件删除这一类漏洞,前前后后修过不下十几次。这次我把 /member/inc/archives_check_edit.php 文件中那个老牌的高危漏洞从原理到补丁完整复盘一遍,方便还在用 v5.7 老版本的朋友照着自查。文章里所有路径、代码片段、命令行操作都是我自己在测试服务器上一行一行敲过的,不是网上抄来的,请放心参考。

一、漏洞背景:为什么这个文件会被反复盯上

DedeCMS v5.7 已经停止主线维护很多年了,但国内站长圈里至少还有几十万站点跑着这个版本。会员模块(/member/)在历史上爆出的漏洞最多,原因是这一块的代码写得比较早,对参数过滤的思路停留在 2010 年前后——大量地方直接信任 $_POST$_GET 传过来的字符串,再丢到文件系统操作里。

archives_check_edit.php 这个文件的作用是会员投稿后审核编辑的接口,里面有一段处理缩略图字段(litpic)的逻辑。当用户提交编辑请求时,如果选择了「删除原图」,程序会调用 unlink() 直接删掉文件路径所指向的文件。问题就出在这里:

  • 缩略图路径来自前端表单字段;
  • 程序只校验了「是不是空」「文件是否存在」;
  • 没有校验路径是否仍然在该用户的私人目录范围内。

结果就是:任何一个注册会员都能构造一个 ../../../include/common.inc.php 这样的相对路径,让服务器把核心配置文件、首页文件、甚至数据库连接文件一删了之。我去年帮一个客户处理过类似事件,攻击者直接把 data/common.inc.php 删了,整站立刻 502,而 nginx 日志里只能看到一条普通的 POST 请求,迷惑性极强。

二、漏洞验证:怎么确认自己的站点中招

在动手打补丁之前,先确认一下版本和文件指纹。保哥的习惯是先做四步检查,再决定怎么修。

2.1 确认 DedeCMS 版本

登录 FTP 或 SSH 进入网站根目录,查看 data/admin/ver.txt

cat /www/wwwroot/example.com/data/admin/ver.txt

如果输出是 20180109 或更早的日期,那就是受影响版本。官方在 2018 年之后也发过零星补丁,但这个文件的修复始终没有合入主分支,很多人手里的「最新版」其实根本没修。

2.2 检查目标文件是否被改动过

stat /www/wwwroot/example.com/member/inc/archives_check_edit.php
md5sum /www/wwwroot/example.com/member/inc/archives_check_edit.php

保哥实测过原版 v5.7 SP2 的这个文件 MD5 是 3a4e1b...(不同子版本会有差异),如果你本地算出来的值和你之前备份的不一致,那有可能已经被人动过手脚,需要先做完整的入侵排查再谈修复。

2.3 翻查会员相关日志

grep -r "archives_check_edit" /www/wwwlogs/example.com/ | tail -n 50

重点关注 POST 请求里出现 litpic=oldlitpic= 字段且值里带 .. 或绝对路径的记录,那就是典型的尝试痕迹。

2.4 临时止血

如果发现已经有恶意请求,先把整个 /member/ 目录禁掉(nginx 加一条 location /member/ { return 403; }),再静下心来打补丁。不要在受攻击的过程中边打补丁边开放服务,很多时候攻击者会留有第二个 webshell。

三、官方修复方案的代码层面解读

社区给出的标准补丁很简单,只是在原本的 $litpic = $oldlitpic; 这一行后面追加了一段路径校验。原代码:

$litpic = $oldlitpic;

修补后的代码:

$litpic = $oldlitpic;
if (strpos($litpic, '..') !== false
    || strpos($litpic, $cfg_user_dir."/{$userid}/") === false) {
    exit('not allowed path!');
}

这段补丁做了两件事:

  1. 拒绝 .. 出现:直接堵掉相对路径回退的可能性。一旦字符串里包含 ..,立刻终止脚本。
  2. 强制校验前缀$cfg_user_dir 是会员附件目录的根,比如 /uploads/userup,再拼上 /{$userid}/ 就形成了「当前会员的私人空间」。如果传过来的路径里压根不包含这个前缀,说明这个文件就不是当前会员的,更没资格删。

看似只有两行 if,实则把绝对路径绕过和相对路径绕过两条主要攻击链同时切断。需要说明的是,这个补丁假设了 $cfg_user_dir$userid 在到达这一行时已经被正确赋值——保哥审计过整个文件的执行路径,确认在调用之前都做过 session 校验和配置加载,所以放心用。

四、保哥的实战修复流程

下面是我自己在客户站点上反复跑过的一套流程,按顺序执行基本不会翻车。

4.1 备份原文件

cd /www/wwwroot/example.com/member/inc/
cp archives_check_edit.php archives_check_edit.php.bak.20260507

备份文件名我习惯带上日期,方便日后回滚或对照。注意备份文件不要以 .bak 结尾后直接放在公网可访问目录,建议挪到根目录之外或加上 deny 规则。

4.2 编辑文件

vim 或者宝塔面板的在线编辑器都行,但要注意编码必须保持 UTF-8 无 BOM,否则织梦会出现乱码或 500 错误。

vim archives_check_edit.php

搜索关键字 oldlitpic:在 vim 里输入 /oldlitpic 然后回车,定位到 $litpic = $oldlitpic; 这一行,把第三节里的修补代码贴在它的下面。

4.3 校验语法

保存退出后,先做语法检查再上线,别让一个分号毁了整站:

php -l archives_check_edit.php

输出 No syntax errors detected 才算 OK。

4.4 端到端验证

保哥喜欢用最朴素的方式验证:注册一个测试会员,登录后台投稿,然后在浏览器开发者工具里手动改 oldlitpic 字段,分别测试以下三个 payload:

  • 正常路径:/uploads/userup/2/img.jpg,期望执行成功;
  • .. 的路径:/uploads/userup/2/../../../data/common.inc.php,期望返回 not allowed path!
  • 越权访问别的会员:/uploads/userup/3/img.jpg,期望返回 not allowed path!

三种情况全部符合预期,才能说明补丁真的生效了。

五、加固延伸:除了打补丁还应该做的事

打完这一处补丁不代表万事大吉。DedeCMS v5.7 的会员模块里类似的「字符串拼接 + 文件操作」模式还有不少,保哥的建议是把以下几件事一起做完:

5.1 关闭会员模块(如果业务用不到)

后台「系统 → 系统基本参数 → 会员设置」里把会员功能关掉,再在 nginx 配置里直接 location ^~ /member/ { return 404; },从入口就堵死,比一行行打补丁高效得多。

5.2 加上 WAF 规则

在宝塔自带的免费 WAF 或 ModSecurity 里加一条规则:

SecRule ARGS:litpic|ARGS:oldlitpic "@rx \.\." \
    "id:1009001,phase:2,deny,status:403,msg:'DedeCMS path traversal'"

这条规则会在请求进入 PHP 之前就把带 .. 的危险参数拦截下来,作为补丁失效时的兜底。

5.3 文件权限收紧

会员上传目录给 755,写死所有者为 www,PHP 文件目录给 644:

find /www/wwwroot/example.com -type d -exec chmod 755 {} \;
find /www/wwwroot/example.com -type f -exec chmod 644 {} \;
chown -R www:www /www/wwwroot/example.com

这样即使再爆出新的任意文件删除漏洞,攻击者拿到的也只是「读」权限,删不动核心文件。

5.4 升级或迁移

说句实话,v5.7 这种十年前架构的 CMS,靠打补丁是补不完的。保哥已经把手里几个还在用织梦的项目陆续迁到 Typecho 或者纯静态生成器了,迁移本身一两天就能搞定,长期来看比一直追着打补丁省心得多。

六、常见问题答疑

Q1:补丁打完后会员上传缩略图功能正常吗?

会的。这段补丁只对路径中包含 .. 或不在当前用户目录下的请求做拦截,正常的上传、编辑、替换缩略图操作都不会触发 exit。如果你打完补丁出现「无法保存」,多半是 $cfg_user_dir 配置不对或者文件编码改坏了,先回滚备份再排查。

Q2:这个漏洞可以远程利用吗?需要登录吗?

需要会员登录,但 DedeCMS 的会员注册一般是开放的,注册 + 登录 + 触发漏洞整套动作可以脚本化完成,所以从攻击者视角来看接近「无门槛」。如果你的站点对外开放注册,强烈建议立刻打补丁。

Q3:打了补丁还需要重新生成全站静态吗?

不需要。这个文件属于会员模块的 PHP 后端逻辑,不参与前端模板渲染,也不影响已经生成的静态页面。保存上传 + 清一下 OPcache(如果开了)就行:

php -r "opcache_reset();"

Q4:能不能把整段会员模块的代码替换成第三方安全版本?

社区里确实有人 fork 过加固版的 DedeCMS(比如 DedeBIZ、DedeV6),但兼容性参差不齐。保哥的建议是:业务还在跑、又不想大动的,老老实实打补丁;准备重构的,直接迁站,不要在中间状态拖太久。

七、总结

archives_check_edit.php 这个洞修起来其实很简单,真正难的是:怎么发现自己中招、怎么验证补丁有效、怎么避免下次再栽在同一类漏洞上。保哥这篇笔记尽量把每一步的命令、代码、验证方法都写实,希望能帮还在维护织梦老站的朋友少走点弯路。如果你按这套流程跑下来仍然有疑问,可以在评论区留言,看到都会回。

分享到
标签
版权声明

本文标题:《DedeCMS v5.7 注册用户任意文件删除漏洞修复实战记录》

本文链接:https://zhangwenbao.com/dedecms-v5-7-registered-user-arbitrary-file-deletion-vulnerability-archives_check_edit-php-vulnerability-repair.html

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

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