保哥笔记

DedeCMS 会员中心 mtypes.php SQL 注入漏洞修复深析:数组键名注入根因、intval 补丁与系统化排查

DedeCMS 会员中心的 /member/mtypes.php 在 2014 年被披露存在 SQL 注入漏洞——攻击者通过分类管理表单的 mtypename[id] 数组键名注入恶意 SQL,能直接读取/修改数据库内容(含管理员密码哈希)。这是 DedeCMS 历史上影响最广的漏洞之一,至今很多老站没打补丁仍在裸奔。

这一篇把这个漏洞讲透:根因(PHP 数组键名直接拼到 SQL 里)、原帖给的补丁完整解析、为什么 intval() 是关键、补丁后还有哪些 SQL 注入点、如何系统化扫描整套 DedeCMS 的注入风险、与 WAF / fail2ban 的协同防御、2026 年还在用 DedeCMS 的安全建议。

一、漏洞原理:数组键名 SQL 注入

原始漏洞代码(/member/mtypes.php):

foreach ($mtypename as $id => $name) {
    $name = HtmlReplace($name);   // ✅ 对 $name 做了过滤
    $query = "UPDATE `#@__mtypes` SET mtypename='$name' WHERE mtypeid='$id' AND mid='$cfg_ml->M_ID'";
    //                                                              ↑↑↑↑
    //                                                          $id 没过滤!
    $dsql->ExecuteNoneQuery($query);
}

开发者过滤了数组的 $name)但忘了过滤 $id)。PHP 数组的键名可以是任意字符串——攻击者构造一个表单:

<form method="POST" action="/member/mtypes.php?dopost=save">
    <input name="mtypename[1' OR '1'='1]" value="payload" />
</form>

提交后 PHP 解析得到 $mtypename = ["1' OR '1'='1" => "payload"]。foreach 循环时 $id = "1' OR '1'='1",直接拼到 SQL:

UPDATE `dede_mtypes` SET mtypename='payload' WHERE mtypeid='1' OR '1'='1' AND mid='123'

WHERE 条件被打穿——所有 mtypes 行都被改成 mtypename='payload'。更高级的攻击者能用 UNION SELECT ... 读取其它表(dede_admin 拿管理员密码哈希)。

二、补丁的关键改动

原帖给的补丁就一行关键改动:

foreach ($mtypename as $id => $name) {
    $name = HtmlReplace($name);
    $id = intval($id);   // ← 关键:把 $id 强制转成整数
    $query = "UPDATE `#@__mtypes` SET mtypename='$name' WHERE mtypeid='$id' AND mid='$cfg_ml->M_ID'";
    $dsql->ExecuteNoneQuery($query);
}

intval("1' OR '1'='1") 会得到 1(PHP 的 intval 解析到第一个非数字字符就停)。攻击 payload 被中和。

2.1 为什么 intval 比 HtmlReplace 强

过滤方式能防 SQL 注入吗能防 XSS 吗
HtmlReplace(DedeCMS 内置)不可靠可(转义 HTML 实体)
addslashes不可靠(GBK 字符集下能绕过)不可
mysqli_real_escape_string对引号内场景可靠不可
intval对纯整数场景绝对可靠不可(但整数 ID 不参与 HTML 输出)
预处理语句(PDO / mysqli prepare)绝对可靠不可

对 ID 这种"应该是整数"的字段,intval() 是最稳的——任何非数字内容直接变 0 或截断到第一个数字。预处理语句更通用但 DedeCMS 老代码大量用字符串拼接 SQL,临时性补丁里 intval 性价比最高。

三、补丁还不够:mtypes.php 里的其它注入点

原帖只补了 UPDATE 这一处,但同文件里还有 DELETE 操作 $delids 也存在风险点:

$delids = '0';
$mtypeidarr = array_filter($mtypeidarr, 'is_numeric');
foreach($mtypeidarr as $delid) {
    $delids .= ','.$delid;
}
$query = "DELETE FROM `#@__mtypes` WHERE mtypeid IN ($delids) AND mid='$cfg_ml->M_ID';";

这里 array_filter(..., 'is_numeric') 已经过滤了非数字键值。但 is_numeric() 接受 "123e+0" 这种科学计数法,理论上可以 "1,2);DROP TABLE x;--" 这种被 is_numeric 拒掉,所以这里相对安全。但严谨做法是每个 $delid 也 intval 一遍

foreach($mtypeidarr as $delid) {
    $delid = intval($delid);   // 双保险
    $delids .= ',' . $delid;
}

四、系统化排查 DedeCMS 整套代码的注入风险

mtypes.php 不是孤例——DedeCMS V5.6 / V5.7 / 早期 SP 版本里类似的"数组键名直拼 SQL"漏洞还有十几处。要系统化排查:

4.1 用 grep 扫"foreach + 拼 SQL"模式

cd /www/wwwroot/yoursite.com/

