如果你手头还维护着一套 DedeCMS 站点这几年你多半已经养成了一个习惯隔一阵就去翻一遍官方公告看看有没有新的安全更新。CNVD-2018-0109 这个编号在老 DedeCMS 圈子里基本是绕不开的一个存在——前台任意用户密码修改漏洞。它厉害的地方在于攻击者不需要进入后台也不需要拿到数据库只需要知道目标会员的用户名就有机会直接在前台把这个账号的密码改掉。如果站长的管理员账号恰好也能从前台登录那后果就不只是会员数据被篡改而是整站权限都可能拱手让人。我在前前后后处理这个漏洞的过程中见过不少因为修复不及时被刷成垃圾站、被挂上跳转马、甚至整站被删的例子也踩过一些修复后功能反而异常的坑。这篇文章会把整个修复流程、代码层面的处理思路、以及实际排错过程中的记录完整整理出来给还在维护老 DedeCMS 的朋友做参考尤其适合那些代码已经改过很多次、没法直接替换官方整包的站点。1. 漏洞影响与成因分析1.1 这个漏洞到底会造成什么后果先把这个漏洞的影响范围说清楚。CNVD-2018-0109 属于典型的前台逻辑漏洞主要影响 DedeCMS 5.7 SP1 及之前的较老版本。这类版本在大量老站里非常常见很多站点从上线到现在就没升过级会员系统、投稿功能都还在运行正好处于漏洞射程以内。漏洞的现实危害可以这么理解一套会员系统相当于一栋楼里的门禁正常流程是住户通过钥匙或门禁卡进出忘记密码时可以找物业重新登记。而这个漏洞相当于门禁系统里多了一个隐蔽的后门——外人只要知道某个住户的房号就能直接把这个住户的门锁密码换掉。用在网站上就是攻击者只要知道目标用户名或者能猜到用户的 ID就有机会提交一个精心构造的请求把该用户的密码重置成自己指定的值。这里有一个很多人会忽略的细节DedeCMS 很多站点的管理员账号是在前台会员表里同样存在的。也就是说管理员既能进后台也能走前台会员入口。一旦这类账号被改掉密码攻击者再顺着后台登录入口进去整站就彻底失守了。所以这个漏洞影响的绝对不止普通会员站长账号一样在风险范围内。1.2 密码重置流程为什么会被绕过要理解漏洞成因得先看 DedeCMS 正常的密码找回流程。常规情况下用户在前台点击“忘记密码”提交自己的用户名或邮箱系统生成一个临时链接发到邮箱里用户点击链接进入设置新密码的页面输入新密码完成重置。这个流程本身是没问题的问题出在“操作者身份”的校验不够严格。漏洞主要集中在 member 目录下的密码重置相关文件里。整套流程在执行过程中有一部分请求参数是直接从前台提交上来的比如用户 ID、操作类型、临时凭证等。如果系统在某个环节没有对这类参数做严格校验攻击者就有机会通过替换参数值绕过邮箱验证这个关键环节直接跳到修改密码的步骤。再加上早期版本中临时凭证的生成规则相对固定一旦被分析出规律整个重置流程就基本等于裸奔了。从数据处理层面深挖问题通常出在两个方面一是校验顺序不对某些关键判断放在密码更新之后执行等于先操作后验证二是对输入参数的过滤不足用户 ID 这类敏感值没有做强制类型转换和归属校验。修复时必须同时处理这两点否则只堵住其中一个入口另一个方向仍然可能被绕过。2. 动手修复前先把准备工作做扎实2.1 确认站点代码版本和漏洞命中情况这里得先说一句修复之前必须确认你手头 DedeCMS 的具体版本和文件结构。同一套修复代码在不同版本上表现差异很大直接照搬网上的片段轻则功能异常重则把会员系统改废掉。确认版本有几种常用方式方式操作路径说明后台版本显示后台首页或“系统”菜单下的版本信息最直观能看到 V57_GBK 之类的版本标识版本常量文件查看 /include/version.inc.php里面定义了 DEDEVERSION能精确到具体修订版本安装信息文件查看 /data/admin/ver.txt部分版本安装时会写入版本号源码特征对比grep “resetpassword” 等关键词通过 member 目录下文件内容辅助判断版本区间在确认版本的同时顺手看一下 member 目录下的文件列表。重点检查 resetpassword.php 是否存在、文件大小、最后修改时间。如果这个文件的修改时间非常老而且一直没有备份记录那基本可以判定漏洞处于未修复状态需要立即处理。2.2 备份、环境盘点与本地复现环境搭建接下来做备份。很多站长觉得“改个文件而已不用备份”但实际操作中改坏一个函数导致整站白屏的情况太常见了。我处理过的案例里至少有三分之一是在修复过程中引入了新的问题。所以备份这一环无论如何不能跳。备份分三层全站代码备份最简单的做法是把网站根目录完整打包下载压缩后在本地留存一份。如果服务器空间紧张至少要打包 member 目录和 include 目录。数据库备份用 DedeCMS 后台自带的“系统 - 数据库备份/还原”功能或者直接用 mysqldump 把整库导出。注意数据库备份完成后把备份文件下载到本地不要把 SQL 文件长期留在网站目录里。配置信息记录把当前使用的 PHP 版本、Web 服务器类型、伪静态规则、邮件发送方式记录下来。这些信息在后期排查修复后遗症时非常有用。备份完成以后强烈建议在本地或者一台测试服务器上先做一遍修复演练。DedeCMS 这种老系统生产环境里往往叠加了无数二次开发和模板改动直接在线上改代码风险极高。测试环境里模拟一次完整的修复流程确认会员注册、登录、找回密码都正常后再把修复步骤搬到生产环境。3. 手动修复核心代码的完整步骤3.1 修复密码重置主流程member/resetpassword.php这个文件是整个漏洞的核心战场修复的重点是给密码重置流程加上“一次性临时凭证”机制并在每个关键节点都重新校验操作者身份。先说修复思路。正规的密码重置流程应该拆成三个独立步骤第一步是用户提交找回请求系统生成一次性 token 并发送到用户邮箱第二步是用户点击邮件中的链接携带 token 访问重置页面系统校验 token 的合法性第三步是用户提交新密码系统二次确认 token 和用户身份一致后才真正执行密码更新操作。漏洞版本的问题在于第二步和第三步之间存在逻辑断层攻击者可以绕过第二步直接提交第三步。以下是一个修复后的流程示意代码供你结合自己站点的实际版本调整// 第一步用户提交找回密码申请 if ($dopost send) { $username trim($_POST[username]); // 根据用户名查询会员信息确认账号存在 // 生成一次性 token与账号绑定并写入数据库或缓存 $token md5(uniqid(rand(), true)); // 将 token、用户ID、申请时间存入临时表或 session // 构造重置链接/member/resetpassword.php?dopostgetpasswdtoken$token // 通过邮件发送给用户 exit(); } // 第二步用户从邮件点击链接进入 if ($dopost getpasswd) { $token preg_replace(/[^a-z0-9]/i, , $_GET[token]); // 查询临时凭证记录校验 token 是否存在、是否过期 // 校验通过后将用户 ID 写入 session并显示设置新密码的表单 if (!$valid) { ShowMsg(链接无效或已过期请重新申请, resetpassword.php); exit(); } } // 第三步提交新密码 if ($dopost resetpwd) { // 二次校验表单中的 token 必须与第一步生成的 token 一致 $token isset($_POST[token]) ? trim($_POST[token]) : ; $sessionToken isset($_SESSION[reset_token]) ? $_SESSION[reset_token] : ; if ($token || $token ! $sessionToken) { ShowMsg(安全校验失败请重新申请, resetpassword.php); exit(); } // 强制转换用户 ID 为整型防止参数注入 $uid intval($uid); // 校验通过后才允许执行 UPDATE 密码操作 $pwd md5(trim($_POST[pwd])); $dsql-ExecuteNoneQuery(UPDATE #__member SET pwd{$pwd} WHERE id{$uid}); }这段代码不是让你原样复制的不同版本的 DedeCMS 在变量命名和函数调用上有很大差异。核心要把握三个修改原则第一重置密码前必须有一次性 token 校验第二token 必须绑定到具体用户不能是全局通用凭证第三用户 ID 参数必须做 intval 强制转换并和后端查询结果做比对避免直接使用前台提交的 UID 去执行 SQL 更新。我在实际修复中还遇到过一种情况有些二次开发版本会直接跳转到/member/index.php?actionresetpwduid某个数字这样的地址整个流程没有任何凭证校验。这种情况非常危险等于把修改密码的接口完全暴露给外界。遇到类似代码必须把参数传递方式改成“仅从 session 或临时记录中读取用户 ID”完全忽略 GET/POST 提交的 uid 参数。3.2 处理其他前台会员入口并收紧全局校验修完主流程文件还差一步收尾工作。member 目录下还有其他前台入口文件比如会员中心首页、投稿处理、资料修改等模块在修复过程中也需要顺手加固。主要做以下几个动作检查 member 目录下所有 PHP 文件看是否存在类似的“参数驱动操作”逻辑尤其是涉及 UPDATE、DELETE、INSERT 语句的位置。凡是直接从前台接收用户 ID 并执行数据库更新操作的都要改成基于 session 来判断当前登录用户。全局变量过滤。DedeCMS 的 common.inc.php 里有全局变量注册相关的机制不同版本处理方式不同。如果你的版本还保留了比较宽松的变量注册方式建议结合站点实际情况收紧密集避免变量覆盖类攻击和参数注入同时发生。如果站点实际上并没有开放会员注册或投稿功能最省事的做法是直接在后台关闭会员模块并在 Web 服务器层面禁止外网访问 member 目录。暴露面越少被利用的概率越低。这轮收尾做完基本能保证漏洞入口被有效封堵。3.3 配置层面的访问控制与权限收紧代码层面的修复到位之后我建议再往上叠一层配置加固这样即使以后又出现类似逻辑漏洞攻击者也要先过配置这一关。首先是文件目录权限。member 目录本身只需要读取和执行权限不应该允许 PHP 写入建议设置成 755属主可写其他用户只读底层文件属主和 Web 运行用户如果不是同一个还需要仔细确认两者关系。上传目录、data 目录、cache 目录这类需要写权限的地方要设定严格的目录白名单禁止跨目录执行脚本。其次是 PHP 运行环境。在 php.ini 或站点配置中启用 open_basedir把 PHP 可读取的目录限制在网站根目录范围内同时根据实际情况考虑禁用部分危险函数比如 eval、assert、system、exec 等。DedeCMS 某些功能可能依赖这些函数建议先在测试环境验证确认无影响后再上线避免因为配置过死导致站点功能崩溃。再就是数据库账号权限。给 DedeCMS 单独创建数据库账号并只授予当前数据库的 SELECT、INSERT、UPDATE、DELETE 权限不要给 FILE、PROCESS、SUPER 等高权限。即便某个 PHP 文件被拿下攻击者也很难利用数据库账号去读取服务器文件或提权。4. 官方补丁安装与长期安全运维4.1 官方升级包和补丁的正确安装方式代码手动修复是最直接的方案但如果你手头的版本还没有经过大规模二次开发优先考虑安装官方补丁会更省事。DedeCMS 官方针对这一漏洞发布过安全更新补丁会同步修改多个受影响文件比手动逐个文件修复覆盖得更全面。补丁安装的流程要注意几个步骤先去 DedeCMS 官网或官方安全公告页面找到对应你当前版本的安全补丁下载链接。注意区分 GBK 版本和 UTF-8 版本编码搞错了文件直接乱码。下载完补丁先不要急着传生产环境放到测试站点解压对比一下补丁文件是否和你当前修改过的文件存在冲突。如果你的站点之前已经改动过同路径文件需要手动合并直接覆盖会丢掉原有功能。测试环境确认无误后在生产环境进行操作。操作前再次确认整站代码和数据库已备份。按补丁说明覆盖到对应目录然后登录后台执行“系统 - 数据备份/还原 - 更新缓存”或者直接删除 data/cache 下的缓存文件强制系统重建缓存。升级完成后第一时间验证会员注册、登录、找回密码流程是否正常。务必用真实邮箱走一遍完整的找回密码流程确认邮件能收到、链接能用、密码能改成功。这里必须强调一点如果站点已经被二次开发过很多次官方补丁很可能覆盖不进去甚至部分补丁默认基于原版代码制作和你改过的文件结构对不上。这种情况下反而建议直接走上一章的手动修复方案起码改动范围和影响面是可控的。4.2 一套长期有效的 DedeCMS 安全加固清单修复完一个漏洞不代表以后就安全了。以我维护 DedeCMS 站点的经验只要站点还在用这套老系统安全加固就是一个需要持续跟进的长期事项。下面这套清单是我每处理完一次安全事件都会顺手过一遍的建议你也留着修改后台登录路径。把 admin 目录名改成一段随机字符串降低被批量扫描工具直接命中的概率。修改数据库表前缀和数据库账号密码。DedeCMS 默认前缀是 dede_扫描工具会优先尝试用默认前缀构造 SQL 注入改掉前缀等于多一层防护。删除 install 目录。很多 DedeCMS 站点安装完成后没有删除安装脚本攻击者可以直接重装系统覆盖管理员账号。定期备份数据库和站点文件。备份文件不要放在网站目录内否则一旦备份路径被猜到等于把整站数据直接送给攻击者。关注官方安全公告尤其是 DedeCMS 官方停止更新后的安全预警信息。如果站点已经没有维护团队尽量关闭投稿、会员注册等交互功能最大程度减少攻击面。这些操作听起来零碎但每一项都能提升攻击者的利用成本。安全没有一劳永逸的解决方案只有把每一层防护都做到位才能把风险压到最低。5. 实际操作中遇到的问题与排查记录5.1 修复后会员忘记密码功能无法正常使用这个是我处理过程中遇到最多的问题也是修复后最常见的“后遗症”。具体症状表现为用户提交找回密码后收不到邮件或者收到邮件点击链接提示无效、已过期甚至直接报 500 错误。排查顺序一般是这样确认邮件是否真的发送成功。到服务器上检查邮件发送日志不少 DedeCMS 站点用的是 SMTP 方式发送如果 SMTP 账号密码配置失效、发信频率受限邮件根本出不去。这一步能排除掉大部分故障。如果邮件能收到但链接报错重点检查 token 校验逻辑。手动修复时如果 token 的生成和校验用的不是同一套规则或者 session 写入时机不对就会出现“链接无效”的提示。常见错误是第一步生成 token 后没有正确写入 session第二步校验时又读取了一个空的 session 变量。检查 PHP 错误日志。把 display_errors 临时打开看页面报错的具体文件和行号能快速定位是语法错误还是函数不存在。定位完记得把 display_errors 关回去避免线上暴露调试信息。这种问题多是因为修复代码写得太“想当然”没有充分考虑 DedeCMS 原有代码的调用方式。对照原版代码逐行比对通常很快就能找到问题。5.2 补丁升级后出现白屏或 500 错误另一种常见情况是升级补丁后整站白屏。绝大多数原因是文件覆盖不完整比如补丁包里包含 10 个文件实际只上传了 8 个或者上传时目录层级不对文件根本没落到正确位置。解决办法也不复杂把补丁包里的文件按照官方说明逐一核对确保每个文件都覆盖到了对应路径然后清空缓存目录重新加载如果仍然白屏开启 PHP error_log 看具体报错信息。如果是因为兼容性问题导致的语法错误就要考虑是不是补丁版本和你当前站点版本不匹配需要去下载对应历史版本的兼容补丁。5.3 日常日志监控与可疑行为识别修复完成且各功能验证正常之后别忘了把日志监控补上。很多时候漏洞利用并不是一次性动作攻击者可能会在站点里留后门、添加隐藏管理员等到合适时机再回来操作。日常监控主要关注这几个方向Web 访问日志里member 目录相关的请求是否异常增多尤其是 resetpassword.php 这个文件被反复请求的情况。数据库里是否有新增的、典型管理员用户名的记录会员表里是否出现大量非正常注册的账号。网站根目录是否出现不认识的 PHP 文件特别是文件名看起来像是随机字符串、内容又非常短的文件这类文件多半是恶意脚本。我之前给朋友站点做排查时就是通过访问日志里发现某个 IP 短时间内连续请求了上百次 member 目录下的接口才顺藤摸瓜找到了被写入的后门文件。有条件的话可以写一个简单的脚本每周扫描一次新增文件和最近被修改的文件列表把常见的可疑特征过滤出来这样即便出现新的问题也能在早期发现并处理。再分享一个我自己的小习惯每次处理完这类漏洞修复我都会顺手把整站文件备份打包一次并在备份文件旁边写一个 TXT 说明记录这次修复涉及的文件列表和修改日期。这样下次再出问题时光看这份记录就能快速判断哪些文件被动过省去大量重新排查的时间。对于 DedeCMS 这种老系统这种“留痕”的习惯比任何安全工具都实在。