
1. 项目概述一次典型CMS漏洞的深度剖析最近在梳理一些老牌内容管理系统CMS的安全状况时DedeCms的一个编号为CVE-2024-3148的漏洞引起了我的注意。这个漏洞发生在makehtml_archives_action模块是一个典型的SQL注入漏洞。对于像我这样常年和Web安全打交道的从业者来说这类漏洞的分析过程就像一次精密的“考古”工作不仅要定位问题点更要理解其背后的设计逻辑和历史成因。DedeCms作为国内曾经广泛使用的CMS其代码结构承载了特定时期开发习惯的烙印分析它的漏洞不仅能帮助我们修复问题更能从中提炼出对现代应用安全开发有借鉴意义的教训。这篇文章我就带大家完整地走一遍这个漏洞的分析、验证与思考过程无论你是安全研究员、渗透测试工程师还是后端开发者都能从中获得一些实用的东西。2. 漏洞背景与核心原理拆解2.1 DedeCms与静态化生成机制DedeCms的核心功能之一就是强大的静态HTML页面生成能力这在其早期对于提升网站访问速度和搜索引擎友好度至关重要。makehtml_archives_action这个模块从名字就能看出是专门用于批量生成文档archives静态页面的动作控制器。它的工作流程通常是接收用户通常是管理员提交的一批文档ID然后循环遍历这些ID逐一调用核心的模板解析和文件写入功能生成对应的.html文件。问题往往就出在“接收用户提交的参数”这个最开始的环节。在Web安全中有一条几乎颠扑不破的真理所有来自客户端的数据都是不可信的。这包括了表单提交POST、URL参数GET、Cookie、甚至HTTP头部信息。makehtml_archives_action模块需要接收一个文档ID列表这个列表如何传递、如何被处理就成为了安全的关键。2.2 SQL注入漏洞的本质与常见触发点SQL注入的原理大家都很熟悉攻击者通过构造特殊的输入欺骗后端程序将输入数据当作SQL代码的一部分执行。在DedeCms这种使用PHPMySQL架构并且历史代码中大量使用字符串拼接方式构建SQL语句的应用里注入点非常常见。常见的触发场景包括直接拼接用户输入$sql “SELECT * FROM dede_archives WHERE id IN (” . $_POST[‘ids’] . “)”;这种写法是灾难性的。经过不安全的“过滤”后拼接很多老系统会自定义一些过滤函数比如只过滤空格、union等关键词但绕过方式层出不穷如使用/**/代替空格使用大小写混合、十六进制编码等。变量覆盖与全局变量滥用在老版本的PHP设置如register_globals On或某些框架习惯下可能导致用户输入直接覆盖程序内部的变量进而影响SQL语句。CVE-2024-3148很可能就是上述某种或多种情况结合下的产物。我们需要进入具体的代码层看看makehtml_archives_action是如何处理ids这个关键参数的。3. 漏洞代码定位与分析3.1 定位漏洞文件在DedeCms的目录结构中与生成HTML相关的后台文件通常位于/dede/后台管理目录下。makehtml_archives_action很可能对应着文件/dede/makehtml_archives_action.php。这是我们的首要分析目标。打开这个文件我们首先要寻找核心的业务逻辑函数通常是function定义的部分或者直接从头开始执行的脚本逻辑。重点关注处理$ids、$aid、$id等可能表示文档ID的变量接收和处理的代码段。3.2 关键代码段剖析假设我们找到了类似如下的代码段以下代码为基于漏洞原理的模拟还原用于分析// /dede/makehtml_archives_action.php 中的部分代码 $ids isset($_POST[‘ids’]) ? $_POST[‘ids’] : ‘’; $typeid isset($_POST[‘typeid’]) ? intval($_POST[‘typeid’]) : 0; $startid isset($_POST[‘startid’]) ? intval($_POST[‘startid’]) : 0; $endid isset($_POST[‘endid’]) ? intval($_POST[‘endid’]) : 0; // ... 一些逻辑判断 ... if ($ids ! ‘’) { // 情况A直接拼接进入查询条件 $query “SELECT * FROM #__archives WHERE id IN ($ids) AND arcrank -1”; $dsql-ExecuteQuery($query); while($row $dsql-GetArray()) { // 生成静态页 MakeArt($row[‘id’], $row[‘typeid’]); } } elseif($startid 0 $endid 0) { // 情况B使用$startid, $endid进行范围查询 for($i$startid; $i$endid; $i) { $query “SELECT * FROM #__archives WHERE id’$i’ AND arcrank -1”; // ... 执行并生成 } }漏洞点分析在上面的模拟代码中$ids变量直接从$_POST[‘ids’]获取并且没有经过任何有效的安全过滤就直接拼接到了SQL语句IN ($ids)中。这就是一个赤裸裸的SQL注入漏洞。攻击者可以提交这样的POST数据ids 1) OR 11 --。那么最终执行的SQL语句就变成了SELECT * FROM #__archives WHERE id IN (1) OR 11 -- ) AND arcrank -1--在MySQL中是注释符它会注释掉后面的) AND arcrank -1使得WHERE条件变成了id IN (1) OR 11。由于11恒真这条语句很可能会返回数据库中的所有文档记录导致信息泄露或者在更复杂的构造下进行联合查询UNION SELECT获取管理员密码、插入数据等危险操作。注意实际的漏洞代码可能更隐蔽。例如DedeCms可能有一个全局的过滤函数如_RunMagicQuotes、CheckSql但该函数可能存在缺陷比如只过滤了常见的union、select和空格但未能过滤括号()、注释符--、#或者未能处理编码绕过的情况。3.3 深入挖掘过滤函数的绕过许多CMS会自定义一个CheckSql函数。我们需要查看这个函数的实现通常在一个全局的common.inc.php或common.func.php文件中。// 模拟一个存在缺陷的CheckSql函数 function CheckSql($db_string, $querytype‘select’) { $clean preg_replace(‘/[\s]/’, ‘ ‘, $db_string); // 合并多余空格 $clean preg_replace(“/union[\s]select/i”, “”, $clean); // 过滤union select $clean str_replace(“‘“, “‘“, $clean); // 转义单引号但可能用addslashes更早处理了 // ... 其他过滤 return $clean; }这个过滤函数的问题在于它使用preg_replace直接替换union select为空这可以通过union/**/select用/**/注释符代替空格轻松绕过。它没有过滤括号()、分号;、注释符--、#。如果全局已经使用了addslashes或mysql_real_escape_string对字符串类型的变量进行了转义那么对于IN ($ids)中的$ids如果它是数字型参数转义是无效的。因为数字不需要引号包围注入的代码不会被转义。这是数字型注入的典型特征。因此即使有过滤在$ids被当作数字型字段id字段通常是整数的一部分进行拼接时不充分的过滤或错误的参数类型判断都会导致防护失效。4. 漏洞复现与验证实操4.1 本地测试环境搭建为了安全且合法地研究漏洞必须在本地或隔离的虚拟机中搭建测试环境。获取代码从官方或历史版本库下载存在漏洞的DedeCms版本需根据CVE信息确定具体版本范围例如V5.7 SP2之前的某个版本。部署环境安装PHP5.x/7.x匹配CMS版本、MySQL5.x、Web服务器Apache/Nginx。建议使用Docker或XAMPP等集成环境快速搭建。安装CMS按照安装向导完成DedeCms的安装记住后台地址如/dede/和账号密码。备份与快照在开始测试前对虚拟机或数据库进行快照备份方便回滚。4.2 构造攻击Payload我们假设漏洞点就在/dede/makehtml_archives_action.php并且ids参数存在数字型注入。步骤一信息探测首先我们需要确认漏洞是否存在以及数据库类型。登录后台找到“批量生成文档HTML”或类似功能通常位于“核心” - “批量维护”下。打开浏览器开发者工具F12的“网络(Network)”选项卡。选择少量文档点击生成观察发出的POST请求。找到请求的URL和参数格式。假设我们发现它向makehtml_archives_action.php提交了ids1,2,3。我们可以使用Burp Suite拦截这个请求或者直接使用浏览器的“编辑并重发(Edit and Resend)”功能。步骤二构造基础注入Payload我们将ids参数修改为ids1) AND 11 --注意--后面有一个空格在URL中可能是--%20。提交请求观察响应。如果页面正常生成或返回了文档列表说明AND 11条件为真语句执行成功。 再将ids修改为ids1) AND 12 --如果此时页面报错、返回空结果或与11时有明显不同则基本可以确认存在SQL注入漏洞。因为12恒假导致查询不到任何记录。步骤三利用漏洞获取信息在确认漏洞后可以尝试进行联合查询。这需要知道表名和列数。判断列数使用ORDER BY子句。ids1) ORDER BY 5 --不断增加数字直到页面报错如Unknown column ‘5’ in ‘order clause’则上一个数字就是列数。假设判断出有8列。联合查询构造UNION SELECT语句需要让前一个SELECT查询结果为空以便显示我们注入查询的结果。通常让前一个查询的id为一个不存在的值比如-1。ids-1) UNION SELECT 1,2,3,4,5,6,7,8 FROM dede_admin --提交后观察页面哪里显示了数字1-8这些位置就是可以回显数据的地方。假设数字2和3的位置在页面上显示出来了。获取管理员账号密码DedeCms的管理员表通常是dede_admin用户名字段可能是userid密码字段可能是pwdMD5加密。ids-1) UNION SELECT 1,userid,pwd,4,5,6,7,8 FROM dede_admin --如果成功页面上显示数字2和3的位置就会变成管理员的用户名和MD5密码哈希值。重要实操心得在实际测试中可能会遇到防火墙WAF、转义或过滤。需要尝试多种绕过技巧注释符尝试--空格、--、#URL编码为%23。空格绕过使用/**/、%09TAB、%0a换行符代替空格。大小写/双写绕过UnIoN SeLeCtUNunionION SELselectECT。编码绕过对关键字符进行十六进制编码如SELECT-0x53454c454354。4.3 漏洞验证与影响评估成功执行上述步骤后我们验证了漏洞的存在性和危害性。CVE-2024-3148的影响可以概括为影响版本特定版本的DedeCms需根据官方通告确认。攻击路径攻击者需要拥有后台登录权限因为makehtml_archives_action.php是后台文件。这是一个后台注入漏洞。这意味着漏洞利用的前提是攻击者已经通过其他方式如弱口令、前端漏洞拿到管理员密码进入了后台。其危害在于一旦进入后台此漏洞可以作为一个强有力的“后渗透”工具直接窃取数据库中的所有敏感信息如管理员密码、用户数据、配置信息甚至通过SELECT INTO OUTFILE尝试写入Webshell需满足严格的文件权限和配置条件。风险等级对于已获得后台权限的攻击者来说风险为高危对于未获得权限的外部攻击者此漏洞本身无法直接利用风险相对较低。5. 漏洞修复方案与安全加固5.1 临时修复与官方补丁最稳妥的方式是关注DedeCms官方发布的安全公告和补丁。对于CVE-2024-3148官方可能会发布一个修复版本或单独的补丁文件。应用官方补丁是首选。如果无法立即应用补丁可以进行手动代码级修复。修复的核心原则是使用参数化查询预编译语句。修复代码示例找到漏洞文件中的危险SQL拼接处将其改造。DedeCms通常使用自带的$dsql类进行操作。// 修复前危险 $query “SELECT * FROM #__archives WHERE id IN ($ids) AND arcrank -1”; $dsql-ExecuteQuery($query); // 修复后安全 if ($ids ! ‘’) { // 1. 将逗号分隔的字符串转换为安全的整型数组 $idArray explode(‘,’, $ids); $safeIdArray array(); foreach ($idArray as $id) { $id intval(trim($id)); // 强制转换为整数过滤非数字字符 if ($id 0) { $safeIdArray[] $id; } } if (!empty($safeIdArray)) { // 2. 构造参数化查询的占位符 $placeholders implode(‘,’, array_fill(0, count($safeIdArray), ‘?’)); $query “SELECT * FROM #__archives WHERE id IN ($placeholders) AND arcrank -1”; // 3. 执行参数化查询假设$dsql支持 // 需要查看DedeCms $dsql类是否有如ExecuteQueryM、BindQuery等方法。 // 更通用的方式可能是使用PDO或mysqli预处理。 // 这里演示一种基于现有类可能的安全执行方式如果类不支持则需升级底层DB类 $dsql-SetQuery($query); foreach ($safeIdArray as $index $value) { $dsql-BindParam($index1, $value, ‘int’); // 绑定参数指定为整型 } $dsql-Execute(); } }如果$dsql类非常老旧不支持预处理那么必须进行严格的输入过滤和类型强制转换。对于IN子句中的数字ID最有效的方法就是上面展示的explode-intval- 重新组合。确保最终拼接进SQL的每一个部分都是经过intval处理的整数。5.2 长期安全开发规范修复一个具体漏洞是治标建立安全编码习惯才是治本。最小权限原则数据库连接账户不应使用root应为其分配仅能满足CMS需求的最小权限通常是SELECT, INSERT, UPDATE, DELETE谨慎授予DROP, FILE等权限。输入验证与过滤白名单优于黑名单对于类型明确的数据如ID是数字使用intval()、floatval()、ctype_digit()进行强制转换或验证。对于字符串根据上下文进行严格过滤。如果期望是字母数字就用正则preg_match(‘/^[a-zA-Z0-9]$/’, $input)验证。永远不要信任$_GET、$_POST、$_REQUEST。使用参数化查询/预编译语句这是防止SQL注入的终极武器。无论是PDO还是MySQLi都提供了完善的预处理支持。即使像DedeCms这样使用自封装数据库类也应在其内部实现预处理机制。转义是最后一道防线而非唯一防线addslashes()、mysql_real_escape_string()已废弃只能处理字符串上下文中的特殊字符对于数字型注入无能为力。且转义逻辑依赖数据库字符集不够可靠。错误信息处理生产环境务必关闭PHP的display_errors并将错误日志记录到文件。避免将数据库错误信息直接暴露给用户这会给攻击者提供大量线索。定期更新与安全审计对于仍在使用的老系统定期关注官方安全更新。对核心业务代码尤其是用户输入处理、数据库操作、文件上传、命令执行等模块进行定期的代码安全审计或使用自动化工具扫描。6. 从漏洞分析中提炼的防御思考分析CVE-2024-3148这类漏洞给我的启示远不止于一个补丁。它折射出Web应用安全中几个持久的问题第一历史债务的代价。许多像DedeCms一样辉煌过的老系统其代码库成型于Web安全意识相对薄弱的年代。字符串拼接SQL、全局变量滥用、过滤函数形同虚设等问题比比皆是。维护这样的系统犹如在沙地上盖楼修补一个漏洞可能又会引入新的问题。对于企业而言是持续投入资源进行艰难的安全加固和重构还是逐步迁移到更现代的、具有安全基因的框架如Laravel, ThinkPHP等是一个需要权衡的战略决策。第二安全边界的模糊性。这个漏洞是后台漏洞。传统观念认为“后台漏洞危害较小因为需要登录”。但在实际攻防中攻击链是环环相扣的。攻击者可能通过社会工程学获取一个弱口令可能通过一个前台的XSS漏洞窃取管理员Cookie也可能通过其他应用漏洞实现权限提升。一旦进入后台像这样的注入漏洞就成了攻击者的“数据收割机”。因此安全防御必须贯彻“纵深防御”原则不假设任何一道防线是绝对可靠的后台代码的安全标准必须与前台一致。第三工具与意识的脱节。现在有很多优秀的SAST静态应用安全测试工具、IAST交互式应用安全测试工具和漏洞扫描器。它们能有效地发现类似SQL注入的代码缺陷。但工具不能替代开发者的安全意识。如果开发者不理解“为什么参数化查询是安全的”而只是机械地套用那么他可能会在其他地方如动态表名、排序字段错误地再次使用拼接。安全培训必须将原理讲透让安全的编码方式成为肌肉记忆。在我个人的渗透测试经历中遇到类似的老系统往往能通过系统地测试其后台的批量操作、数据导出、搜索过滤等功能发现不止一处的注入点。一个实用的排查清单是凡是接收用户输入并用于数据库查询的地方尤其是那些涉及IN、ORDER BY、LIKE、WHERE字段名或表名动态拼接的地方都是需要重点审计的高危区域。最后对于仍然在使用DedeCms这类系统的管理员我的建议是立即检查官方是否已发布针对CVE-2024-3148的补丁并尽快应用。如果官方已停止维护应强烈考虑将系统迁移至更活跃、更安全的平台。在迁移前务必实施严格的主机层防护如WAF、网络隔离和最小权限访问控制以降低被攻击的风险。安全是一个持续的过程而非一劳永逸的状态。