# 找所有 foreach 后面跟 query 拼接的代码
grep -rn "foreach.*as.*=>" --include="*.php" -A 5 | \
    grep -B 1 -E "(query|sql).*\\\$" | head -100

每条命中行都需要人工审查:"键名是否被过滤?值是否被过滤?"。

4.2 重点排查路径

4.3 已知的 DedeCMS 高危 CVE

CVE位置类型受影响版本
CVE-2018-9134/member/mtypes.phpSQL 注入(本文)V5.7 SP1 之前
CVE-2018-7700/uploads/任意文件上传V5.7 SP1 之前
CVE-2019-8362/dede/file_manage_view.phpRCEV5.7 SP2 之前
CVE-2020-25008/include/dialog/任意文件读取V5.8.1 之前
CVE-2022-23047/dede/article_string_mix.phpSQL 注入V5.7.106 之前

5 条只是"明显的"——实际累计高危漏洞超过 50 条。建议:① 升级到最新社区分叉 DedeBIZ;② 定期跑 D 盾、河马等漏洞扫描器;③ 上 WAF 兜底。

五、SQL 注入的攻击面与影响

5.1 攻击者能做什么

5.2 防御纵深

即使 PHP 代码有注入,也能通过其它层次缓解:

六、用 PDO 预处理语句重构 mtypes.php

临时补丁是 intval。彻底修复用 PDO 预处理,永久无忧:

// 假设把 DedeCMS 的 $dsql 替换成 PDO 实例
$pdo = new PDO('mysql:host=localhost;dbname=dede;charset=utf8mb4', $user, $pass);

foreach ($mtypename as $id => $name) {
    $stmt = $pdo->prepare("UPDATE dede_mtypes SET mtypename = :name WHERE mtypeid = :id AND mid = :mid");
    $stmt->execute([
        ':name' => HtmlReplace($name),
        ':id'   => (int)$id,                  // 显式转 int
        ':mid'  => (int)$cfg_ml->M_ID,
    ]);
}

预处理语句的好处:参数永远不会被解释为 SQL 关键字,无论攻击者怎么构造 payload 都不会破坏 SQL 结构。性能上 PDO 预处理还能让数据库缓存执行计划,比拼字符串 SQL 还略快。

七、补丁部署的注意事项

7.1 升级前务必备份

cp /www/wwwroot/yoursite.com/member/mtypes.php /backup/mtypes.php.$(date +%Y%m%d)

万一打补丁打错或冲突,能 1 分钟回滚。

7.2 同时清模板缓存

修改 PHP 文件后清 data/tplcache/,避免老缓存还在用旧版逻辑。

7.3 验证补丁生效

用 sqlmap 测试一下:

sqlmap -u "https://yoursite.com/member/mtypes.php?dopost=save" \
       --data="mtypename[1*]=test&cfg_ml=1" \
       --cookie="DedeUserID=xx; DedeLoginTime=xx" \
       --level=3 --risk=2

未打补丁的版本会被 sqlmap 立刻发现注入;打了补丁的应报"no injection point found"。

八、长期建议:彻底升级或迁移

修一处漏洞只是补一个小坑。彻底解决:

  1. 升级到 DedeBIZ V6.x:社区分叉版,活跃维护,已合并大部分历史 CVE 补丁;
  2. 切到 WordPress + 迁移内容:WordPress 的核心代码经过更严格审计,安全生态更成熟;
  3. 用静态站生成器:Hexo / Hugo / Jekyll 等,没有动态后端就没有 SQL 注入面。

2026 年仍然新建 DedeCMS 站风险远大于收益——历史漏洞之多、社区维护之弱,每个上线日都是赌博。

九、运行时防御:上 WAF

即使代码有补丁,上 WAF 是额外的兜底。Cloudflare 免费版自带 OWASP 核心规则集,能拦绝大多数自动化扫描器:

Cloudflare WAF Rule (Free Plan):
- SQL Injection (SQLi) protection: ON
- Cross-Site Scripting (XSS) protection: ON
- File Inclusion (LFI/RFI): ON

阿里云 WAF / 腾讯云 WAF 国内方案类似。WAF 不是替代代码补丁——是补丁的补充。代码必须修对,WAF 是兜底。

十、监控与告警

装好 WAF 后再加监控:

十一、应急响应流程(如果发现已被入侵)

