Pikachu靶场实战:搜索型SQL注入闭合与联合查询详解 很多刚入门Web安全的朋友在靶场练习SQL注入时常常会遇到一个困惑明明跟着教程输入了 or 11 --为什么有时能成功有时却毫无反应甚至直接报错问题往往出在“闭合”上。你以为你在拼接字符串实际上你连SQL语句的“门”都没敲对。今天我们就以Pikachu靶场中极具代表性的“搜索型注入”为例彻底拆解“字符型闭合”与“联合注入Union Inject”的结合运用。这不仅是Pikachu通关的必经之路更是理解真实世界中SQL注入漏洞形态的关键一步。很多人止步于此不是因为技术多难而是没搞懂后台代码究竟是如何拼接你的输入以及你该如何“礼貌”地嵌入自己的恶意代码。本文将带你从零开始完成一次完整的攻击链分析从判断注入类型、测试闭合方式到利用联合查询获取数据库信息。你会发现真正的渗透测试思维过程远比执行那几个固定Payload重要得多。1. 这篇文章真正要解决的问题为什么“搜索型注入”更难闭合在SQL注入中根据用户输入被拼接进SQL语句的方式主要分为数字型和字符型。数字型简单直接如id$id而字符型则复杂一些如name$name你的输入会被单引号包裹。但“搜索型注入”是一种更特殊的字符型注入。它通常用于实现搜索功能后台SQL语句可能长这样SELECT * FROM articles WHERE title LIKE %用户输入%或者为了更精确的模糊匹配开发者可能会写成SELECT * FROM articles WHERE title LIKE %用户输入% AND status1看到问题了吗你的输入$keyword被包裹在LIKE %...%这个结构中。这意味着如果你想注入你不仅要闭合包裹你输入的那个单引号还要处理掉SQL语句中原本就存在的那个前百分号%和后百分号%以及可能存在的后续查询条件如AND status1。这就是搜索型注入的核心难点你需要构造的Payload必须能同时“消化”掉SQL语句中预置的百分号通配符和后续语句让整个语句语法正确且执行你的恶意查询。很多新手在这里折戟就是因为只考虑了闭合引号没考虑通配符和后续语句的“占位”。Pikachu靶场的“搜索型注入”关卡完美复现了这种场景。我们将通过它学习如何系统性地解决这个问题。2. 环境准备与靶场搭建在开始实战之前你需要一个可用的Pikachu靶场环境。方案一使用Docker推荐最快捷如果你已经安装了Docker和Docker Compose这是最干净、隔离性最好的方式。搜索或准备一个Pikachu的Docker镜像例如area39/pikachu。运行容器docker run -d -p 8080:80 area39/pikachu访问http://localhost:8080即可看到Pikachu首页。方案二本地集成环境如PHPStudy、XAMPP从Pikachu的GitHub仓库https://github.com/zhuifengshaonianhanlu/pikachu下载源码。将源码文件夹例如命名为pikachu放置到你的Web服务器根目录下如PHPStudy的WWW目录XAMPP的htdocs目录。访问http://localhost/pikachu进行安装。根据提示初始化数据库通常需要你手动创建一个数据库然后执行安装脚本。安装成功后即可访问首页。确保环境就绪访问靶场在左侧导航栏找到“SQL-Inject”-“搜索型注入”。你应该能看到一个简单的搜索框提示你“试试在搜索框里面输入一些正常的内容例如‘kobe’”。这个页面就是我们今天的“战场”。3. 核心概念联合注入Union Inject与闭合Closure在深入实战前我们必须厘清两个核心概念这决定了你Payload构造的成败。3.1 联合注入Union Inject的本质UNION是SQL语句中的一个操作符用于合并两个或多个SELECT语句的结果集。关键前提是每个SELECT语句必须拥有相同数量的列且列的数据类型也必须相似。在注入中我们利用UNION将我们精心构造的、用于窃取数据的SELECT语句“粘”到原始查询后面一起执行。例如 原始查询SELECT title, content FROM news WHERE id1注入后SELECT title, content FROM news WHERE id1 UNION SELECT username, password FROM users --这样数据库返回的结果集就同时包含了新闻内容和用户表的数据。联合注入是回显型注入中最强大、最直接的信息获取手段。3.2 闭合Closure为你的Payload铺平道路“闭合”是注入成功的前提。它的目标是让你输入的Payload能够“无缝嵌入”到原始的SQL语句中形成一个语法完全正确的新语句。以字符型注入为例 原始语句SELECT * FROM users WHERE name$input如果你直接输入admin语句是SELECT * FROM users WHERE nameadmin语法正确。 但如果你想注入输入 or 11语句变成了SELECT * FROM users WHERE name or 11。注意看第一个单引号闭合了name中的引号。然后我们输入了or 11。最后SQL语句末尾那个原本用于闭合的单引号现在闭合了我们字符串1中的引号。整个过程我们“消化”掉了SQL语句中用于包裹用户输入的那个引号并确保了整个语句的引号配对正确。这就是“闭合”。对于搜索型注入闭合的挑战更大因为我们要处理的是LIKE %$input%这个结构。4. 实战第一步探测与确认注入点渗透测试的第一步永远是信息收集与探测盲目攻击是大忌。4.1 正常功能测试在搜索框输入kobe并提交。页面正常返回了包含“kobe”关键词的结果。这说明搜索功能是正常工作的。4.2 初步异常探测尝试输入一个单引号并提交。观察结果页面很可能报错或者返回了与输入kobe时完全不同的结果例如无结果、错误提示。这是一个强烈的信号表明我们的输入被直接拼接进了SQL语句并且破坏了其语法结构因为多了一个单引号。后台猜测此时的SQL语句可能变成了SELECT ... WHERE title LIKE %%由于引号不匹配而报错。4.3 尝试基础闭合我们的目标是构造一个永真条件让查询返回所有结果。尝试输入 or 11 #。用于闭合LIKE %中在%后面的那个引号。注意是闭合%这个组合中的引号让%成为我们输入字符串的一部分。or 11构造一个永远为真的条件。#在MySQL中#是行注释符用于注释掉SQL语句中后续的所有内容包括那个可能存在的后百分号%以及AND status1之类的条件。提交后如果页面返回了所有数据而不是只包含某个关键词的数据那么恭喜你注入点存在且基础闭合成功这证明后端查询大概是LIKE %$input%的形式且#成功注释了后续语句。如果#不行可能因为编码或过滤可以尝试--注意--后面有一个空格。在MySQL中--也是行注释。重要思考为什么是 or 11 #而不是 or 11 --这取决于原始SQL的写法。如果原始语句是LIKE %$input%我们用闭合前引号后输入的内容就和%拼接在了一起。#注释掉的是%及之后的部分。这是一个需要根据实际情况调整和测试的过程。5. 实战第二步确定字段数Order By联合注入的前提是知道前面SELECT语句查询的列数。我们使用ORDER BY子句来探测。ORDER BY n表示根据第n列进行排序。如果n超过了实际列数数据库就会报错。在搜索框输入kobe order by 1 #。提交后页面正常显示。递增数字测试kobe order by 2 #kobe order by 3 #kobe order by 4 #...关键观察点当输入kobe order by X #时页面正常而输入kobe order by X1 #时页面报错或返回异常如空白、错误提示那么字段数就是X。假设我们测试到order by 3正常order by 4报错那么原始查询的字段数就是3。Payload构造解析kobe完成了初步闭合order by 3是我们的探测语句#注释掉后续所有代码保证语法正确。6. 实战第三步寻找回显点Union Select知道字段数假设为3后我们使用UNION SELECT来确认哪些字段的内容会显示在页面上。构造Payloadkobe union select 1,2,3 #这里1,2,3是我们填充的占位数据。如果联合查询成功且页面有回显那么这些数字1、2、3可能会出现在页面的某些位置例如标题、内容区域代替原本的数据。提交Payload。观察页面仔细查看返回的页面源代码和显示内容。你可能会发现页面上的某个位置显示了数字“2”和“3”而“1”可能没显示。这说明第2和第3个字段是回显点即页面上显示的数据来源于原始查询的第2列和第3列。为什么这么做我们需要知道把我们的“窃取数据”的查询语句放在UNION SELECT的哪个位置才能让结果在页面上显示出来。例如如果只有第2列回显那么我们应该把想获取的数据如database()放在UNION SELECT的第二个位置。7. 实战第四步利用联合查询获取信息现在我们有了武器联合注入、知道了敌人阵型字段数3、也找到了突破口回显点2和3。可以开始系统性地获取数据库信息了。这是一个标准的“数据库-表-列-数据”的流程。我们将回显点2和3替换为我们需要查询的函数或语句。7.1 获取当前数据库名Payload:kobe union select 1, database(), 3 #database()函数返回当前操作的数据名称。提交后在页面对应回显点2的位置你应该能看到数据库名例如pikachu。7.2 获取数据库中的所有表名在MySQL中表信息存储在information_schema.tables中。 Payload:kobe union select 1, group_concat(table_name), 3 from information_schema.tables where table_schemadatabase() #table_schemadatabase()限定只查询当前数据库的表。group_concat(table_name)将所有的表名连接成一个字符串返回避免因多行回显导致页面只显示第一行。提交后你可能会看到类似httpinfo,member,message,users,xssblind...的结果。这里我们重点关注users表。7.3 获取目标表如users表的所有列名表结构信息存储在information_schema.columns中。 Payload:kobe union select 1, group_concat(column_name), 3 from information_schema.columns where table_schemadatabase() and table_nameusers #table_nameusers指定查询users表。 提交后你可能会看到类似id,username,password,level的结果。我们显然对username和password字段最感兴趣。7.4 最终一击提取用户名和密码现在我们可以直接从users表中查询数据了。 Payload:kobe union select 1, username, password from users #或者为了更清晰地查看可以使用 Payload:kobe union select 1, concat(username, :, password), 3 from users #concat()函数将用户名和密码用冒号连接起来。 提交后在页面的回显位置你应该就能看到梦寐以求的用户名和密码可能是MD5哈希值了。8. 完整攻击链Payload回顾与解析让我们把整个攻击链串联起来理解每一步Payload是如何与后台SQL语句交互的。假设后台原始SQL为SELECT id, title, content FROM articles WHERE title LIKE %$keyword% AND status1 ORDER BY id DESC探测与闭合输入 or 11 #拼接后SQLSELECT id, title, content FROM articles WHERE title LIKE % or 11 #% AND status1 ORDER BY id DESC解析LIKE %这部分%是通配符是引号。我们输入的闭合了这个引号。LIKE %本身是一个模式匹配任何以空字符串结尾的内容即所有内容。or 11使条件永真。#注释掉了后面所有的字符% AND status1 ORDER BY id DESC消除了它们的影响。因此查询返回所有文章。确定字段数输入kobe order by 3 #拼接后SQLSELECT id, title, content FROM articles WHERE title LIKE %kobe order by 3 #% AND status1 ORDER BY id DESC解析LIKE %kobe匹配以“kobe”结尾的标题。order by 3按第三列content排序。因为原始查询就是3列所以语法正确执行成功。联合查询获取信息输入kobe union select 1, database(), 3 #拼接后SQLSELECT id, title, content FROM articles WHERE title LIKE %kobe union select 1, database(), 3 #% AND status1 ORDER BY id DESC解析前半部分查询可能返回0条或若干条“kobe”相关结果。UNION连接的后半部分select 1, database(), 3固定返回一行数据(1, pikachu, 3)。页面回显时这行数据也会被显示出来从而暴露出数据库名。9. 常见问题与排查思路QA在实战过程中你可能会遇到以下问题问题现象可能原因排查方式解决方案输入‘单引号后页面无变化或直接跳转/报500错误。1. 输入被前端或后端过滤/转义。2. 靶场环境配置有误错误处理方式不同。1. 查看页面源代码确认输入是否被原样提交。2. 尝试其他特殊字符如、\进行测试。3. 检查靶场数据库连接和PHP错误日志。1. 尝试大小写、双写、编码绕过等技巧在Pikachu基础关卡中通常不需要。2. 确保靶场环境如Apache/MySQL服务正常运行。使用#注释无效order by测试一直报错。1. 数据库不是MySQL如SQLite。2.#在传输过程中被URL编码或处理。3. 原始SQL语句结构并非LIKE ‘%$input%’可能有其他闭合方式。1. 尝试使用--注意有空格进行注释。2. 尝试使用%23#的URL编码提交。3. 尝试更通用的闭合‘ or ‘1’’1‘ or ‘1’’1手动构造闭合。1. Pikachu默认使用MySQL优先尝试--或--。2.最有效的方法系统性地测试闭合尝试‘、“、‘)、“)、‘))等组合配合or 11观察页面是否返回所有数据。union select 1,2,3执行成功但页面上看不到数字回显。1. 回显点不在前端直接显示可能在页面源码、HTTP响应头或其他位置。2. 联合查询的前半部分有结果页面只显示了第一条数据。1. 右键查看网页源代码搜索1,2,3。2. 使用Burp Suite等工具查看原始HTTP响应。3. 尝试让前半部分查询无结果例如输入一个不存在的值asdf‘ union select 1,2,3 #。让原始查询结果为空是确保联合查询结果被显示的关键技巧。在搜索型注入中可以输入一个肯定不存在的词如不存在关键词‘ union select 1,2,3 #。使用group_concat查询表名时返回结果不完整或被截断。group_concat函数有长度限制默认1024字节。1. 使用substring或limit分批次查询。2. 查询group_concat_max_len变量并临时修改在靶场中不推荐。使用分页查询kobe‘ union select 1, table_name, 3 from information_schema.tables where table_schemadatabase() limit 0,1 #然后修改limit 1,1、limit 2,1依次查看。10. 最佳实践与深入思考通过Pikachu的搜索型注入关卡我们完成了一次标准的手工注入流程。但真正的学习不止于此自动化工具只是辅助Sqlmap等工具固然强大但手工注入的过程是理解漏洞原理、锻炼调试思维和Payload构造能力的基石。在复杂场景如过滤、编码、非常规闭合下手工能力往往能发现自动化工具无法识别的问题。关注错误信息开发环境或配置不当的生产环境的SQL错误回显是渗透测试的“金矿”。它不仅能确认注入点有时还能直接暴露数据库结构、路径等敏感信息。在Pikachu中你可以尝试打开PHP的display_errors设置观察更详细的报错。闭合方式的多样性本文重点讲了LIKE ‘%$input%’的闭合。现实中还有数字型、LIKE ($input)、IN (‘$input’)、ORDER BY $input等多种情况其闭合方式各不相同。核心思路永远是推测后台SQL的拼接逻辑然后构造Payload去“完成”它。防御视角作为开发者如何防止此类漏洞最根本的方法是使用参数化查询Prepared Statements或ORM框架从根本上杜绝用户输入被解释为SQL代码的可能。其次对输入进行严格的过滤和转义设置数据库最小权限原则关闭错误回显都是有效的防御措施。搜索型注入的闭合是SQL注入学习路上一个经典的思维训练。它要求你从黑盒测试中逆向推断出后台代码的拼接逻辑。掌握它你不仅能够通关Pikachu的这一关卡更能举一反三应对真实世界中更隐蔽、更复杂的注入场景。建议你将本文中的Payload逐一在靶场中测试并尝试修改其中的细节观察不同的返回结果这比死记硬背Payload要有效得多。