1. 动手前先分清Pikachu靶场里这10种注入到底怎么分类1.1 为什么新手首选Pikachu如果你是第一次接触SQL注入千万不要去公网上随便找个站点练手。我见过太多新人直接对着线上业务乱打结果要么被防护组件拦下来要么把别人测试数据洗了一遍最后被请去喝茶。正规的做法是在本地搭一套开源的漏洞靶场比如Pikachu。它把OWASP Top 10里最经典的漏洞类型都收纳成了独立“练习关卡”尤其是SQL注入这一块从数字型、字符型、搜索型到宽字节几乎覆盖了入门阶段能遇到的绝大多数形态。这套靶场的优势在于“开箱即用”。它不要求你有多强的运维能力只需要把PHP环境和MySQL准备好解压后改一下数据库连接配置就能跑起来。靶场内部自带初始化数据你不需要自己造表造数据所有注入点都放在明显的位置甚至还在页面里提示了注入类型。对于新手来说这相当于拿到了一张标注好的地图你只要顺着路线把每个坑都踩一遍就能建立对注入漏洞的立体认知。还有一个关键点Pikachu的注入点覆盖了GET、POST、HTTP头、数据库操作语句等多个位置不只是页面URL里那一种简单形态。很多人学SQL注入只会在URL后拼接参数遇到POST表单注入、请求头注入就完全懵了而Pikachu正好把这些场景全部补齐。练完这一轮你再去看真实环境里的注入点起码知道“该往哪里看”。1.2 分类维度参数位置、回显方式、闭合形式很多新手面对10种注入会觉得乱其实它们可以按三个维度来拆分。第一个维度是“参数提交位置”对应的是GET型、POST型和HTTP头型。GET型参数直接拼在URL上适合用浏览器或者Burp改包来测试POST型参数在请求体里你在地址栏里改是没用的必须在抓包工具里改HTTP头型则藏在User-Agent、Referer这些字段里属于非显眼位置容易被忽略。第二个维度是“数据库返回结果是否可见”这决定了你用“联合查询”还是“盲注”。如果页面会直接把查询结果显示出来或者把SQL报错打印出来那就属于“有回显”可以快速拿数据如果页面只有“正常/异常”或者“时间快/慢”两种反馈那就只能走盲注路线。第三个维度是“SQL语句的拼接方式”。数字型直接把参数当数值用字符型外面套了单引号搜索型可能还拼了百分号宽字节则是因为编码转换导致引号被绕过。你得先搞清楚程序是用什么方式把参数塞进SQL语句里的才能构造出闭合语句的payload。Pikachu的每个注入点都在页面里标注了类型目的就是让你把“类型特征”和“payload写法”一一对应起来。等练完这10个点你再遇到一个未知的注入点就会主动去想参数在哪里提交回显能不能看到语句是拿它当数字还是当字符串2. 从数字型打到字符型两种最典型的注入链路2.1 数字型注入联合查询三步走Pikachu的数字型注入在“SQL注入”模块下点进去是一个球员信息查询的列表URL里的参数是id比如pikachu/vul/sqli/sqli_id.php?id1。这种参数直接被拼进SQL语句时程序大概是这样写的select id, name, email from member where id $id注意这里没有引号参数是纯数字所以先拿它试“真和假”最合适。把id1改成id1 and 11页面正常返回改成id1 and 12页面就查不到数据了。这两个结果一对比基本可以确定这个点存在数字型注入而且SQL语句里没有对它做任何过滤。确定存在注入之后接下来就是判断字段数。用order byid1 order by 1 -- 正常 id1 order by 2 -- 正常 id1 order by 3 -- 正常 id1 order by 4 -- 报错当我把order by从1一路往上试发现到4时报错说明这条查询结果只有3列。这一步的意义在于为联合查询腾位置。联合查询要求前后两个select返回的列数一致否则数据库会直接报错之前的工作就白做了。字段数确定后把查询结果“挤空”然后联合注入id1 and 12 union select 1,2,3页面会显示2和3说明第二列和第三列的内容被回显到了页面上。接下来把数字位替换成敏感信息即可id1 and 12 union select 1,database(),version()得到当前数据库名和版本。我一般还会顺手测一下user()确认连接数据库的账号权限。到这里一次标准的数字型注入就算复现完了。整个过程中最重要的思路是先用真假对比确认注入存在再用order by确认列数最后用union获取数据。这“三步走”在后续字符型、搜索型甚至其他数据库里都通用。2.2 字符型注入闭合引号与注释符字符型注入和数字型的最大区别就是参数外面包了一层引号。Pikachu的“字符型注入get”模块页面点“查询”之后URL里会出现name参数后台拼接方式大概是select id, name, email from member where name $name此时直接传1 and 11是无效的因为$name被单引号包住你输入的内容被当成字符串的一部分。要让后面的SQL代码生效必须先把这个引号“闭合”掉再用注释符把多余的单引号干掉。最常用的payload是nameadmin and 11 -- 最后的--是MySQL注释符它会注释掉原本闭合字符串的那个单引号。注意我写的是“双减号加空格”在MySQL里也可以写成#。如果你想用联合查询可以这样nameadmin and 12 union select 1,database(),version() -- 这里有个新手容易踩的坑在某些环境下--后面必须带一个空格否则注释不生效。Pikachu是MySQL环境--和#都能用但你去其他靶场或者真实环境最好先试#再试--。字符型注入复现的意义在于让你养成一个习惯不要一上来就写union先想清楚“我要闭合什么、注释掉什么”。2.3 搜索型与XX型容易被新手忽略的两个变种Pikachu里还有两个特殊变种一个是搜索型注入一个是XX型注入这里的“XX”指的是闭合方式不是单纯的引号。搜索型注入通常出现在站内搜索功能里后台拼接语句类似select id, name, email from member where name like %$name%因为搜索本身就是模糊匹配程序会把你的关键字放进一对百分号中间。测试的时候输入a正常输入a%正常输入a报错输入a and 11 --能正常返回数据那就可以确认存在注入。payload写法其实是字符型的升级版只不过你需要考虑百分号的存在namea and 12 union select 1,database(),version() -- 这里最开始用的a会顶掉左边的百分号最后的--会注释掉右边的%于是SQL就变成了完整的注入语句。XX型注入则是闭合方式更像“代码逻辑”后台拼接可能是select id, name, email from member where id ($id)参数既被单引号包住外层又套了括号。测试时直接传1 and 11没用传1会报错传1) and 11 --才能绕过。这种类型是专门为了告诉你闭合方式不能靠猜要观察页面的报错信息或者尝试多种括号组合。很多人在真实环境里一上来就固定用遇到括号型的就直接卡死。Pikachu把这几个变种放在一起就是为了打破你“只会一种payload”的惯性。3. 增删改场景与堆叠注入别只盯着select3.1 Insert/Update/Delete注入的利用思路很多人练SQL注入只盯着查询语句觉得后面带数据的select才是重点但Pikachu里的“insert/update/delete注入”模块会直接把你拉到另一个场景写入、更新、删除功能的参数同样能“带病运行”。这个模块的页面长得很像后台管理界面可以新增用户、修改用户信息、删除记录。比如新增用户时后台可能是这样拼接的insert into member(id, username, password) values ($id, $username, $password)此时你在用户名输入框里填入的值会直接进入insert语句的字符串位置。要注入必须先闭合前面的单引号然后想办法处理后面括号里的值。一个常见的思路是“用子查询代替当前值” or extractvalue(1, concat(0x7e, (select database()))) -- 这样会把数据库名拼到报错信息里返回在页面上。这是因为在insert语句的values区域里你闭合引号后插入了一个子查询表达式。对update和delete也是同理它们同样会把参数拼进SQL语句区别只在于你利用时需要考虑原语句的语法结构。比如update场景中你可以在set username...的赋值位置注入delete场景则可以在where id...的位置注入。这个模块真正要训练的是“SQL语句结构感”。select注入时你只要关注闭合和注释但insert/update/delete注入时你还得保证你插入的payload在语法上能让整条语句保持成立不能破坏后面的括号和逗号。新手容易犯的错是在insert里用了剩一个单引号的payload结果整条语句语法错误页面报错一堆却不知道哪一步错了。建议复现时把后台SQL语句先写在本地文本里自己拼一遍再拿到靶场里去验证。3.2 堆叠注入一个分号的价值堆叠注入是Pikachu里一个单独模块它利用的是“一个请求可以执行多条SQL语句”的特性。之前的注入都是在原语句上做修改而堆叠注入是直接结束当前语句再执行一条全新的语句。比如页面传入id参数后台查询是select id, name, email from member where id $id如果你输入1; delete from member where id 2整个语句会变成select id, name, email from member where id 1; delete from member where id 2MySQL会依次执行两条语句结果是数据被删除。堆叠注入之所以单独拿出来讲是因为它和联合查询有本质区别联合查询只能往后追加select类型的数据查询而堆叠注入可以执行insert、update、delete甚至调用存储过程。当然在真实环境里有很多数据库驱动和中间件会禁止多条语句同时执行比如一些ORM框架默认就只允许单条SQL所以堆叠注入的可利用条件比普通注入苛刻得多。Pikachu里这个模块建议你重点观察“能否回显”的区别。当你执行1; insert into member(username, password) values(aaa,bbb)时页面上不一定能看到插入结果但你再打开查询模块就会发现多了一条记录。这就是典型的“不回显但是生效”。我个人的习惯是每执行一条堆叠payload都去对应的数据展示页面确认一下数据是否真的变了而不是只看当前页面的反应。这个习惯在真实渗透测试里非常重要因为很多注入点都是盲的你需要通过二次确认来验证命令是否执行成功。4. 盲注三兄弟报错、布尔、时间4.1 报错注入让数据库把错误说出来报错注入在Pikachu里是独立的一个模块它解决了一个很尴尬的问题有些页面把SQL语句的错误信息直接打印出来但查询结果不会回显到页面。这时候union没得用了你得想办法让数据库“报错”并且把你要的数据带进错误信息里。MySQL里最常见的报错函数是updatexml()和extractvalue()它们本来是用来处理XML文档的当传入的XPath表达式格式不对时MySQL会把表达式内容原样返回到错误信息里。Pikachu的报错注入点大概是select id, name, email from member where id $id我们可以用id1 and updatexml(1, concat(0x7e, (select database())), 1)这里concat(0x7e, (select database()))构造出一个以~开头的字符串updatexml()第二个参数要求是合法的XPath路径这个字符串不合法于是数据库把它当作错误信息的一部分打印出来。页面会显示类似XPATH syntax error: ~pikachu数据库名就泄露了。报错注入看起来比盲注舒服但要注意长度限制extractvalue()和updatexml()的错误信息最多只显示32个字符。如果你想查的数据很长比如字段值是一长串就得配合substr()分批拿。Pikachu里的报错注入模块并不是让你只跑一次就完事而是建议你把目标数据拆解成多段逐段拼接。我个人会用二分法来计算字符位置比如id1 and updatexml(1, concat(0x7e, (select substr(database(),1,1))), 1)把database()换成库名跑一次拿到第一个字符再改位置参数拿下一个字符。虽然麻烦但这一步能帮你彻底理解“报错信息是可控输出点”这件事。4.2 布尔盲注逐字符猜解的体力活布尔盲注的页面特征是你只能看到“正常”和“异常”两种结果。Pikachu的布尔盲注模块查询时如果条件成立页面返回“查询成功”之类的字样不成立则页面回显“查询不到数据”。后台SQL大概是select id, name, email from member where id $id但这个查询结果不会直接展示表数据你只能靠页面的“真/假”信号来判断。所以盲注的核心思路就是把SQL语句改造成一个“条件判断”然后根据页面反馈去猜数据。比如判断当前数据库名的第一个字符是不是A可以这样构造id1 and substr(database(),1,1)a如果页面正常返回说明数据库名的首字符是a。把a换成b、c一直到全部字符猜完你就拼出了整个库名。这个过程用手工做会非常痛苦一个库名、表名、字段名猜下来请求次数几百次起步。所以我强烈建议你第一次复现时写一个简单的Python脚本来做这件事。核心逻辑其实很简单import requests import string chars string.ascii_letters string.digits _. result for i in range(1, 20): for c in chars: url fhttp://127.0.0.1/pikachu/vul/sqli/sqli_blind_b.php?id1 and substr(database(),{i},1){c} r requests.get(url) if 正常标记 in r.text: result c break if len(result) i: break print(result)这个脚本里“正常标记”要根据靶场实际页面的特征字符串来替换。我自己在跑的时候发现直接判断整条SQL执行后页面内容的差异其实很容易写错最好是先手动请求两次一次真条件一次假条件然后用diff的方式找出区分点再把这个区分点写在脚本里。布尔盲注虽然体力活但它训练的是你对条件表达式的控制能力。4.3 时间盲注没有回显就用延迟说话时间盲注比布尔盲注更极端页面不管你条件真假反馈都一样没有任何差异。Pikachu的时间盲注模块就是如此你输入1和输入1 and 11页面长得一样输入1 and 12也一样。此时唯一能利用的通道就是“时间”。MySQL里触发延迟的常用方法是sleep()配合if()条件函数id1 and if(substr(database(),1,1)a, sleep(3), 0)如果当前库名的首字符是aMySQL会睡3秒再返回页面响应时间明显变长如果不是则瞬间返回。通过多次测试延迟出现与否就能逐字符猜出数据。需要注意时间盲注受网络环境影响很大如果靶场放在远程服务器上延迟波动可能掩盖掉注入产生的延迟。我的建议是基准测试时多测几次正常请求的响应时间确定一个阈值比如正常响应是200毫秒那么sleep(3)至少会有3秒以上的响应比较明显。Pikachu的时间盲注复现时我喜欢用如果条件包一层sleep()因为这样能保证响应时间只和当前猜测的字符相关。还有一个小技巧判断完一个字符后下一轮脚本要重新测量基准延迟避免把网络抖动引起的长响应误判成真条件。实际写脚本时可以设置超时时间为5秒凡是超过2秒的请求都记作“真”低于1秒的记作“假”两次判定之间加一个短暂的退避间隔防止把靶场数据库的连接池打满。时间盲注是三种盲注里最慢、最容易被发现的一种但正因为如此它往往是真实场景里最后一个选择学会它是为了让你“多一张底牌”。5. 宽字节注入编码层的魔法攻防5.1 宽字节注入的原理与触发条件宽字节注入是中文网站时代非常经典的一种SQL注入。很多程序为了防注入会把用户输入里的单引号、双引号等特殊字符在前面加反斜杠转义比如输入会变成\这样在SQL语句里它就不再是字符串结束符而是一个普通字符。这种防护看起来没什么问题但一旦数据库连接使用了GBK这类多字节字符集就会产生编码层面的漏洞。攻击者传入一个%df%27程序看到%27是单引号于是给它加上反斜杠变成%df%5c%27。问题在于MySQL在解析GBK编码时会把%df%5c当成一个完整的汉字“運”后面的%27就变成了独立的单引号。本来用来转义单引号的反斜杠反而被“吃掉”成了汉字的一部分。这就是宽字节注入的基本原理。Pikachu里的宽字节注入模块后台拼接大概是select id, name, email from member where name $name同时代码里用了addslashes()或者类似函数来转义输入。直接输入1会被变成1\没有作用。但输入1%df时经过转义和GBK解码后%df和加上的反斜杠%5c合并成一个字符单引号成功逃逸。payload可以写成name1%df and 11 -- 页面返回正常说明注入成立了。再换成name1%df and 12 union select 1,database(),version() -- 就能把数据带出来。这里要注意%df在不同的字符集下并不总是有效比如在UTF-8环境下这种绕过就很难成功所以宽字节注入的前提是目标站点使用了GBK/GB2312这类多字节字符集。Pikachu靶场默认已经配置好这种环境你不需要自己去改PHP编码设置直接打开模块就能复现。5.2 实操复现与修复建议在Pikachu里复现宽字节注入有个坑很容易踩你要是直接在浏览器的URL地址栏里输中文编码后的payload浏览器可能会自动再做一次编码导致%df被解析成别的字符。我建议你使用Burp Suite的Repeater模块来发送请求这样能保证payload里的百分号编码不被二次篡改。具体操作是先打开宽字节注入模块在查询框里输入一个正常关键词然后拦截请求把参数值替换成1%df and 12 union select 1,database(),version() --再放行。如果页面出现了数据库名说明注入成功。修复宽字节注入的办法最彻底的不是继续加过滤而是从字符集统一上解决。把PHP和MySQL的连接字符集全部改成UTF-8同时在程序入口处设置SET NAMES utf8mb4让输入输出和数据库存储全部走同一种编码。另一种方式是使用参数化查询SQL语句模板和参数分离数据库驱动会自动处理特殊字符的转义从根上杜绝这类拼接问题。Pikachu作为攻击靶场不会修但我在实际项目里做过类似的代码审计见过很多团队以为“只要addslashes就能防注入”结果就是被这类编码绕过打得措手不及。复现宽字节注入的深层价值是让你明白安全防护不能靠“这里挡一下、那里转义一下”的补丁思路而要从数据流的源头统一约束。6. 复现过程中的常见问题与避坑清单6.1 高频问题速查表我在带新人复现Pikachu SQL注入时遇到最多的几个问题整理成了一张速查表。现象可能原因排查与解决办法输入1 and 11页面无变化不是数字型注入参数被当成字符串了改用字符型payload先测引号闭合order by一直不出错查询列数很多或者参数被过滤从1开始每次加1不要跳着试上限设到20左右联合查询页面只显示数字不显示表头列数判断错误返回union select 1,2,3时看页面出现哪几个数字以页面回显的位置为准字符型payload用了--不生效注释符后面没加空格或数据库不是MySQL换成#试试或者用--在URL里代替空格报错注入不报错函数名写错或数据库版本不支持先试extractvalue()再试updatexml()布尔盲注脚本判断不准页面特征字符串选错手动请求两个真假条件对比HTML差异找出唯一区分点宽字节注入输入%df无效果浏览器做了二次编码或数据库不是GBK用Burp的Repeater发送原始payload检查目标连接字符集这些问题是每一个刚开始打Pikachu的人几乎都会碰到的。尤其是--注释符的问题在URL里因为空格会被解析成%20或者被截断我建议直接改用#它在MySQL里同样能注释掉尾部内容而且不需要空格能省掉很多麻烦。6.2 个人实操心得打完这一整套靶场我自己最深的体会是SQL注入复现的核心不是背payload而是建立“判断链”。看到参数第一反应不是抄payload而是先判断类型、闭合、回显位置再决定用哪种方案。Pikachu把这10种注入按由浅入深的顺序排好本质上就是让你把这个判断链走十遍直到形成肌肉记忆。还有一个特别想提醒的细节每次提交payload之前先在本地把最终拼接出来的SQL语句写一遍。比如字符型是where name $name你把$name替换成你的payload看看会不会多出一个引号或者少一个注释符。很多新人报错后一脸懵就是因为他们根本不知道后台SQL长什么样。Pikachu的源码是开放的遇到猜不透的闭合方式直接去看后台代码这比盲试一百遍都有效。最后不要忽略“手工会话”的基本功。虽然现在有各种自动化工具但工具报错时你很难定位问题。手工把这个靶场全部打一遍之后你再去用工具会有一种“原来如此”的感觉。真实环境里的注入点不可能像靶场这样标注得清清楚楚而你练过这10种之后至少能在混乱的请求包里快速定位到参数位置判断出该走哪条路。这比记住一堆payload有用得多。
阅读完成 · 觉得有帮助?