万一晚一步——发现站点已经被注入或挂马,按以下顺序处理:

  1. 立刻断网:暂停 Nginx / Apache 服务,避免攻击者继续操作;
  2. 全量备份当前状态:包括所有 PHP 文件、数据库、access.log、error.log——证据保全;
  3. 修改所有数据库密码:DedeCMS 数据库账号、管理员账号哈希;
  4. 查 access.log 找入侵时间窗:搜可疑 payload(含 'UNIONSELECT 的 POST 请求),确定攻击起点;
  5. 对比文件 mtimefind . -name "*.php" -newer /var/log/messages -mtime -7 找最近 7 天修改过的 PHP 文件,重点排查是否被加 webshell;
  6. 清理 webshell:常见后门函数 eval() / assert() / preg_replace 加 /e 修饰符,grep 整套代码找;
  7. 修补漏洞:本文 mtypes.php 补丁 + 升级到 DedeBIZ;
  8. 重置所有用户密码:admin、会员都强制重置;
  9. 恢复服务:开 Nginx,监控 24 小时;
  10. 事后复盘:写故障报告,归档攻击轨迹与防御加固清单。

11.1 找 webshell 的 grep 命令速查

# 找含 eval 的 PHP 文件
grep -rln "eval(" --include="*.php" /www/wwwroot/yoursite.com/

# 找 base64 编码的 PHP 字符串(webshell 常用伪装)
grep -rln "base64_decode" --include="*.php" /www/wwwroot/yoursite.com/

# 找含 assert 的(assert 也能执行 PHP)
grep -rln "assert(" --include="*.php" /www/wwwroot/yoursite.com/

# 找文件包含 + 用户输入(LFI/RFI)
grep -rEn "(include|require)(_once)?[\s(]+\\\$_(GET|POST|REQUEST)" --include="*.php" /www/wwwroot/yoursite.com/

每条命中的文件都要人工审查——是合法用法还是 webshell。

常见问题解答

怎么知道我的站有没有被攻击过?

三个排查点:① 看 web 服务器 access.log 找 mtypename[ + 引号 / OR / UNION 等可疑 payload;② 看 dede_admin 表有没有意外新增的管理员账号;③ 看 dede_mtypes 表的 mtypename 字段有没有奇怪的值(被 UPDATE 篡改痕迹)。fail2ban 日志和 WAF 告警也是入口。

HtmlReplace 真的不能防 SQL 注入吗?

不能。HtmlReplace 是 DedeCMS 自带的 HTML 转义函数(把 <&lt; 之类),针对 XSS 设计的。对 SQL 没有任何保护——SQL 不解析 HTML 实体。要防 SQL 注入要用 intval / addslashes / 预处理语句。

DedeCMS 已经停止维护,补丁从哪里拿?

三个来源:① DedeBIZ 社区分叉https://www.dedebiz.com/)已经合并了大部分历史 CVE,下载最新版即可;② D 盾 / 河马等国产 PHP 漏洞扫描器自带 DedeCMS 补丁库;③ 自己根据 CVE 公告手动改代码(本文方式)。生产环境推荐方案 ①。

升级 DedeBIZ 会影响现有数据吗?

不会。DedeBIZ 与 DedeCMS V5.7 数据库 schema 兼容——只是替换 PHP 文件,数据库不动。但升级前必须备份所有 PHP 文件 + 数据库,万一新版本与你的自定义模板/插件冲突,能回滚。

会员中心是否要彻底关闭?

看业务。如果你的站不需要会员(纯展示型企业站),关闭整个 /member/ 目录是最安全的——直接 Nginx deny all 这个 location,所有相关漏洞瞬间消失。如果业务需要会员,要么升级 DedeBIZ,要么自己审计代码 + 加 WAF。

修补丁后还要不要清 Web 服务器缓存?

看你用的缓存层。① OPcache(PHP 自带 opcode 缓存)会缓存 PHP 文件解析后的字节码,文件改动后默认监测 mtime 自动失效,但保险起见可 opcache_reset() 或重启 PHP-FPM;② 静态页面缓存(Cloudflare / 阿里云 CDN)跟 PHP 修改无关,但如果你的攻击页 URL 被 CDN 缓存了攻击响应,要清 CDN 缓存。

WAF 拦截的请求会影响真实用户吗?

会,但概率极低。WAF 偶尔会误报(比如用户输入的关键词恰好像 SQL)。Cloudflare WAF 有"挑战模式"——可疑请求弹验证码而不是直接 403,平衡安全与用户体验。生产建议上线前用 Log Mode 跑 1 周看告警准确率,再切到 Block Mode。

预处理语句会比拼字符串 SQL 慢吗?

不会。① 数据库会缓存预处理执行计划,二次执行更快;② PHP PDO 的预处理本质就是给参数加引号 + 转义,开销几乎为零;③ 在大查询里两者性能差异在毫秒级。预处理语句永远是更优选择,没有不用的理由。

DedeCMS V5.7 SP3 / SP4 修了多少漏洞?

SP2 之后官方维护放缓,SP3 / SP4 主要是 PHP 8 兼容修复 + 少量 CVE 补丁。绝大部分高危历史漏洞要靠社区分叉(DedeBIZ)维护。如果你站点版本老于 V5.7 SP2,立刻升级是头等优先。

2026 年还能新装 DedeCMS 吗?

从技术上能,但非常不推荐。原因:① 大量历史漏洞累积;② 官方停更,没有持续安全补丁;③ PHP 8 / MySQL 8 兼容性差;④ 现代 SEO 友好度不如 WordPress;⑤ 模板/插件生态萎缩。新建项目用 WordPress / Hexo / 自研 + 现代框架的成本远低于"用 DedeCMS + 持续打补丁"。