1. 项目概述与核心价值
1.1 “SQL-inject”模块到底练什么
Pikachu这个名字,玩过漏洞靶场的人应该都不陌生。它是一套基于PHP+MySQL的漏洞练习平台,把Web安全里最常见的漏洞类型做成了一个个小实验,SQL注入、XSS、CSRF、RCE、反序列化、越权这些模块全都有。而“SQL-inject”这个模块,就是专门用来练SQL注入的。
你可能会问:SQL注入都听过八百遍了,有什么好练的?但真上手写payload的时候才会发现,很多东西不是“记得原理”就行的。Pikachu里的SQL注入模块把注入场景拆得很细——数字型、字符型、搜索型、POST型、盲注、宽字节注入等等。每一种背后的触发条件、拼接方式、绕过思路都不一样。在这上面完整走一遍,基本能把手工注入的底子打牢。
这个靶场适合谁?两类人:一类是刚接触Web安全,想找个环境从头理解SQL注入原理的初学者;另一类是已经会跑工具(比如sqlmap),但想搞明白工具背后到底在干什么的进阶学习者。对于前者,Pikachu能让你直接看到漏洞代码和正常代码的差异,理解“为什么这里能注入”;对于后者,它能帮你把注入的类型体系捋清楚,以后遇到工具绕不过去的情况,知道怎么手工补位。
1.2 为什么选Pikachu而不是其他靶场
市面上能练SQL注入的靶场不少——DVWA、SQLi-Labs、SqliLab、PortSwigger Web Security Academy,各有各的优势。但Pikachu有一个很突出的特点:它对“漏洞成因”的展示是最直白的。
DVWA的SQL注入模块偏向“功能演示”,SQLi-Labs偏向“注入技术思路训练”,但Pikachu在每个漏洞页面里会直接标注“漏洞产生的主要原因是什么”“对应的核心代码长什么样”。这对学习者来说特别友好——你不用去翻源码,页面本身就在教你怎么看问题。另外Pikachu是中文界面,模块命名也通俗(比如“字符型注入(get)”),对国内学习者来说门槛低很多。
更关键的一点是,Pikachu的注入点设计很贴近真实站点的常见写法。它不是把SQL拼装放在一个生硬的文件里,而是模拟了正常的业务逻辑——按ID查用户、按名字搜用户、登录表单等。你在这个靶场里的每一步操作,基本对应着真实渗透测试中会遇到的实际场景。练完之后再去做授权测试,不会有一种“靶场会,实战废”的落差感。
注意:Pikachu是漏洞测试平台,只建议在本地或授权的实验环境中使用。写这篇文章也是基于本地搭建的靶场环境,不涉及任何真实目标。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境搭建与靶场部署
2.1 本地环境准备
Pikachu基于PHP+MySQL,最省事的部署方式就是集成环境。Windows上用phpstudy(小皮面板),macOS上用MAMP或者直接Docker,Linux上可以用LAMP或者Docker,看个人习惯。
我本地用的是phpstudy,因为它的PHP版本切换和MySQL管理都比较直观。需要注意一个版本问题:Pikachu有些模块的代码写法比较老,用太高版本的PHP(比如8.x)可能会出现弃用函数警告甚至直接报错。建议PHP版本选5.6或7.x,我自己在7.4下跑得很稳。MySQL方面无所谓,5.x和8.x都能跑,8.x要注意一下认证插件问题,5.x省心一些。
选集成环境的另一个原因,是后续调试方便。比如你注入的时候想看SQL语句到底怎么拼的,可以直接在phpstudy里打开MySQL的通用日志,或者装一个Adminer/phpMyAdmin,现场看数据库里的数据变化,对理解注入逻辑帮助很大。
2.2 Pikachu部署与初始化
部署流程很常规:
- 从官网或GitHub下载Pikachu源码,解压到Web根目录(phpstudy默认是
WWW目录)。 - 修改数据库配置:进入
inc目录,打开config.inc.php,把数据库账号密码改成自己环境的配置。 - 在浏览器访问Pikachu首页,点击“初始化”按钮。它会自动建库建表,并写入基础实验数据。
- 初始化完成后,从首页进入“SQL-Inject”菜单,就能看到各个注入实验入口。
有一个小细节容易踩坑:初始化之后,如果页面显示数据库连接失败,大概率是MySQL账号没有远程权限,或者密码认证方式不对。本地环境直接把host改成127.0.0.1,账号用root,密码留空,基本都能解决。
初始化最大的价值在于:Pikachu自带了几个预设的用户表数据,比如users表里的用户名和密码,还有member表里的会员信息。注入后能不能拿到这些数据,就是你判断“注入是否成功”的依据。建议初始化后先去phpMyAdmin里看一眼这些表的字段结构和数据量,后面验证注入结果时会很有参考价值。
3. SQL注入原理与模块整体设计
3.1 注入的本质:把输入变成代码
理解SQL注入,只需要记住一句话:当应用程序把用户输入直接拼接到SQL语句中,并且没有做任何过滤或参数化处理时,用户输入就“变成”了SQL语句的一部分。你的输入不再只是数据,而是代码。
打个比方。正常人填查询表单时,系统执行的是:
sql复制SELECT * FROM users WHERE id = 1;
如果系统直接把用户输入的id拼进SQL,那么当输入变成1 OR 1=1时,执行的就是:
sql复制SELECT * FROM users WHERE id = 1 OR 1=1;
结果就是把表里所有记录都查出来了。这只是最简单的“破坏”方式。更高级一点的玩法是联合查询:
sql复制SELECT * FROM users WHERE id = -1 UNION SELECT username, password FROM users;
这就是SQL注入的“数据窃取”本质:利用联合查询把别的数据带到页面显示区。
Pikachu的SQL注入模块,本质上是把这些玩法拆成了不同的小实验,让你逐个上手。
3.2 Pikachu对SQL注入的分类逻辑
Pikachu的SQL注入模块包括以下入口:
- 数字型注入(GET)
- 字符型注入(GET)
- 搜索型注入(GET)
- 错误型注入(基于报错)
- 时间型盲注
- 布尔型盲注
- 宽字节注入
- POST型注入
- HTTP Header注入(部分版本有)
- 堆叠注入(部分版本有)
这个分类逻辑非常贴近实战:数字型对应WHERE id = $id这类无引号拼接;字符型对应WHERE name = '$name'这类有引号拼接;搜索型对应LIKE '%$keyword%';POST型对应登录、查询表单;盲注对应页面不显示数据库内容、只能靠布尔或时间判断的场景;宽字节注入对应gbk编码下%df'绕过转义的特殊情况。
一个个过关之后,你的脑子里会自然形成一张“注入类型—触发条件—利用方式—判断方法”的对应表。以后再碰到一个注入点,你就能快速判断它属于哪一类,该用什么手法。
提示:Pikachu每个模块页面下方都有“提示”和“核心代码”两个入口。做之前先自己尝试,卡住了再去看提示,最后对照核心代码理解漏洞根因。直接看答案会损失70%的训练价值。
4. 通关实操:从探测到利用的完整流程
4.1 第一步:数字型注入(GET)
数字型注入是最简单、最友好的入门实验。进入页面,你会发现一个输入框,提示“请输入id”。输入1,页面返回一条用户信息;输入2,返回另一条。
先判断注入点。在参数后面加一个单引号:
code复制id=1'
如果页面报错,或者显示结果为空/异常,大概率就是有注入漏洞。
接着用ORDER BY判断字段数:
code复制id=1 ORDER BY 1
id=1 ORDER BY 2
id=1 ORDER BY 3
当ORDER BY的列数超过实际查询列数时,页面会报错。假设表有3列,那么ORDER BY 3正常,ORDER BY 4报错,说明查询结果为3列。
再确定显示位。用UNION SELECT构造:
code复制id=-1 UNION SELECT 1,2,3
注意id=-1的目的是让前面的查询结果为空,这样页面显示的就是UNION查询的结果。如果页面上出现了1、2、3,那就说明显示位是第1、2、3列。
然后就可以开始偷数据了。查当前数据库名:
sql复制id=-1 UNION SELECT 1,database(),3
查所有表名:
sql复制id=-1 UNION SELECT 1,group_concat(table_name),3 FROM information_schema.tables WHERE table_schema=database()
查表里的字段:
sql复制id=-1 UNION SELECT 1,group_concat(column_name),3 FROM information_schema.columns WHERE table_name='users'
最后直接拖数据。这一步很有成就感,但更重要的是理解原理:为什么UNION能带出数据?为什么显示位要用1,2,3这种占位符?为什么用information_schema?搞明白这几个问题,数字型注入就算真正过关了。
4.2 第二步:字符型注入(GET)
数字型和字符型的区别,就在SQL拼接时有没有引号。数字型是id = $id,字符型是name = '$name'。
字符型注入最关键的一步,是闭合前面那个引号。输入:
code复制name=test' AND '1'='1
如果返回正常结果,说明单引号没有被过滤,而且我们已经闭合了SQL语句。
字符型注入的利用手法和数字型一样,也是UNION SELECT,只是payload的引号闭合要更小心:
code复制name=' UNION SELECT 1,database(),3 -- '
注意最后面的-- (两个短横线加一个空格)是注释符,把原来SQL语句末尾的引号注释掉。也可以使用#注释符,但在部分数据库版本里#可能不会被识别为注释,所以养成用-- 的习惯比较好。
字符型注入还有一个常见的坑:页面源码里可能带有HTML实体编码。你在注入点输入的单引号,如果浏览器显示成",那就说明前后端有实体转义,这时候注入不一定成立。所以建议用浏览器的开发者工具去观察“实际发出的请求”,别只看页面渲染。
4.3 第三步:搜索型注入
搜索型注入很有意思,它对应的是业务里最常见的“按关键词查找”功能。
Pikachu搜索型注入的SQL拼接逻辑是:
sql复制SELECT * FROM member WHERE name LIKE '%$name%'
注意这里有两个百分号,意味着用户输入夹在LIKE '%和%'之间。注入的思路是:先闭合前面的%',再用注释符把后面的%'注释掉。
payload可以这样构造:
code复制name=test%' AND 1=1 -- '
或者利用UNION:
code复制name=%' UNION SELECT 1,2,3 -- '
搜索型注入的难度不在于手法,而在于你是否能意识到“这个搜索框也可能存在注入”。很多人在实际测试中会忽略搜索类功能,这是一个值得记住的教训——搜索框往往直接拼接SQL,而且由于LIKE语句的存在,过滤逻辑更容易被绕过。
4.4 第四步:布尔盲注与时间盲注
布尔盲注和时间盲注是Pikachu里最锻炼思维的环节。它们的应用场景是:页面不显示数据库内容,只根据查询结果返回“存在/不存在”这两种状态(布尔盲注),甚至什么状态变化都没有,只能靠延迟判断(时间盲注)。
布尔盲注的判断思路是:用AND条件判断单个字符的ASCII值。
sql复制id=1 AND ASCII(SUBSTR((SELECT database()),1,1))>97
如果页面正常返回,说明数据库名的第一个字符的ASCII值大于97(也就是小写字母a)。通过二分法逐步调整比较值,最后就能确定每一个字符。
时间盲注也一样,只是把判断条件换成SLEEP:
sql复制id=1 AND IF(ASCII(SUBSTR((SELECT database()),1,1))>97,SLEEP(5),0)
如果页面响应时间变长,说明条件成立。
手工会非常累,但是理解原理特别快。我建议第一次练盲注的时候不要用脚本,纯手工二分法跑完一个字符串。等到你充分理解了判断逻辑,再去考虑写Python脚本自动化。有了这个基础,你以后看sqlmap的--technique=BT和--technique=T参数时,就不会觉得那是黑魔法。
技巧:判断字符用二分法,每判断一个字符大约需要5~7次请求,比逐字符爆破快得多。比如先判断是否大于100,再判断是否大于110,逐步逼近。这样每次截取一个字符,效率能提升一倍的量级。
4.5 第五步:宽字节注入
宽字节注入是一个比较特殊的场景,通常出现在数据库编码为GBK的情况下。Pikachu提供了这个实验,是因为很多老站点还在用GBK编码。
原理是这样的:当系统使用addslashes或mysql_real_escape_string这类函数过滤单引号时,它会在单引号前加上反斜杠\,变成\'。SQL解析器看到\'时,会把它当作一个普通的字符,而不是字符串边界。这样注入就失败了。
但如果数据库是GBK编码,而且PHP代码里面用了SET NAMES gbk这样的语句,就会产生一个字符集转换的漏洞。攻击者输入一个特殊字节序列%df':
- 在GBK编码中,
%df和后面的反斜杠\(即%5c)会拼成“運”这个汉字。 - 单引号前面的反斜杠被“消化”掉了。
- 单引号就脱离转义,恢复了字符串分隔符的作用。
所以宽字节注入的payload是:
sql复制name=%df' UNION SELECT 1,2,3 -- '
这个知识点平时用到的频率不高,但一旦遇到老系统,它就是唯一的入口。把原理吃透很有价值,尤其是“字符集歧义导致安全过滤失效”这个思路,在很多解码类漏洞里都能看到影子。
4.6 第六步:POST型注入与HTTP Header注入
POST型注入在Pikachu里的体现是登录表单的查询,比如输入用户名和密码后,后台执行:
sql复制SELECT * FROM users WHERE username='$name' AND password='$pass'
这里只需要在用户名参数上做注入,闭合单引号,注释掉后面的密码条件即可:
code复制username=admin' AND 1=1 -- '
password=任意值
POST注入的特点在于,你需要在Burp Suite或者浏览器开发者工具里修改请求体,而不是直接在URL上改参数。这要求你熟悉HTTP请求的报文结构,知道参数名是什么、如何修改请求体。
HTTP Header注入在部分版本的Pikachu里有,它把SQL拼接到了User-Agent或X-Forwarded-For这样的HTTP头里。这种注入在自动化扫描工具里经常漏报,因为很多工具默认只测请求参数,不测请求头。
面对这种场景,你需要在Burp里拦截请求,修改User-Agent字段为:
code复制' UNION SELECT 1,2,3 -- '
然后观察页面响应。这个模块的价值在于提醒你:SQL注入的入口不只是参数,HTTP头同样可能是漏洞点。
4.7 第七步:堆叠注入
堆叠注入(Stacked Injection)相对少见,但Pikachu如果包含这个模块,值得玩一玩。它利用的是PDO或多语句执行环境下,分号;可以分隔多条SQL语句的特性。
sql复制id=1; DELETE FROM users WHERE '1'='1
这条语句会先执行查询,再执行删除。这种注入的危害比普通UNION注入更大,因为你可以执行任意SQL语句——插入、更新、删除、创建账号都行。
但堆叠注入在实际场景中没那么常见,因为PHP的mysql_query函数本身不支持多语句执行。只有PDO的multi_query或部分数据库驱动才会允许。Pikachu设计这个模块的目的,更多是为了让你理解“边界闭合”和“语句分隔”这两个概念。练完有一个好处:以后看到分号敏感型过滤规则时,你会立刻反应过来它是在防什么。
5. 常见问题与排查技巧实录
5.1 环境与部署层面的坑
问题一:初始化后页面空白/连接失败。 大部分情况是config.inc.php里的数据库配置不对。检查数据库账号密码、端口、host设置。本地环境建议:
php复制$host = '127.0.0.1';
$user = 'root';
$pass = '';
$dbname = 'pikachu';
问题二:PHP版本太高导致报错。 Pikachu部分老代码在PHP 8.x会触发Deprecated警告,甚至因为函数移除而直接白屏。解决办法是换PHP 7.4或5.6。phpstudy可以一键切换,非常方便。
问题三:MySQL 8.x认证问题。 MySQL 8默认使用caching_sha2_password插件,老版本PHP扩展不支持。解决方案:把用户认证插件改为mysql_native_password,或者直接把MySQL降到5.7。
5.2 注入操作层面的坑
问题一:单引号闭合后页面还是报错。 大概率是SQL拼接里除了单引号还有别的括号或引号。用开发者工具看响应内容,把报错信息贴出来,分析拼接处是什么结构。Pikachu的“核心代码”功能这时候就很有用了。
问题二:UNION SELECT 不生效。 最常见的原因是前后两个查询的列数不一致。ORDER BY探测列数的时候,一定要从1开始逐个试,不能上来就猜3。
问题三:盲注判断不准确。 布尔盲注里,页面正常返回和异常返回的区别有时候很不明显。建议提前在本地环境里多打几个对照请求,搞清楚“TRUE时页面长什么样,FALSE时页面长什么样”,再做判断。
问题四:宽字节注入没反应。 先确认当前数据库编码是不是GBK。如果Pikachu首页或数据库连接配置用的是utf8,那你用宽字节payload肯定无效。只有在SET NAMES gbk的环境下,宽字节注入才成立。
5.3 我踩过的几个实战体会
第一,永远不要小看“报错信息”。Pikachu的实验环境默认没关报错,这是好事——报错是你最快的判断依据。但在真实环境里,管理员通常会关掉错误显示,所以训练的时候就要有意练习“不依赖报错”的判断思路,多练练盲注。
第二,习惯用Burp Suite而不是直接用浏览器地址栏操作。注入过程中经常需要回看完整请求、修改编码方式、重放请求。Burp Suite的Repeater是练习注入最顺手的工具。Pikachu的页面交互逻辑简单,但只有用Burp去模拟请求时,你才会注意到Content-Type、URL编码这些细节。
第三,多尝试“破坏性”测试之前记得备份环境。比如在堆叠注入里DELETE FROM users,删完了你后面的练习就没数据了。Pikachu虽然可以重新初始化,但老是重新初始化也会影响训练节奏。所以建议每做一个新实验前,都确保数据状态是干净的,或者先在草稿纸上推演一下payload再执行。
6. 注入点发现的通用方法论
6.1 为什么“找注入点”比“打注入”更重要
很多新手花大量时间研究payload怎么写,却忽略了最关键的一步——怎么找到一个潜在的注入点。
“找注入点”的本质,是找出所有可能被拼接到SQL语句里的用户可控输入。这些输入不只是URL参数。在Pikachu里,常见入口包括:
- 查询参数(
?id=1) - 表单字段(用户名、搜索框)
- HTTP请求头(
User-Agent、Referer、Cookie)
在真实站点中,入口更多、更隐蔽。比如分页参数、排序字段、导出筛选条件、JSON请求体里的嵌套字段,都可能被带入SQL语句。建议你在做Pikachu实验时,刻意把每个页面里的所有输入点都列出来,逐个测试,而不是只盯页面明显的输入框。
6.2 注入探测的标准化流程
我自己的习惯是:拿到一个可疑参数后,依次做下面几步。
先测单引号。在参数值末尾加',观察页面是否报错、返回是否变化。这一步能快速判断是否存在“字符型注入+引号可闭合”的可能。
再测整数型。尝试1、1 AND 1=1、1 AND 1=2三组请求。如果第一组正常、第二组正常、第三组异常,说明参数直接被拼到了数字比较里,极可能是数字型注入。
然后测布尔条件。尝试1 OR 1=1和1 OR 1=2,对比两次响应的差异。
最后是报错响应测试。如果页面开启报错,尝试提交1' AND extractvalue(1,concat(0x7e,(SELECT version()),0x7e)) -- 这类payload,看是否报错信息中带回数据。
这套流程走一遍,注入点的类型和可利用性基本就清楚了。Pikachu的优点在于,每个实验页面都自带不同类型的注入点,你完全可以把这套流程在这些页面上反复练习,练到变成肌肉记忆为止。
7. 防御视角:理解注入之后怎么防
7.1 参数化查询:最有效的一道防线
注入漏洞的核心问题,是“数据和代码没有分离”。所以最根本的防御方式,就是让用户输入永远只作为数据处理,而不参与SQL结构的生成。
参数化查询(Prepared Statement)就是这个思路。以PHP的PDO为例:
php复制$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([$id]);
提前编译SQL模板后,再传入参数值。此时无论参数值里包含什么字符,数据库都只把它当值处理,再也不跟SQL结构混在一起了。
类似的还有MySQLi的bind_param,Java的PreparedStatement,Python的cursor.execute(sql, params)。在Pikachu的“核心代码”里,你可以对比同一个功能在“拼接写法”和“参数化写法”下的差异——这个对比本身就很有教育意义。
7.2 纵深防御的思路
参数化查询是主线,但不能只依赖它。现实世界里的代码可能是老系统、框架自动生成的、维护了十几年的业务逻辑,参数化改造未必能一步到位。这时候就需要纵深防御。
输入校验方面,对业务参数做强类型校验,比如ID必须是正整数。这能挡掉一部分低级注入。
ORM和查询构造器的使用上,如果项目用了Laravel的Eloquent、Spring Data JPA这类查询框架,SQL拼接的机会就变少了。但要注意:就算用了ORM,如果你写了whereRaw、nativeQuery,一样会回到拼接SQL的老路上。
最小权限原则也值得强调,数据库账号尽量不要用root连业务库。Pikachu实验里用root没问题,但生产环境里,应用账号只给SELECT/INSERT/UPDATE/DELETE权限,即使发生注入,拿到的能力也有限。
7.3 用Pikachu加深对防御的理解
防御不是死记规则,而是要明白攻击者每一步在做什么。你在Pikachu注入实验里花费的每一分钟,都在帮你积累“攻击者如何思考”的视角。
我自己带过几个新人,有一个共同现象:只看防御类文章的人,遇到真实站点的SQL注入时,往往反应是“不会啊,POST参数我也测了”;但认真练过Pikachu整套注入模块的人,即使遇到一个新的注入形态,也能通过经典的“探测—判断—利用”流程快速定位问题。
所以,我强烈建议在练完所有注入实验后,再回头去阅读每个模块下面“核心代码”里的修复方式。你会发现,很多防御手段的本质,就是让“用户输入”回归“数据”的位置——不管是预编译、过滤还是转义,都是这个思路的不同表达。
8. 写在最后:练习注入的正确心态
最后想聊聊心态问题。SQL注入是Web安全最经典的入门漏洞类型,但也是“看起来简单、深入难”的类型。能背几个payload不算会,能在不同环境下快速定位注入类型、写出对应payload,才算是真正入门。
Pikachu的SQL注入模块是我见过的最适合系统训练这一能力的实验环境之一,但它的价值完全取决于你怎么用。如果只是照着别人的教程复制粘贴payload,一遍跑完什么也没记住,那确实有点浪费。我的建议是每做完一个实验,都自己复述一遍:这个注入点的特征是什么?SQL拼接逻辑是什么?我用的payload为什么能生效?如果换个过滤方式,我要怎么绕过?
这个“为什么”的训练,比通关本身重要得多。等你习惯了问为什么,再去看真实业务项目里的SQL查询逻辑,就会有完全不同的感觉——你会下意识地扫描每一个可能被拼进去的参数,像一个安全工程师一样思考问题。这算是这个靶场带给我的最大收获了。
