DedeCMS恶意文件删除漏洞修补实战:47步排查+完整应急方案
本文目录
- 漏洞背景和危害评估
- 为什么这个文件会被反复盯上
- 漏洞CVE编号和披露时间线
- 漏洞危害的完整攻击链
- 漏洞验证和事故确认
- 确认DedeCMS版本
- 检查目标文件是否被改动过
- 翻查会员相关日志
- 临时止血
- 官方修复方案的代码层面解读
- 原始有问题的代码
- 社区标准补丁
- 加强版补丁(推荐)
- 保哥的实战修复流程
- 备份原文件
- 编辑文件
- 校验语法
- OPcache刷新
- 端到端验证
- 加固延伸:除了打补丁还应该做的事
- 关闭会员模块(如果业务用不到)
- 加上WAF规则
- 文件权限收紧
- 关键文件备份与恢复脚本
- 升级或迁移
- 入侵响应:万一已经被删了文件怎么办
- 应急止血
- 完整入侵排查
- 数据库审计
- 完整恢复
- 常见问题解答
- 补丁打完后会员上传缩略图功能正常吗?
- 这个漏洞可以远程利用吗?需要登录吗?
- 打了补丁还需要重新生成全站静态吗?
- 能不能把整段会员模块的代码替换成第三方安全版本?
- 除了archives_check_edit.php还有哪些任意文件删除漏洞?
- WAF规则会不会误伤正常用户?
- 修补完之后如何持续监测同类漏洞?
- 这种老旧CMS继续守值不值得?
保哥这几年帮客户处理DedeCMS(织梦)的安全事故太多了,光是任意文件删除这一类漏洞前前后后修过不下十几次。这次我把member/inc/archives_check_edit.php文件中那个老牌的高危漏洞从原理到补丁完整复盘一遍,方便还在用v5.7老版本的朋友照着自查。文章里所有路径、代码片段、命令行操作都是我自己在测试服务器上一行一行敲过的,不是网上抄来的,请放心参考。这个漏洞的可怕之处不在于"删个文件",而在于通过删除特定文件能直接拿到WEBSHELL——下面会讲完整的攻击链。
漏洞背景和危害评估
为什么这个文件会被反复盯上
DedeCMS v5.7已经停止主线维护很多年了,但国内站长圈里至少还有几十万站点跑着这个版本。会员模块(member目录)在历史上爆出的漏洞最多,原因是这一块的代码写得比较早,对参数过滤的思路停留在2010年前后——大量地方直接信任POST和GET传过来的字符串再丢到文件系统操作里。
archives_check_edit.php这个文件的作用是会员投稿后审核编辑的接口,里面有一段处理缩略图字段(litpic)的逻辑。当用户提交编辑请求时,如果选择了删除原图,程序会调用unlink函数直接删掉文件路径所指向的文件。问题就出在这里:
第一缩略图路径来自前端表单字段;第二程序只校验了是不是空、文件是否存在;第三没有校验路径是否仍然在该用户的私人目录范围内。
结果就是:任何一个注册会员都能构造一个三个点点斜杠include/common.inc.php这样的相对路径让服务器把核心配置文件、首页文件、甚至数据库连接文件一删了之。
漏洞CVE编号和披露时间线
这个漏洞在CNVD的编号是CNVD-2018-01221,CVE系列对应CVE-2018-9123。首次披露是2018年1月,POC在乌云镜像(已下线)和exploit-db上有公开记录。2018年4月织梦官方在论坛发了部分修补建议但没有合入主分支版本,导致后续下载的官方版本仍然存在漏洞。
2019到2022年间这个漏洞被广泛利用,是最常见的DedeCMS入侵手法之一。2023年之后随着WordPress和Typecho吃掉DedeCMS的市场份额,相关攻击有所减少但仍未消失。
漏洞危害的完整攻击链
很多人以为任意文件删除最坏情况就是删个文件,其实这只是攻击链的起点。完整的攻击链:
第一步:删除install目录的index.php.bak文件(或者其他能触发install逻辑的文件),让站点重新进入安装界面。第二步:在安装界面里填入攻击者控制的数据库地址,让站点连到攻击者的MySQL。第三步:安装过程中织梦会写入一些初始化文件,攻击者控制数据库内容可以诱导写入webshell。第四步:拿到shell后植入持久后门。
另一条更直接的攻击链:删除特定的认证文件让某些防御机制失效,然后利用其他漏洞(比如soft_add.php模板注入)直接拿shell。我去年帮一个客户处理过类似事件,攻击者直接把data/common.inc.php删了整站立刻502,而nginx日志里只能看到一条普通的POST请求迷惑性极强。
所以这个漏洞的实际危害评级是Critical(CVSS 3.1基础分9.8),不是"删个文件"那么简单。
漏洞验证和事故确认
在动手打补丁之前先确认一下版本和文件指纹。保哥的习惯是先做四步检查再决定怎么修。
确认DedeCMS版本
登录FTP或SSH进入网站根目录查看data/admin/ver.txt:
cat /www/wwwroot/example.com/data/admin/ver.txt
如果输出是20180109或更早的日期那就是受影响版本。官方在2018年之后也发过零星补丁但这个文件的修复始终没有合入主分支,很多人手里的最新版其实根本没修。也可以通过后台首页的版本信息确认,但有的攻击者会篡改后台显示的版本号迷惑管理员,文件层面的版本号最准。
检查目标文件是否被改动过
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开头(不同子版本会有差异),如果你本地算出来的值和你之前备份的不一致那有可能已经被人动过手脚,需要先做完整的入侵排查再谈修复。stat显示的modify时间如果跟其他系统文件不一致,特别是远在系统装机时间之后的修改,重点怀疑。
翻查会员相关日志
grep -r "archives_check_edit" /www/wwwlogs/example.com/ | tail -n 50
重点关注POST请求里出现litpic等于、oldlitpic等于字段且值里带两个点点或绝对路径的记录,那就是典型的尝试痕迹。如果发现大量来自同一IP的高频请求基本可以确认被定点攻击。
更精细的过滤:grep过滤出POST方法、URI包含archives_check_edit、且请求体包含点点(用URL编码%2e%2e或者裸点点),然后按IP聚合看分布。
临时止血
如果发现已经有恶意请求先把整个member目录禁掉(nginx加一条location段deny all),再静下心来打补丁。不要在受攻击的过程中边打补丁边开放服务,很多时候攻击者会留有第二个webshell。临时止血的另一个选项是直接给文件加上拒绝执行权限:chmod 000 archives_check_edit.php,这样PHP无法读取该文件,相应功能直接报错但其他功能不受影响。
官方修复方案的代码层面解读
原始有问题的代码
原代码:
$litpic = $oldlitpic;
这一行直接把表单提交的oldlitpic赋值给litpic后续用于unlink调用。完全没有任何校验。
社区标准补丁
社区给出的标准补丁很简单,只是在原本的litpic赋值这一行后面追加了一段路径校验:
$litpic = $oldlitpic;
if (strpos($litpic, '..') !== false
|| strpos($litpic, $cfg_user_dir."/{$userid}/") === false) {
exit('not allowed path!');
}
这段补丁做了两件事:
第一拒绝两个点点出现。直接堵掉相对路径回退的可能性。一旦字符串里包含两个点点立刻终止脚本。
第二强制校验前缀。cfg_user_dir是会员附件目录的根,比如/uploads/userup,再拼上斜杠userid斜杠就形成了当前会员的私人空间。如果传过来的路径里压根不包含这个前缀说明这个文件就不是当前会员的,更没资格删。
看似只有两行if实则把绝对路径绕过和相对路径绕过两条主要攻击链同时切断。需要说明的是这个补丁假设了cfg_user_dir和userid在到达这一行时已经被正确赋值——保哥审计过整个文件的执行路径确认在调用之前都做过session校验和配置加载,所以放心用。
加强版补丁(推荐)
标准补丁能挡掉大多数攻击但仍有边界场景。比如攻击者用URL编码绕过(%2e%2e代替两个点点)或者用UTF-8变体编码。加强版补丁:
$litpic = $oldlitpic;
// 先做URL解码再校验,挡掉编码绕过
$decoded = urldecode( $litpic );
// 标准化路径(消除中间的./和.)
$decoded = preg_replace( '#/+#', '/', $decoded );
// 多重检查
if ( strpos( $decoded, '..' ) !== false
|| strpos( $decoded, $cfg_user_dir . "/{$userid}/" ) === false
|| preg_match( '#[\\x00\\r\\n]#', $decoded ) // 空字节和换行符
|| preg_match( '#\\.(php|phtml|inc|cgi|pl|jsp)$#i', $decoded ) ) {
exit( 'not allowed path!' );
}
// 用realpath再次校验
$realPath = realpath( DEDEDATA . '/../' . $litpic );
$allowedBase = realpath( DEDEDATA . '/../' . $cfg_user_dir . "/{$userid}/" );
if ( $realPath === false || strpos( $realPath, $allowedBase ) !== 0 ) {
exit( 'not allowed path!' );
}
这个版本增加了:URL解码后再校验、空字节和换行符过滤(防止某些OS特性绕过)、禁止删除PHP和其他可执行扩展名的文件(即便在用户目录里)、用realpath做最终的物理路径校验。
保哥的实战修复流程
备份原文件
cd /www/wwwroot/example.com/member/inc/
cp archives_check_edit.php archives_check_edit.php.bak.20260507
备份文件名我习惯带上日期方便日后回滚或对照。注意备份文件不要以.bak结尾后直接放在公网可访问目录,建议挪到根目录之外或加上deny规则。否则攻击者可能通过guess URL直接下载备份文件查看修补前后的diff。
编辑文件
用vim或者宝塔面板的在线编辑器都行但要注意编码必须保持UTF-8无BOM否则织梦会出现乱码或500错误。
vim archives_check_edit.php
搜索关键字oldlitpic:在vim里输入斜杠oldlitpic然后回车定位到litpic等于oldlitpic这一行把第三节里的修补代码贴在它的下面。
校验语法
保存退出后先做语法检查再上线别让一个分号毁了整站:
php -l archives_check_edit.php
输出No syntax errors detected才算OK。如果有Parse error立即用备份恢复重新编辑。
OPcache刷新
如果服务器开了OPcache(PHP 7和8默认开),修改文件后OPcache可能还在用旧版本。重启PHP-FPM或者强制刷新OPcache:
service php-fpm reload
# 或者
php -r "if(function_exists('opcache_reset')) opcache_reset();"
端到端验证
保哥喜欢用最朴素的方式验证:注册一个测试会员登录后台投稿然后在浏览器开发者工具里手动改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!
URL编码绕过:uploads/userup/2/%2e%2e/%2e%2e/data/common.inc.php,期望也被拦截。
四种情况全部符合预期才能说明补丁真的生效了。
加固延伸:除了打补丁还应该做的事
打完这一处补丁不代表万事大吉。DedeCMS v5.7的会员模块里类似的字符串拼接加文件操作模式还有不少,保哥的建议是把以下几件事一起做完。
关闭会员模块(如果业务用不到)
后台系统-系统基本参数-会员设置里把会员功能关掉再在nginx配置里直接location段return 404,从入口就堵死比一行行打补丁高效得多。如果业务必须开会员功能至少把投稿、上传、修改等高危入口全部deny,只保留登录、注销、密码修改这几个基本入口。
加上WAF规则
在宝塔自带的免费WAF或ModSecurity里加一条规则:
SecRule ARGS:litpic|ARGS:oldlitpic "@rx \\.\\." \\
"id:1009001,phase:2,deny,status:403,msg:'DedeCMS path traversal',\\
tag:'attack-lfi',severity:'CRITICAL'"
SecRule ARGS:litpic|ARGS:oldlitpic "@rx %2e%2e" \\
"id:1009002,phase:2,deny,status:403,msg:'DedeCMS encoded traversal'"
SecRule ARGS:litpic|ARGS:oldlitpic "@rx \\.(php|phtml|inc)$" \\
"id:1009003,phase:2,deny,status:403,msg:'DedeCMS PHP file deletion'"
这三条规则会在请求进入PHP之前就把带两个点点、URL编码点点或者PHP文件名的危险参数拦截下来作为补丁失效时的兜底。
文件权限收紧
会员上传目录给755,PHP文件目录给644,所有者统一为www用户:
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
对核心文件(common.inc.php、config.inc.php等)可以更严格:
chmod 444 /www/wwwroot/example.com/data/common.inc.php
chattr +i /www/wwwroot/example.com/data/common.inc.php
chattr +i会让文件变成immutable状态,连root都无法直接删除(必须先chattr -i),是最强的防删除手段。但要记住自己加了之后,正常更新数据库连接配置的时候要先去掉immutable属性。
关键文件备份与恢复脚本
就算被删了文件,能秒级恢复也是好的。写一个简单的备份脚本:
#!/bin/bash
BACKUP_DIR=/root/dedecms_backup
mkdir -p $BACKUP_DIR
SOURCE=/www/wwwroot/example.com
KEY_FILES=("data/common.inc.php" "include/config_base.php" "include/common.inc.php" "index.php")
for f in "${KEY_FILES[@]}"; do
cp "$SOURCE/$f" "$BACKUP_DIR/$(echo $f | tr / _).bak"
done
配合cron每小时执行一次。一旦发现某关键文件被删立即从备份目录恢复。同时建议把备份目录从web root剥离,免得攻击者顺着备份策略反推。
升级或迁移
说句实话v5.7这种十年前架构的CMS靠打补丁是补不完的。保哥已经把手里几个还在用织梦的项目陆续迁到Typecho或者纯静态生成器了,迁移本身一两天就能搞定,长期来看比一直追着打补丁省心得多。具体迁移方法见保哥的"DedeCMS转Typecho迁移"完整指南。
入侵响应:万一已经被删了文件怎么办
应急止血
第一时间把网站设为维护模式:在nginx里加一个临时配置返回503维护页面,所有流量都看到维护页。这样既不让攻击者继续操作也不让搜索引擎抓到500错误页面影响SEO。
完整入侵排查
按时间线倒查最近30天的服务器日志:grep过滤Googlebot之外的所有POST请求按响应code 200成功的优先看;查crontab是否有陌生任务;查iptables是否有陌生规则;查/etc/passwd和/etc/shadow是否有陌生账号;查authorized_keys是否有陌生公钥。
find命令找出最近修改过的所有文件:find /www/wwwroot -mtime -30 -type f排除掉自己修改的剩下的全部列出来审查。
数据库审计
dede_admin表如果多了陌生admin账号立即删除并修改剩余账号密码;dede_member表如果有时间集中的大批量注册(攻击者用注册功能批量创建会员)查看注册IP分布;dede_archives表如果有时间集中的批量发布查看内容是否包含外链或SEO Spam。
完整恢复
从最近一次干净备份恢复全部文件加数据库,然后按本文方法重新加固。不建议只恢复被删的几个文件——攻击者很可能还留了后门,必须整站恢复。
常见问题解答
补丁打完后会员上传缩略图功能正常吗?
会的。这段补丁只对路径中包含两个点点或不在当前用户目录下的请求做拦截,正常的上传、编辑、替换缩略图操作都不会触发exit。如果你打完补丁出现无法保存,多半是cfg_user_dir配置不对或者文件编码改坏了。先回滚备份再排查具体原因。常见的错误:cfg_user_dir在不同DedeCMS版本里的默认值不一样有的是/uploads/userup有的是uploads/userup(没有前导斜杠),如果不一致前缀匹配会失败导致所有合法上传都被拦截。
这个漏洞可以远程利用吗?需要登录吗?
需要会员登录但DedeCMS的会员注册一般是开放的,注册加登录加触发漏洞整套动作可以脚本化完成,所以从攻击者视角来看接近无门槛。如果你的站点对外开放注册强烈建议立刻打补丁。如果暂时没法打补丁先在后台关闭注册功能也能临时缓解(但已注册的恶意账号仍能利用)。
打了补丁还需要重新生成全站静态吗?
不需要。这个文件属于会员模块的PHP后端逻辑不参与前端模板渲染也不影响已经生成的静态页面。保存上传加清一下OPcache就行:php执行opcache_reset函数即可。如果你的站点没开OPcache直接保存上传就好。
能不能把整段会员模块的代码替换成第三方安全版本?
社区里确实有人fork过加固版的DedeCMS比如DedeBIZ和DedeV6,但兼容性参差不齐。保哥的建议是:业务还在跑又不想大动的老老实实打补丁;准备重构的直接迁站不要在中间状态拖太久。第三方加固版的另一个问题是无法保证代码本身的可信度——如果加固版自身含有后门,相当于把锁换了把更糟的锁。
除了archives_check_edit.php还有哪些任意文件删除漏洞?
DedeCMS历史上的任意文件删除漏洞还有:member/album_edit.php(CVE-2018-7700)、member/uploads_edit.php(CVE-2018-9119)、include/dialog/select_images_post.php(删除图片接口)、plus/recommend.php(早期版本的删除推荐文件)。建议把这些文件都按本文方法审一遍。
WAF规则会不会误伤正常用户?
如果配置得当不会。本文的三条WAF规则只检查litpic和oldlitpic两个特定参数,对其他请求完全不影响。正常用户的litpic值永远是合法图片路径,不会包含两个点点或PHP扩展名。如果上线后发现误伤通常是WAF规则被配置成全局检查所有参数而不是限定到这两个字段,重新检查规则的scope即可。
修补完之后如何持续监测同类漏洞?
四个工具组合:Fail2Ban自动封禁高频访问/member/路径的IP;Wazuh或OSSEC做HIDS入侵检测,对核心文件做完整性监控;Filebeat加Elasticsearch做日志集中分析;定期跑OpenVAS或Nessus做漏洞扫描。预算紧张选Fail2Ban加md5sum脚本(前面提到的)够用,预算充裕上完整SOC方案。
这种老旧CMS继续守值不值得?
看业务情况。如果站点流量稳定、内容有沉淀、SEO权重高,迁移成本高于继续维护成本可以继续守但必须做完整加固。如果站点流量小、内容数量少、没有特别的SEO积累,直接迁到Typecho或WordPress省事得多。保哥的判断标准:年访问UV低于10万的小站建议直接迁移;年UV超过50万的站点评估迁移成本和持续维护成本再决定;UV在10万到50万之间的中间地带,看运维团队的技术能力和心理承受能力。
FAQPage + Article AI 引用友好版
DedeCMS v5.7的archives_check_edit.php任意文件删除漏洞CVE-2018-9123的完整修补方案:标准补丁加加强版补丁、四步验证流程、WAF规则、chattr加i不可删除属性、入侵响应SOP。
- 织梦漏洞
- 网站安全
- DedeCMS
- 任意文件删除
- 路径遍历
- 织梦CMS教程
title: DedeCMS恶意文件删除漏洞修补实战:47步排查+完整应急方案 author: 张文保 (Paul Zhang) — PatPat SEO 经理 url: https://zhangwenbao.com/dedecms-v5-7-registered-user-arbitrary-file-deletion-vulnerability-archives_check_edit-php-vulnerability-repair.html published: 2018-06-23 modified: 2026-05-16 source-type: First-hand expert commentary language: zh-CN license: CC BY-NC-SA 4.0 (要求保留原文链接与作者归属)
本文标题:《DedeCMS恶意文件删除漏洞修补实战:47步排查+完整应急方案》
版权声明:本文原创,转载请注明出处和链接。许可协议: CC BY-NC-SA 4.0