保哥这几年帮朋友处理过三十多起DedeCMS老站被挂马、被植入暗链的事故,其中和member/soft_add.php相关的模板注入和SQL注入是出现频率极高的一类——粗略统计占DedeCMS事故的28%。织梦从2018年之后官方维护就基本停滞,2021年最后一个正式补丁发布之后五年内陆续披露的47个高危漏洞都没有官方修复,很多站点还跑在那一版上,漏洞被反复利用。今天这篇文章把这个漏洞的成因、利用思路、修补代码、加固方案以及事后审计要做的事,按真实排查顺序完整写下来。这篇内容偏运维向,不涉及任何攻击实操细节,所有代码都是修复用途。看这篇之前默认你已经能SSH登录服务器、能改PHP文件、看得懂基本的正则。
漏洞背景与影响范围
soft_add.php是干什么的
DedeCMS的member/soft_add.php是会员中心里的软件发布入口。注册会员可以通过这个接口提交软件信息,包括下载链接、服务器消息、说明文字等等。这个接口设计的本意是方便软件资源类站点接受用户投稿,但代码层做的过滤很弱,在多个版本里都有问题。
核心问题在于参数servermsg1。这个参数最后会被拼成DedeTag模板片段写到生成的栏目链接里,用花括号dede:link花括号包裹。如果不对内容做严格过滤,攻击者可以构造一个提前闭合标签的字符串再开新的dede:标签,相当于把任意织梦模板代码注入到模板编译流程里。织梦模板支持PHP标签和系统函数调用,模板注入直接等于GETSHELL,危害极高。
漏洞披露时间线
这个漏洞家族的披露记录很长:2018年4月乌云镜像首次公开POC(已下线);2018年12月CNVD-2018-26535正式收录;2019年3月互联网爆发大规模利用,多家安全厂商发出预警;2021年织梦最后一次官方更新只修了其中部分变体,整个漏洞家族其实没有完整修复;2023年和2024年仍有新变体被披露,主要利用思路是绕过最后一次官方补丁的过滤逻辑。
受影响版本
DedeCMS 5.6 SP2及之前:原始漏洞所有版本通用;DedeCMS 5.7 GBK所有版本:受影响;DedeCMS 5.7 UTF8到5.7.106:受影响(5.7.106有部分补丁但仍存在变体绕过);最后官方版本5.7.110也仍存在已知变体。
简单判定标准:你的DedeCMS如果是2024年之后没有打过自定义补丁的版本,假设受影响。
定位有问题的代码行
先用SSH连到服务器进到站点根目录下的member文件夹。漏洞代码在soft_add.php里,不同版本行号略有差别,5.7 SP2版本大约在171行,5.7 UTF8版本可能在168行附近。直接搜一下定位最稳:
cd /www/wwwroot/yoursite/member
grep -n "dede:link" soft_add.php
你会看到类似这样一行:
$urls .= "{dede:link islocal='1' text='{$servermsg1}'} $softurl1 {/dede:link}\r\n";
问题就在这里。servermsg1直接拼接进字符串,没有任何转义、没有任何过滤。同时建议grep其他几个关联文件:
grep -rn "servermsg1\|servermsg2\|softurl1\|softurl2" member/ include/
把所有使用这些参数的位置一并审一遍。常见有问题的还有inc/inc_archives_functions.php、article_add.php、edit_softurl_action.php。
三层防御的修补方案
最小修补:黑名单兜底
社区里流传最广的一个改法是在拼接前先用正则判断,如果发现内容里出现了能闭合dede:link再重新开dede标签的形态直接跳过拼接:
if ( preg_match( "#}(.*?){/dede:link}{dede:#sim", $servermsg1 ) != 1 ) {
$urls .= "{dede:link islocal='1' text='{$servermsg1}'} $softurl1 {/dede:link}\r\n";
}
这个改法保哥实测有效能挡掉绝大多数公开POC,但它属于黑名单防御,思路是不对劲就不执行,攻击者只要绕过这个正则就还是能打进来。所以这是底线方案不是最佳方案。
推荐方案:白名单加转义双保险
保哥更推荐的做法是:第一步把servermsg1强制限制成无害字符集,第二步对花括号、单双引号做实体化处理,让它即便被写进模板片段也变成字面字符:
// 强制类型 加长度限制
$servermsg1 = isset( $servermsg1 ) ? (string) $servermsg1 : '';
$servermsg1 = mb_substr( $servermsg1, 0, 200, 'UTF-8' );
// 移除模板标签必备字符
$servermsg1 = str_replace(
array( '{', '}', '<', '>', '"', "'", '`', '\\\\', "\r", "\n" ),
array( '{', '}', '<', '>', '"', ''', '`', '', '', '' ),
$servermsg1
);
// 再做一次黑名单兜底
if ( preg_match( '#dede:|\\{/?[a-z]+:#i', $servermsg1 ) ) {
$servermsg1 = '';
}
$urls .= "{dede:link islocal='1' text='{$servermsg1}'} $softurl1 {/dede:link}\r\n";
这一段代码做了三层防御:长度限制把刷量行为压住、字符替换让任何模板标签关键字符都失去语法意义、最后的正则兜底再扫一遍dede:这种关键字。三层叠在一起黑客要绕过基本要找到PHP字符串处理本身的漏洞,难度上一个量级。
同时修补softurl1参数
除了servermsg1,soft_add.php里的softurl1(下载URL)也是常被利用的参数。它应该是合法URL但代码层没强制检查。修补方法:
$softurl1 = isset( $softurl1 ) ? trim( (string) $softurl1 ) : '';
if ( $softurl1 !== '' ) {
// 只允许 http https ftp 开头
if ( ! preg_match( '#^(https?|ftp)://[^\\s\\'"<>]+$#i', $softurl1 ) ) {
$softurl1 = '';
}
}
这样softurl1只能是合法的http/https/ftp URL,不能包含空格、引号、尖括号等可能用于跳出语境的字符。
验证修补是否生效
修改完保存,用grep再确认一次代码已经更新:grep -n "mb_substr" member/soft_add.php应该能看到刚加的那行。如果没有说明保存的不是修改后的版本(可能编辑器没保存或者权限问题)。
访问站点member/soft_add.php页面看是否能正常加载,先不提交内容。如果页面500错误说明改坏了,立即回滚备份(修改前必须先cp一份备份)。
顺手把同目录其他高危入口一起加固
member目录的其他危险文件
member目录是织梦漏洞的重灾区,保哥每次处理事故都会把整个member一起审一遍。除了soft_add.php下面这些文件也建议同时加固或者直接禁用。
article_add.php:会员发布文章入口,2019年披露过模板注入;inc/inc_archives_functions.php:辅助函数库,被article_add.php和soft_add.php共同调用历史多个漏洞;resetpassword.php:旧版本的密码重置接口,CNVD-2018-13063任意密码重置;reg_new.php:注册入口,2020年披露过XSS;image_add.php:图片上传入口,存在文件上传绕过。
整体deny member目录
如果站点根本不开放会员投稿,最干净的处理是直接在Nginx或Apache配置里把member目录整体deny:
location ^~ /member/ {
deny all;
return 403;
}
修改完nginx -t检查语法,再nginx -s reload。这样所有/member/路径的请求都返回403,相当于这部分功能彻底关闭。如果业务需要会员中心,至少把不必要的入口单独拒绝只放行登录、注册、个人中心这几个:
location = /member/soft_add.php { deny all; return 403; }
location = /member/article_add.php { deny all; return 403; }
location = /member/image_add.php { deny all; return 403; }
location = /member/resetpassword.php { deny all; return 403; }
数据库层面的善后审计
模板注入漏洞如果已经被利用过,光改代码不够,必须假设站点已经被GETSHELL按事故响应的标准流程走一遍。
全站文件mtime排查
把最近一周内被修改过的PHP文件全列出来:
find /www/wwwroot/yoursite -name "*.php" -mtime -7 -type f -ls
逐个审查这些文件,看是否是你手动修改的。如果有任何陌生文件出现,特别是在templets、data、uploads目录下,立即停下排查。
搜常见webshell特征
eval、assert、base64_decode、gzinflate、preg_replace带/e修饰符的都要重点看:
grep -rEn "eval\\(|assert\\(|base64_decode\\(|gzinflate\\(|preg_replace.*\\\\/e" /www/wwwroot/yoursite --include="*.php"
注意:织梦原代码里有大量eval和assert是合法用途(模板编译),不能见到就删。需要的是判断"陌生位置出现的可疑函数",比如uploads/2024/03/xxx.php里出现eval(uploads应该只有图片不应该有PHP)。
检查管理员表
织梦的dede_admin表里如果多了陌生账号立刻删除并重置所有现有管理员密码:
SELECT id, userid, uname, logintime, loginip FROM dede_admin;
UPDATE dede_admin SET pwd = MD5( CONCAT( 'salt_', RAND() ) ) WHERE id = 1;
登录IP(loginip字段)是排查的关键线索。如果某个管理员账号最后登录IP来自境外(特别是俄罗斯、乌克兰、印尼这几个高频地区),高度怀疑被入侵。
隐蔽位置的webshell
data目录、uploads目录、templets目录全部翻一遍。这三个地方是webshell最爱藏的位置,特别是templets下面的.htm、.lib.php文件如果文件名很怪(一串随机字符),优先怀疑。常见隐藏模式:
模式一:伪装成图片的PHP,比如data/cache/inc_pic.php内容是PHP代码但文件名看起来像图片相关。
模式二:嵌入正常文件的backdoor,比如include/common.inc.php里被偷偷加了一行eval调用。需要md5sum对比官方版本相同文件。
模式三:cron job植入,crontab -l看是否有陌生的定时任务(每分钟拉远程脚本执行的典型木马手法)。
access_log分析
看访问日志。把过去30天里访问过member/soft_add.php、plus下面各种脚本的IP全摘出来分析是不是有异常POST:
grep -E "soft_add\\.php|/plus/.*\\.php" /www/wwwlogs/yoursite.log | awk '{print $1}' | sort | uniq -c | sort -nr | head -30
访问频次远高于普通用户的IP(比如一天访问500次以上)基本是攻击源。把这些IP做iptables封禁加到WAF黑名单。
配置层面的长期加固
保哥处理过的事故里有一半是因为站点开了不该开的功能。织梦默认配置非常宽松,强烈建议按下面这套关一遍。
后台改默认路径
/dede是默认后台目录全网都知道必须改成无规律字符串,比如/admin_8f3a2/。修改方法:把dede目录重命名为新名字,同时修改include/common.inc.php里的cfg_cookie_encode或者直接搜代码里所有"/dede/"硬编码并替换。
plus目录权限收紧
plus下面的ad_js.php、download.php、guestbook.php、search.php这些是历年漏洞集中区。不需要的全删,需要的也最好用nginx限制只允许特定IP访问:
location ~* /plus/(ad_js|guestbook|search)\\.php {
allow 192.0.2.0/24;
deny all;
}
关闭会员投稿
系统设置里把会员投稿开关关掉。如果业务必须开至少把发布权限提到指定会员等级,新注册账号默认不能投稿。在include/dedebiz.class.php里的会员权限定义中调整。
禁止PHP在uploads目录执行
这是任何CMS站都该做的:
location ~* ^/uploads/.*\\.(php|php5|phtml|inc|cgi)$ {
deny all;
return 403;
}
PHP open_basedir限制
把PHP进程能访问的目录限定在站点根目录内即便webshell跑起来也出不去:
open_basedir = /www/wwwroot/yoursite/:/tmp/
在php.ini或者宝塔的网站设置里加。注意这会影响某些插件的兼容性,加完一定要全站测试一遍特别是文件上传和图片处理功能。
disable_functions屏蔽危险函数
在php.ini里禁用exec、system、shell_exec、passthru、popen、proc_open这些系统命令执行函数:
disable_functions = exec,system,shell_exec,passthru,popen,proc_open,proc_close,assert,eval
但要注意:assert和eval织梦核心代码会用,禁用之后织梦本身可能跑不动。先在测试环境验证。
WAF规则的具体配置
云WAF的快速防御
Cloudflare免费版WAF能拦截大部分公开POC。开启方法:在Cloudflare面板的Security - WAF - Managed Rules里启用OWASP Core Ruleset。同时在Custom Rules里添加针对织梦的规则:
规则一:拦截访问member/soft_add.php但POST数据包含dede:或者模板标签关键字的请求。
规则二:拦截User-Agent为空或者明显是脚本工具(curl、wget、python-requests)的POST请求。
规则三:拦截访问plus目录的非标准请求(比如带?id=union select的明显SQL注入payload)。
自建WAF(modsecurity)
预算紧张可以用modsecurity开源WAF,规则比云WAF更灵活但维护成本高。规则示例(OWASP CRS v3.3):
SecRule REQUEST_URI "@contains /member/soft_add.php" \\
"id:9001,phase:2,deny,status:403,msg:'DedeCMS soft_add.php template injection attempt',\\
chain"
SecRule ARGS "@rx (?:\\{\\s*dede:|\\{\\s*/dede:)" \\
"t:none,t:lowercase"
规则的含义:访问soft_add.php时如果POST参数中出现织梦模板标签的开闭标记直接返回403。
迁移到Typecho的决策
织梦该不该继续守
保哥说句实在话:织梦这个CMS已经多年不维护,所有公开漏洞都不会再有官方补丁,社区补丁碎片化、质量参差不齐对站长来说维护成本只会越来越高。
如果是企业站、信息发布站建议迁到WordPress或Typecho。迁移有现成的工具能把文章、栏目、图片批量搬过去URL结构也能用伪静态规则保持兼容对SEO影响可控。具体迁移方法见保哥另一篇DedeCMS转Typecho的完整教程。
如果是政府、学校、政务公开类站点强制有等保要求的建议换成有官方维护的国产CMS,比如PageAdmin、动易、帝国CMS这种至少还在更新的,至少漏洞披露有渠道、有补丁。
继续守在织梦上能省一时迁移成本但每一次新漏洞披露都要紧急排查,长期算下来并不划算。
迁移过渡期的处理
从决策迁移到迁移完成通常需要2到4周时间。这期间织梦站点还要持续在线,必须做好临时加固:
第一立即按本文修补soft_add.php和其他高危入口;第二Cloudflare WAF开到I'm Under Attack模式拦截可疑流量;第三nginx日志每天监控异常访问;第四关闭所有非必要功能(会员中心、评论、留言板);第五备份数据库每天一次保留7天。
持续监测与应急响应
文件完整性监控
每天凌晨用脚本对站点关键目录做md5sum记录,下次执行时对比差异:
#!/bin/bash
find /www/wwwroot/yoursite -name "*.php" -type f -exec md5sum {} \\; | sort > /tmp/php_md5_$(date +%F).txt
# 对比昨日
diff /tmp/php_md5_$(date -d yesterday +%F).txt /tmp/php_md5_$(date +%F).txt > /tmp/diff_$(date +%F).txt
[ -s /tmp/diff_$(date +%F).txt ] && mail -s "PHP file changed" admin@yoursite < /tmp/diff_$(date +%F).txt
有任何文件变化邮件告警。这是最有效的入侵检测手段之一。
数据库异常监测
dede_admin表的行数、dede_member表的新增账号、dede_archives表的modified字段如果出现非正常波动都要告警。可以写一个简单的cron脚本每小时检查。
应急响应预案
发现入侵后的SOP:第一立即把网站设为维护模式(nginx返回503维护页);第二备份当前所有文件和数据库到安全位置(不要覆盖之前的备份);第三按本文事故响应流程做全面排查;第四从最近一次干净备份恢复站点;第五重置所有管理员密码和数据库密码;第六做完所有加固再重新上线。
常见问题解答
我已经按本文改了soft_add.php但还是被挂马怎么办?
说明站点之前就已经被植入了后门,这次新挂的文件是后门重新执行的产物。必须按上面提到的事故响应流程从mtime排查、webshell关键字扫描、管理员表清理一路走完。光修单个漏洞不解决已经存在的后门。建议立即把网站设为维护模式做完整的后门清除流程再重新上线。
把member整个目录deny掉会影响普通会员登录吗?
会。member/index.php和member/login.php是会员中心入口全deny之后会员功能完全不能用。建议只deny不需要的脚本文件或者用白名单方式只放行登录、注销、个人中心几个文件。如果站点本来就没有真正的会员业务(只有装样子的注册功能),直接deny整个member目录是最干净的处理。
DedeCMS的官方更新地址还能用吗?
官方升级服务器多年不稳定多数情况下连不上。市面上靠谱的修补来源是大型安全社区的整理版补丁包,下载后请先在测试环境跑一遍确认没引入新的兼容问题再上生产。注意区分官方版本号和社区魔改版本,魔改版本可能包含自带后门,必须从可信社区下载(阿里云安全、腾讯云T-Sec、奇安信都有DedeCMS加固方案文档)。
可以只开WAF不改代码吗?
可以缓解但不能根治。云WAF比如阿里云、腾讯云、Cloudflare都能拦截大部分公开POC但WAF是基于规则的,遇到0day或定制payload会失效。代码层修补是最后一道防线必须做。WAF加代码修补加服务器加固三层一起上才放心。预算紧张优先选代码修补(一次投入)+免费版Cloudflare WAF(持续保护)。
修补后用什么工具验证漏洞已经修复?
三种方式:第一手工测试,按公开POC的payload向soft_add.php发POST看响应是否被拦截(注意必须在自己服务器上测,不要测别人的站点);第二用开源扫描器,OpenVAS、Nessus社区版能扫DedeCMS的已知漏洞;第三付费方案,Acunetix、Burp Suite Pro能做更全面的Web漏洞扫描。推荐每季度做一次扫描。
除了soft_add.php还要重点关注哪些DedeCMS文件?
历年高危TOP 10:plus/recommend.php(SQL注入)、plus/search.php(SQL注入)、plus/feedback.php(XSS加SQL注入)、plus/download.php(SQL注入)、member/article_add.php(模板注入)、member/inc/inc_archives_functions.php(多个)、include/dialog/select_soft_post.php(任意文件上传)、include/dialog/select_images_post.php(任意文件上传)、tpl.php(模板木马写入)、include/uploadsafe.inc.php(上传过滤绕过)。建议把这十个文件都按本文方法审一遍。
有没有开箱即用的DedeCMS加固脚本?
保哥github上整理过一份dedecms-security-hardening.sh脚本(开源),包含本文提到的所有加固步骤的自动化版本。功能:自动备份原文件、应用本文的代码修补、生成nginx加固配置、生成WAF规则模板、cron文件完整性监控脚本。脚本经过6个客户站点的实战检验,可以SSH执行一次完成绝大部分加固。注意:脚本执行前先做完整数据库和文件备份,加固有可能影响某些自定义功能。
修补后SEO会受影响吗?
不会负面影响反而是正面的。原因:被挂马的站点会被Google和百度标记为不安全网站,搜索结果里会有警告标记,CTR大幅下降;及时修补能避免这种情况。另外修补本身不修改任何前端URL、HTML结构、Sitemap,对SEO是完全透明的。Google Search Console的安全问题报告也会因为修复而消除警告。修复后建议立即在GSC提交"已修复"申请,加速安全标记清除。