ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SQL注入靶场实战:Pikachu平台从搭建到通关的完整渗透笔记

SQL注入靶场实战:Pikachu平台从搭建到通关的完整渗透笔记 1. 靶场环境准备从零搭建一套可复现的注入实验环境先说句实话。SQL注入的教程在网上一抓一大把但很多人看完依然不会做题、不会挖洞核心问题就一个缺少一套能随手复现、反复折腾的靶场环境。纸上谈兵永远练不出手感Pikachu这个开源漏洞测试平台的价值就在这儿——它把Web安全里最常见的漏洞类型全部集成在一起SQL注入模块更是从入门到进阶单独铺了一条完整的练习路线非常适合拿来当第一套手术练习台。Pikachu这个名字听着可爱底子却相当扎实。它基于PHPMySQL实现不需要复杂的分布式架构一台虚拟机甚至本机就能跑起来。相比DVWA那种偏CTF风格的靶场Pikachu的模块划分更贴近实际业务场景比如搜索型注入、HTTP头注入、宽字节注入这些在真实站点的渗透测试里都是高频出现的类型练完直接能迁移到工作中。平台搭建的官方推荐方式是PHPStudy配合VMware虚拟机但如果你只是想快速上手刷题其实有更省事的路径。我的建议是本地直接装一套集成的Web环境比如PHPStudy或者XAMPP下载Pikachu源码解压到网站根目录改一下数据库连接配置三步搞定。具体操作流程我跑通了好几遍照着做基本不会翻车下载Pikachu源码包解压到web根目录下的pikachu文件夹。进入inc/目录找到config.inc.php把数据库账号密码改成你自己的默认是root/root。浏览器访问http://127.0.0.1/pikachu平台会自动创建数据库并跳转到初始化页面。点击初始化安装按钮出现初始化成功的提示就说明环境OK了。注意如果你用的是PHPStudy默认MySQL端口是3306PHP版本建议选7.x。Pikachu对PHP 8的兼容性偶尔会有小毛病尤其是老版本源码在mysqli函数上会报错遇到这种情况直接换PHP 5.6或7.0版本最省心。再提供一个判断环境是否正常的冷知识安装好后打开首页左下角会显示当前的数据库连接状态。如果显示连接失败八成是config.inc.php里的密码没对上或者MySQL服务没启动跟代码本身没关系。还有一类典型坑用虚拟机装靶场宿主机浏览器死活访问不了。这通常是网络模式选错了。VMware里把虚拟机的网络模式改成NAT或桥接然后手动设一个跟宿主机同网段的静态IP防火墙放行80端口和3306端口问题就解决了。我见过不少人在这个环节卡了一下午其实跟靶场代码毫无关系纯粹是网络配置问题。2. 注入前必懂的内功心法SQL注入为什么能骗过数据库很多人把SQL注入当成单纯的拼字符串游戏这个认知必须纠正。SQL注入能成立本质上是代码层把用户输入直接拼接进SQL语句而数据库无法区分哪段是代码、哪段是数据。你传进去的引号、括号、注释符在数据库眼里就是语法的一部分。理解这个前提后面的所有绕过手法才能串起来。SQL注入的成因可以用一句话概括外部输入被当成SQL代码执行了。后端代码常见的写法是SELECT * FROM users WHERE username$name直接把变量塞进SQL语句里。当输入变成 OR 11时拼接出来的SQL就变成了WHERE username OR 11条件恒成立数据就全查出来了。这里有一个初学者最容易忽略的细节闭合和注释是SQL注入的两个基本动作。闭合是为了结束前面的SQL片段让我们的恶意语句成为合法的独立部分注释则是为了把后面原本的代码废掉。经典的万能密码 OR 11 --前半个单引号是用来闭合SQL里username字段的单引号--则是把后面password的判断直接注释掉。Pikachu靶场设计得特别贴心的地方在于它在每个注入点的下方都显示当前执行的SQL语句。这意味着你可以亲眼看到自己的输入是如何被拼接到SQL语句里的一边注入一边对照原SQL和注入后的SQL理解效率比单纯看文档高好几倍。强烈建议新手刷题时养成先看SQL拼接结果、再构造payload的习惯。2.1 SQL注入的核心判断方法先搞清楚注入点是数字型还是字符型Pikachu的SQL注入模块开头就是数字型注入和字符型注入两个基础练习它们代表着两种最常见的查询场景。数字型注入的SQL原型是WHERE id$id字符型注入的原型是WHERE name$name两者在构造payload时的思路完全不同。判断类型的方法很简单输入一个单引号看页面反应。数字型注入id1正常id1报错id1 and 11正常id1 and 12异常——说明输入被当作数字处理不需要闭合单引号。字符型注入name1报错name1 and 11正常——说明输入被当作字符串处理需要额外提供一个单引号完成闭合。Pikachu的练习页面还内置了SQL语句显示功能你可以直观看到字符型注入的拼接过程。尝试输入kobe and 11 --你会发现SQL语句变成了WHERE namekobe and 11 -- 这就是闭合和注释配合使用的完整逻辑。2.2 判断字符集与绕过方案的思路宽字节注入为什么能吃掉转义符进入了Pikachu的宽字节注入关卡后难度开始上升。这个模块模拟的是网站开启了addslashes或magic_quotes_gpc的情况——输入中的单引号会被自动转义成\普通的注入payload会失效。宽字节注入的原理和字符集有关。当MySQL使用GBK编码时一个中文字符占两个字节而%df中的%df和反斜杠\即%5c组合在一起恰好会被数据库当作一个合法汉字解析掉于是后面的单引号就重新变成了裸引号转义机制形同虚设。Pikachu在这一关的SQL语句显示功能会让你清楚看到转义是如何被吃掉的。输入%df之前SQL显示的是WHERE name$name输入之后单引号成功突破了转义限制闭合了字符串。这个关卡是理解防御措施为什么会失效的绝佳教材值得反复练习。3. Pikachu SQL注入通关全记录每一个注入点的完整攻击路径Pikachu的SQL注入模块一共安排了8个由浅入深的练习场景每一个对应一种真实业务中常见的注入形态。下面我把每个关卡的探测思路和关键payload完整记录一遍照着练就能把整个模块啃下来。3.1 数字型注入最基础的拼数字玩法这一关的查询语句原型是SELECT * FROM user WHERE id$id没有引号包裹是最容易上手的一种注入类型。解题流程输入1正常回显id1的数据。输入1 and 12页面变为空白——说明and逻辑被执行条件判断有效。输入1 order by 3页面正常输入1 order by 4页面报错——说明字段数为3。使用联合查询1 union select 1,2,3回显位置为2和3。从information_schema库中依次查库名、表名、字段名、数据。最终payload示例1 union select username,password from users这关的核心目的是让新手掌握联合查询的完整流程。Pikachu平台自带了一个users表和member表练习时可以直接拿这两个表试手。3.2 字符型注入闭合引号才是关键这一关的SQL原型是SELECT * FROM user WHERE name$name多了引号包裹。探测方法和数字型略有不同输入kobe正常。输入kobe报错提示SQL语法错误。输入kobe and 11恢复正常的kobe数据。输入kobe and 12无回显——确认存在注入。之后的操作就和数字型一样了区别只是payload的写法kobe union select username,password from users where id1 --关于注释符这里补一个细节MySQL的注释符有--注意后面必须跟一个空格、#和/* */。在URL传参时空格会被编码成%20或者#会变成%23直接提交#可能会被浏览器当作锚点截断。我用Pikachu练习时最常使用的是--加空格在URL里写成--%20或--都能正确闭合。这个细节看着不起眼实际做题时却能卡你好几分钟。补充字符型注入的payload可以简化为kobe or 11 --直接绕过登录逻辑。这就是万能密码的本质——不需要知道任何账号密码让条件恒为真即可。想确认万能密码是否有效可以在Pikachu的登录框中输入随意的用户名密码输入 or 11 --如果能登录成功说明登录框存在同样的注入问题。3.3 搜索型注入业务系统里最容易被忽略的洞搜索框的注入Pikachu单独开了一个关卡对应的SQL原型是SELECT * FROM member WHERE name LIKE %$name%。很多新手在这关会卡住因为无论输入什么页面都显示结果不像前两关那样有明显的报错回显。忽略LIKE语句的闭合是这关最大的坑。普通的name and 11在这里是无效的因为搜索语句里本身有%和两个单引号。需要在输入中同时考虑左右两侧的闭合。正确的探测方法是输入kobe and 11 --发现回显正常再输入kobe and 12 --发现空白页。这就确认了搜索框存在字符型注入只不过多了一层%包裹。后续用联合查询时payload结构不变只需要注意把前面的kobe换成% union select... --。这种注入类型在实际业务系统里非常常见凡是带搜索功能的站点都可以顺手测一下命中率不低。3.4 报错注入与时间盲注当页面不再回显数据时怎么打Pikachu把报错注入和时间盲注设计成两个独立关卡这两关的逻辑是递进的。报错注入适用于联合查询无法回显数据、但数据库错误信息会原样输出到页面的场景时间盲注则适用于页面既无报错也无数据回显只能靠延迟判断TRUE/FALSE的场景。报错注入的核心是构造能让数据库报错的特殊函数比如updatexml和extractvalue。它们的原理是把要查询的目标数据拼接到XPath字符串中因为XPath格式非法MySQL会报错同时把参数里的内容原样输出。Pikachu报错注入过关的payload模板kobe and updatexml(1,concat(0x7e,(select database()),0x7e),1) --0x7e是波浪号~的十六进制编码用于制造非法XPath字符触发报错。报错信息里的~和查询结果会一起出现在页面中比如报错显示XPATH syntax error: ~pikachu~你就能确定当前数据库名是pikachu。时间盲注的关键在于让数据库执行延时函数。MySQL的sleep(5)能让查询延迟5秒配合条件判断就能逐个字符地猜出数据——如果猜对了页面响应会明显变慢如果猜错了响应速度正常。Pikachu时间盲注关卡的判断型payloadkobe and if((substr(database(),1,1))p,sleep(3),1) --当第一个字符是p时页面会卡顿3秒不是p时响应立刻返回。逐字符碰撞就能拼出完整的库名、表名和字段值。手动盲注的效率比较低但作为训练项目这个流程能让你彻底理解基于时间的判断逻辑。实际刷题时我会先手工测出确认条件成立再写一段脚本自动碰撞字符集把效率拉满。时间盲注的判断有一个实操中的细节尽量把延时设成3秒不要设成1秒。原因在于网络波动的干扰——本来没有延时的查询也可能因为网络抖动卡一下如果延时太短TRUE和FALSE的响应时间差异不明显容易误判。3秒是一个在准确性和测试效率之间比较平衡的选择。3.5 宽字节注入转义防御的安全性解剖Pikachu的宽字节注入关卡模拟的是开启了addslashes函数的环境输入的单引号会被自动转义成\这一点在页面上方的SQL拼接显示中可以直观看到。突破转义的方法是在输入中加入%dfkobe%df and 11 --提交后观察页面上方显示的SQL语句你会发现本应被转义的单引号成功突破了限制\被%df吸收了。GBK编码下%df%5c会被识别成一个汉字于是单引号恢复了正常语义。这一关的启示很大字符集设置不统一会直接瓦解安全防护。现实中很多系统只做了addslashes或参数转义却没有统一数据库连接字符集为UTF-8宽字节绕过依然有生存空间。不过需要明确真正的防御核心是参数化查询和PDO预处理转义只是辅助手段。3.6 增删改操作与HTTP头注入不只是SELECT才有注入很多人的SQL注入练习停留在SELECT语句上一遇到INSERT、UPDATE、DELETE就不知道该怎么判断了。Pikachu为此专门设计了三个关卡insert注入、update注入、delete注入还有独立成章的HTTP头注入。这几个关卡的检测手段和SELECT注入一脉相承但payload的触发时机完全不同。INSERT注入发生在添加记录时UPDATE注入发生在修改记录时DELETE注入发生在删除记录时——它们的共同点是数据库错误信息或者受影响的行数会通过某种方式回显出来。举个Pikachu的delete注入例子删除操作的URL是http://127.0.0.1/pikachu/vul/sqli/sqli_del.php?id1直接用报错函数探测id1 and updatexml(1,concat(0x7e,database(),0x7e),1)如果报错信息里出现了数据库名说明DELETE语句也存在注入。HTTP头注入是针对登录后业务逻辑的注入点它隐藏的字段通常是User-Agent、Referer、X-Forwarded-For这类客户端信息。Pikachu的HTTP头注入模拟的是后台记录登录日志的功能——管理员每次查看登录日志时系统会把客户端类型和IP地址写入数据库。如果后端直接拼接这些头字段进SQL攻击者就能在正常请求里夹带注入payload。实际操作时用浏览器插件修改User-Agent头就能完成注入。比如把User-Agent改成 and updatexml(1,concat(0x7e,database(),0x7e),1) and 然后查看登录日志页面触发报错回显。HTTP头注入在实际渗透测试中非常有价值因为它不在常规的WAF参数检测范围内——很多WAF只检查GET/POST参数对请求头里的payload关注度不高。这一块是Pikachu独有的练习场景DVWA里基本接触不到建议重点刷。3.7 Pikachu的通关目标找到所有数据表里的用户名和明文密码Pikachu的SQL注入模块设置了一个贯穿始终的通关标准从漏洞库里查出一张包含用户名和密码的表并用得到的密码去登录系统。具体来说平台的users表里有几组用户名和经过某种编码的密码还有一个member表存着更多测试数据。通关的核心流程是利用注入点查询当前数据库名database()。从information_schema.tables查出所有表名。从information_schema.columns查出目标表的字段名。用联合查询把username和password数据查出来。对密码字段进行解码/绕过登录平台管理系统。Pikachu在获取到密码后还埋了一个小关卡密码不是明文需要识别编码方式。常见的是MD5或者某种自定义编码有时需要配合暴力破解才能还原明文。这个设计模拟了真实渗透中拿到数据库hash之后还要想办法破解的完整链路。4. 工具与自动化SQLMap如何在Pikachu靶场里快速验证注入手工刷完Pikachu的SQL注入关卡后强烈建议再用SQLMap把这些注入点全部自动化验证一遍。原因很简单人力判断有一个效率瓶颈而SQLMap这类工具能帮你快速枚举当前注入点可用的注入类型、可查询的数据量级以及能否直接拿到shell这些信息对于决策下一步攻击路径至关重要。Pikachu的每个注入点URL都很有规律SQLMap的入门用法也不复杂。以字符型注入为例sqlmap -u http://127.0.0.1/pikachu/vul/sqli/sqli_str.php?namekobe --dbs这条命令的意思是对目标URL做注入检测成功注入后枚举所有数据库。SQLMap会自动判断注入类型、使用的payload并输出数据库列表。如果目标注入点需要POST提交数据用--data参数指定sqlmap -u http://127.0.0.1/pikachu/vul/sqli/sqli_search.php --datanamekobe --dbs拿到的结果可以作为手工注入的交叉验证——手工判断可能漏掉的盲注点SQLMap的高线程扫描往往能补上。但是这里必须强调一个原则工具是验证手段不是理解替代。很多新手刷完SQLMap的自动化输出后依然不知道--dbs背后的原理。我的建议是先用SQLMap测出结果再对照手工注入的payload分析为什么工具选择了这种注入方式最后试着用工具生成的payload手打一遍。三个步骤走完这个注入点才算真正吃透了。4.1 SQLMap在Pikachu中的进阶玩法cookie注入与延时设置Pikachu的HTTP头注入关卡配合SQLMap也能实现快速验证但需要让SQLMap携带特定的请求头。SQLMap提供了--headers参数可以自定义请求头内容sqlmap -u http://127.0.0.1/pikachu/vul/sqli/sqli_header.php --method POST --dataunameadminpassadmin --headersUser-Agent: and updatexml(1,concat(0x7e,database()),1) and 如果目标是基于登录态的注入点还需要先通过登录接口获取Cookie再用--cookie参数传入。Pikachu的很多注入点都在登录之后的页面里不带Cookie访问会被重定向到登录页SQLMap自然也就测不出任何结果。此外time-based盲注用SQLMap扫描时--time-sec参数可以控制延时长度。默认延时是5秒网络不好时可以调低sqlmap -u http://127.0.0.1/pikachu/vul/sqli/sqli_blind.php?namekobe --time-sec2 --dbs实操提示SQLMap在Pikachu靶场里跑盲注时默认的线程数和延时参数会比较激进靶场性能弱的情况下可能直接把MySQL打崩。建议加--threads1 --time-sec2降低扫描强度保证实验环境稳定。跑完再用--batch模式跳过交互提示体验会流畅很多。5. 盲注加速技巧从手工碰撞到脚本自动化在Pikachu的时间盲注关卡里完全手工碰撞字符是可行的但效率太低了。一个库名加两张表加字段名全手动猜下来至少要两三个小时而且容易出错。我在实际练习中总结了一套加速流程思路很简单先手工确认注入点有效再用脚本全自动碰撞。判断逻辑基于时间差查询结果正确时sleep(3)生效响应时间接近3秒结果错误时直接返回响应时间在几百毫秒以内。Python的requests库就可以完成整套操作。核心步骤如下发送请求并记录响应时间。逐个字符地测试ascii(substr(database(),{pos},1)){num}这类二分条件。根据响应时间差异判断条件真假。递归拼出完整数据。写脚本时有两个细节需要处理一是URL编码二是延时阈值。Pikachu的请求参数需要做URL编码比如单引号要写成%27空格写成%20否则部分字符会被后端解析异常。延时阈值建议设置成2秒——高于正常响应时间的明显分界线误判率会比较低。下面是一段我用于Pikachu时间盲注的python参考脚本逻辑就是逐字符二分碰撞数据库名import requests import time import string url http://127.0.0.1/pikachu/vul/sqli/sqli_blind.php charset string.ascii_lowercase string.digits string.punctuation def inject(payload): params {name: payload} start time.time() requests.get(url, paramsparams, timeout10) elapsed time.time() - start return elapsed 2.5 result for i in range(1, 20): low, high 32, 126 while low high: mid (low high) // 2 payload fkobe and ascii(substr(database(),{i},1)){mid} -- if inject(payload): low mid 1 else: high mid result chr(low) print(f[{i}] - {chr(low)}) if chr(low) }: break print(database:, result)这段脚本的二分逻辑是每轮把字符范围对半砍直到收敛成唯一字符。撞库名时顺带撞表名和字段名只需要替换substr后面的表达式。手动刷完Pikachu全部关卡后能自己写出这个脚本说明盲注的底层逻辑已经真正掌握了而不是只知道复制前辈的payload。6. 踩坑记录与排查技巧Pikachu注入练习中我遇到过的5类典型问题刷Pikachu的过程中有几个问题几乎每个练习者都会遇到。我按出现频率从高到低排个序附上排查思路和解决办法能帮你少走不少弯路。问题一报错信息显示SQL语法错误但payload明明是对的。一般不是payload的问题而是注释符或空格没处理好。检查两点第一--后面有没有跟空格在URL里是否被编码成了--%20或--第二有没有用#注释符却在浏览器里被截断#在URL中要写成%23。这两类问题占了报错原因的大半。问题二联合查询不回显数据页面依然显示正常内容。优先怀疑字段数判断错了。用order by逐步递增试探找出精确的字段总数。Pikachu的查询结果通常只有2~3个字段但如果你卡在了其他表上字段数可能不一样。联合查询时要让前面的查询结果为0简单的办法是在union前加一个不存在的ID值比如id-1 union select...。问题三时间盲注时页面卡顿不稳定误判率高。原因是延时太短或网络波动。解决方案把sleep的延时值设为3秒并且连续对同一个字符测试两次两次都超时才判定为TRUE。时间盲注是概率判断单次结果不可靠至少重复一次再下结论。问题四HTTP头注入点检测不到任何反应。可能的原因是请求头没有正确发送。浏览器里的请求头很难手动修改建议直接用Burp Suite的Repeater功能修改请求包或者用Python的requests库自定义headers。另外Pikachu的HTTP头注入点通常需要登录后才能触发检查一下是否携带了有效的Cookie。问题五SQLMap扫不出任何注入点。大概率是目标URL或参数名写错了。先用浏览器访问目标页面在开发者工具里查看真实的请求参数和URL路径再复制到SQLMap命令里。如果目标需要登录检查--cookie参数是否传了正确的会话标识。还有一点容易被忽略Pikachu的POST注入点必须用--data指定参数内容只用-u传URL是扫不出结果的。我来整理一份快速排查表遇到问题直接对照查问题现象可能原因确认方法解决方案SQL语法报错注释符/空格编码错误检查URL编码后的payload全貌用--%20或%23替代空格和#联合查询不回显字段数判断错误用order by逐级探测修正union的select列数页面无异常GET参数不对浏览器开发者工具查看实际参数改用POST--data传递参数时间盲注误判延时过短/网络抖动同一条件重复测试sleep设为3秒重复验证工具扫描失败Cookie未带/参数名错误核对登录态和请求包添加有效Cookie或修正参数名HTTP头注入无反应头字段未自定义Burp拦截查看实际发送的headers用Repeater修改User-Agent/Referer还有一个容易被忽视的特殊问题Pikachu的宽字节注入关卡如果你在URL里直接输入%df注意浏览器可能会对URL进行一次解码再编码导致%df被错误转换成其他字符。更稳妥的做法是先用Burp拦截请求手动修改原始包内容保证%df原样发送到后端。这个细节我在实战中帮很多朋友排查过十有八九是浏览器自动转码在捣乱。7. 靶场之外这套做题笔记还能迁移到什么地方从Pikachu的SQL注入模块毕业之后你会获得一套完整的方法论判断注入类型、闭合思路、绕过防护、工具使用、自动化碰撞。这套方法论能直接迁移到其他靶场和真实业务系统但不同环境各有侧重。SQLi-Labs是另一个值得继续刷的靶场它最大的优势是关卡数量多、难度梯度细每一关都对应一种具体的绕过场景从基础数字型、字符型到双查询注入、堆叠注入、盲注再到各种绕过WAF的思路。Pikachu的8个关卡是从0到1建立认知SQLi-Labs则是从1到100扩大武器库两个靶场刚好形成互补。CTF赛题里的SQL注入则偏向取巧路线。很多CTF赛题会故意设置各种过滤规则比如屏蔽空格、屏蔽关键字、屏蔽逗号你需要用/**/替代空格、用concatfrom替代逗号语法绕过去之后拿到flag。这和Pikachu练的常规业务注入不完全相同但基础逻辑是相通的——如果Pikachu的闭合和注释手法玩熟了CTF里的绕法就只是变体而已。至于真实业务系统我的建议是谨慎再谨慎。靶场环境和生产环境最大的区别在于靶场的数据库是专门设计的怎么折腾都能恢复生产环境你的一次误操作可能导致数据丢失或服务不可用后果完全不一样。把Pikachu练熟之后你至少应该具备以下能力知道哪些请求参数可能进入SQL语句、如何在不破坏数据的前提下验证注入点、拿到权限后如何最小化入侵痕迹——这是职业道德的底线也是安全从业者和其他人的分水岭。在我的实际工作中SQL注入的排查思路用一句话总结就是先看输入在哪里、再想它怎么进SQL、最后决定怎么闭合和注释。这个思路从Pikachu的第一关到真实系统的高级利用从来没有变过。练熟了靶场就是练熟了这套思考方式。遇到再复杂的业务逻辑本质上也跳不出寻找输入点拼接进SQL构造闭合这三个步骤。最后再分享一个我在反复刷Pikachu过程中养成的习惯每一关过关之后不做下一关先回头把页面上方显示的原始SQL和注入后的SQL抄在本子上对照一遍并把关键payload按注入类型分类整理成自己的速查表。这个动作看似简单但坚持练完整个模块后你对SQL注入的理解就不再是零散的payload背诵而是一种系统化的认知结构。刷靶场最大的收获从来不是记住几个花哨的注入字符串而是把为什么这样做彻底弄明白。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进