1. 靶场选型与环境准备:为什么是SQLi-Labs
带过不少新人,也踩过各种环境坑,如果你问我SQL注入入门用哪个靶场最合适,我的答案始终是SQLi-Labs。这套基于PHP和MySQL的靶场,几乎把所有SQL注入的经典场景都收进去了:从最基础的GET单引号注入,到联合注入、报错注入、布尔盲注、时间盲注、堆叠注入,再到二次注入和绕过WAF的思路,一共65道关卡,难度曲线设计得很合理。
相比DVWA和Pikachu,SQLi-Labs最大的优势是它把“注入点”和“SQL语句”直接暴露在你眼前。DVWA虽然也是经典,但它带了很多业务逻辑封装,你在页面上能看到的是一个完整的Web应用,注入只是其中的一个功能点;而SQLi-Labs更像是一个“SQL语句实验室”,每一关都在强迫你和SQL语句本身打交道,去思考后台到底拼接了什么,输入进去的参数最后落在SQL语句的哪个位置。
另一个实用场景是,它非常适合做SQL语句的学习辅助工具。很多新手问“我SQL语句该咋学”,光看书和刷题总是少了点实感,在SQLi-Labs里你能直观看到:一个闭合的单引号改变了整条查询的语义,一个注释符让后面的条件彻底失效,一个UNION联合查询把数据从别的表里带出来。语句的结构、优先级、执行逻辑,全都可以在靶场里亲手验证。
搭建门槛也不高,一台2核4G的服务器或者本机的虚拟机就够跑,PHP环境加MySQL就能完整运行。我自己常用的方案是Windows下用phpStudy、Linux下用Docker或宝塔面板,跑起来非常稳。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 本地环境搭建:PHP版本与数据库配置
2.1 PHP版本选择是关键
SQLi-Labs是老项目了,它最舒服的运行环境其实是PHP 5.x,但现实是现在很少有人会装这么老的版本。实测下来,PHP 7.4和PHP 8.0都能跑通大部分关卡,但有几个注意点:
PHP 7.x及以上版本对mysql_*这一类老函数已经彻底移除了,SQLi-Labs的代码里大量使用了mysqli的面向对象写法,所以只要你的PHP环境启用了mysqli扩展,问题不大。真正容易出问题的是PHP 8.1以上版本,某些关卡会有Deprecated警告之类的干扰信息,这会直接影响你判断注入是否成功,因为页面输出的任何多余字符都可能污染你的注入逻辑判断。
我在Windows上用phpStudy、在Linux上用宝塔面板都搭过,PHP版本建议直接选7.4,这是兼容性和稳定性的最佳折中点。装好之后打开phpinfo确认一下mysqli扩展的状态,如果没启用,在php.ini里去掉extension=mysqli前面的分号,或者直接在面板里一键安装。
2.2 数据库初始化与密码修改
SQLi-Labs的源码里数据库配置写在sql-connections/db-creds.inc这个文件里,默认配置是MySQL的root账号、密码为空。但在实际环境中,MySQL 5.7以上版本安装时通常会强制要求设置root密码,所以必须手动改这个配置文件。
有个高频坑:如果你用phpStudy或者宝塔面板安装MySQL,MySQL 8.0默认的认证插件是caching_sha2_password,而某些老代码使用的是mysql_native_password,这会导致靶场连接数据库时报“Authentication plugin”错误。
解决办法很简单,修改root用户的认证方式:
sql复制ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码';
FLUSH PRIVILEGES;
这个操作只影响本地连接,在靶场环境下完全够用。
另外记得在sql-connections目录下确认sql-connect.php、user.php这些文件里的用户名密码要和实际数据库一致。建议把所有密码统一设置成一个简单值,比如root,方便后面调试时脑子里不用记多套密码。
2.3 源码部署三步走
部署本身就是三件事:放代码、初始化数据库、修改配置。
第一步,把SQLi-Labs源码放到Web目录。Windows的phpStudy是WWW目录,Linux宝塔是/www/wwwroot/下的站点目录。解压后访问http://你的IP/sqli-labs-master/,进入欢迎页。
第二步,点击页面上那个“Setup/reset Database for labs”的链接,靶场会自动执行sql-connections/setup-db.sql,创建security数据库,建好users、emails、referers、uagents四张表,并插入初始数据。这一步成功后,靶场就算活了。
第三步,改配置文件。把db-creds.inc里的数据库密码改成你实际的MySQL密码,保存后刷新页面。如果看到“Welcome to SQLi-Labs”的欢迎页,说明环境一切正常。
这里有个小技巧:如果Setup页面点击后长时间无响应,多半是数据库密码不对,或者是数据库名security已存在且有残留数据。前者改密码,后者需要手动删掉库再重来。
3. SQL语句基础与注入原理:先把语句看透
3.1 从一条查询语句说起
想玩好SQL注入,最核心的其实是SQL语句的基本功。你在SQLi-Labs里打的每一关,本质上都是在和后台拼接出来的SQL语句打交道。
拿第一关来说,后台的核心逻辑大致是:
sql复制SELECT * FROM users WHERE id='$id' LIMIT 0,1;
$id是你传入的参数,直接拼进了单引号包裹的字符串里。如果正常传?id=1,查询条件就是id='1',返回id为1的用户数据。
学习SQL语句,首先要建立“语法结构”的视角:SELECT后跟要查的列,FROM指定表,WHERE筛选条件,LIMIT限制返回行数。SQLi-Labs的关卡几乎每一关都在让你调结构。你用ORDER BY试探列数、你用UNION SELECT对齐字段数、你用UPDATE和INSERT去理解堆叠注入,全部都是在和SQL语句的语法规则打交道。
3.2 注入的本质:闭合与注释
SQL注入的前提是后台把用户输入直接拼进了SQL语句,没有经过参数化处理。那么攻击者的核心逻辑就是两件事:
第一件事是闭合前文。后台原本是WHERE id='$id',你传1',SQL语句变成WHERE id='1'',此时有两个单引号相邻,第二关会在语法层面出错,因为MySQL解析到第二个单引号时发现字符串已经结束,多余的引号直接报错。
第二件事是注释掉后文。为了让后面的语句不干扰你的注入逻辑,你需要用注释符把多余的SQL语段“屏蔽”掉。第一关用的闭合方式是--+或#:
sql复制SELECT * FROM users WHERE id='1'-- ' LIMIT 0,1;
--是MySQL的注释符,它会把后面的所有内容注释掉;后面的空格或者+是为了满足部分解析器对--后必须跟空白字符的要求。#则直接注释掉整行,不需要额外跟空格。
一句话总结:闭合是让整条语句的语义进入你的掌控范围,注释是清除你控制范围之外的干扰项。
3.3 注入点位置的差异决定了写法
SQLi-Labs的65关里,注入点的位置各有不同,有的在WHERE条件里,有的在ORDER BY语句里,有的在INSERT语句里,有的在UPDATE语句里,还有的在LIKE模糊查询里。
- 数字型注入:
WHERE id=$id,不需要引号闭合,直接拼数字。 - 字符串型注入:
WHERE id='$id',需要单引号闭合。 - 搜索型注入:
WHERE name LIKE '%$name%',需要闭合单引号和%%两个通配符。 - ORDER BY注入:注入点在排序字段,无法用UNION,通常用条件判断或报错函数。
- INSERT/UPDATE注入:目标不是数据读取,而是通过语句的副作用来获取信息,或者触发报错回显。
靶场里每过一关,你都可以去翻一下源码,看看后台原始SQL语句是什么样,这对理解注入位置的影响帮助极大。我见过太多人打靶场只关注“怎么绕过”,完全忽略语句本身,这会导致换一个场景就完全懵了。
4. 经典关卡实操:从Less-1到Less-11的思路拆解
4.1 最基础的联合注入:Less-1
打开Less-1的页面,只有一个输入框,让你输入数字ID。先传?id=1,页面显示“Your Login name: Dumb”和“Your Password: Dumb”。再传?id=1',页面报错:“You have an error in your SQL syntax”,同时直接把SQL语句的一部分回显出来了。
你首先验证两件事:注入存在、后台没有做输出过滤。这种情况下利用联合注入是最快的。
第一步,确定字段数:
sql复制?id=1' ORDER BY 3--+
?id=1' ORDER BY 4--+
当ORDER BY 3正常显示、ORDER BY 4报错时,说明查询只有3列。
第二步,用UNION SELECT对齐字段数,同时让前面的查询结果为空,比如把id改成-1:
sql复制?id=-1' UNION SELECT 1,2,3--+
页面回显了2和3这两个数字,说明查询结果的第2列和第3列会显示在页面上。这就是联合注入的“显示位”。
第三步,用group_concat()一次性把users表的用户名和密码字段全部带出来:
sql复制?id=-1' UNION SELECT 1,group_concat(username),group_concat(password) FROM security.users--+
到了这一步,users表里的所有账号密码都出现在页面上了。密码是MD5加密的,可以去在线网站解密,也可以理解为靶场默认数据,翻看一下数据库就知道了。
这里想强调一个基本功:UNION的规则是前后两个查询的列数必须一致,而且数据类型要兼容。所以在UNION SELECT之前必须用ORDER BY确定列数,这一步少做了,后面全是白搭。
4.2 报错注入的三种典型写法:Less-5
Less-5这段,页面不管怎么输入,都只显示“You are in...”,没有具体数据回显。很多人到这里就卡住了,因为联合注入需要显示位,这里根本没有显示位。
解决办法是把攻击思路从“查询并回显”切换到“让数据库报错并把报错信息带出来”。报错注入的原理是构造一个会触发MySQL报错的表达式,让报错信息里嵌套你想要的查询结果。
三种最常用的写法:
updatexml()
sql复制?id=1' AND updatexml(1,concat(0x7e,(SELECT password FROM users WHERE username='admin'),0x7e),1)--+
extractvalue()
sql复制?id=1' AND extractvalue(1,concat(0x7e,(SELECT database()),0x7e))--+
floor(rand(0)*2)配合group by
sql复制?id=1' AND (SELECT 1 FROM (SELECT count(*),concat((SELECT password FROM users LIMIT 0,1),floor(rand(0)*2))x FROM information_schema.tables GROUP BY x)y)--+
0x7e是~的十六进制编码,作用是让报错信息中你的查询结果前后多出特殊符号,方便快速定位和截取。updatexml和extractvalue都有长度限制(大概32个字符),所以查询单个字段时要控制好长度,或者用substr()函数分段读取。
4.3 布尔盲注与时间盲注:Less-8和Less-9
Less-8的页面特征是:输入正确条件显示“You are in...”,输入错误条件什么都不显示。这种状态下你得不到任何数据,只能通过页面的“是与非”来逐位猜解数据。
核心思路是把查询结果变成布尔判断条件。用ascii()和substr()函数逐个字符取编码,然后和猜解的数值比较:
sql复制?id=1' AND ascii(substr((SELECT password FROM users LIMIT 0,1),1,1))>100--+
如果页面正常显示,说明第一位字符的ASCII码大于100;继续缩小范围,直到精确猜出每一位字符。
而Less-9比Less-8更狠:无论条件真假,页面都只显示一行话,没有任何布尔差异。此时只能用时间盲注——通过sleep()函数人为制造延时来判断条件真假。
sql复制?id=1' AND IF(ascii(substr((SELECT password FROM users LIMIT 0,1),1,1))>100,sleep(3),0)--+
如果页面等待了3秒才返回,说明条件为真;否则条件为假。这种方式效率极低,实战中一般交给工具跑,但初学者必须亲手手动猜几轮,才能真正理解盲注的“逐位猜解”逻辑。
4.4 POST型注入与万能密码:Less-11和Less-23
Less-11切换到了POST提交,登录表单里的用户名和密码都是注入点。后台的SQL语句大致是:
sql复制SELECT username, password FROM users WHERE username='$uname' AND password='$passwd' LIMIT 0,1;
最经典的万能密码思路就是让WHERE条件恒为真。在用户名处输入admin'-- ,密码随便填,SQL语句变成:
sql复制SELECT username, password FROM users WHERE username='admin'-- ' AND password='xxx' LIMIT 0,1;
--注释掉了密码的校验条件,登录逻辑直接被绕过。“万能密码”不是真的有一个万能密码字符串,而是利用注释符和恒真条件改变了SQL语句的判断语义。
Less-23则是一个“过滤了--和#注释符”的关卡,当你输入'-- 时会发现注释符被替换成了空格,无法正常注释。这种场景下的绕过思路是用等价闭合,比如:
sql复制?id=1' AND '1'='1
利用后面的AND '1'='1让整条语句语义闭合,即使没有注释符,也能保证语法正确。这个思路在真实场景中非常常用,因为很多WAF和过滤规则都会优先封堵注释符。
5. 1-65关的通关路线:一个清晰的路线图
5.1 按注入类型分类的关卡分布
SQLi-Labs一共65关,按照注入类型可以这样归组:
表格如下:
| 范围 | 主要知识点 | 核心思路 |
|---|---|---|
| Less-1 至 Less-4 | GET型联合注入 | ORDER BY找字段数,UNION SELECT对齐列数 |
| Less-5 至 Less-7 | GET型报错注入 | updatexml、extractvalue、floor报错 |
| Less-8 至 Less-10 | 盲注 | 布尔盲注与时间盲注的逐位猜解 |
| Less-11 至 Less-17 | POST型注入 | 登录表单、搜索表单的注入点 |
| Less-18 至 Less-22 | Header头注入 | User-Agent、Referer、Cookie等位置的注入 |
| Less-23 至 Less-28 | 过滤绕过 | 注释符过滤、关键字过滤、空格替代 |
| Less-29 至 Less-31 | HTTP参数污染(HPP) | 利用服务端解析差异绕过WAF |
| Less-32 至 Less-37 | 宽字节注入 | GBK编码下的%df宽字节绕过 |
| Less-38 至 Less-45 | 堆叠注入 | 多条SQL语句堆叠执行 |
| Less-46 至 Less-53 | ORDER BY注入 | 排序字段的报错与盲注 |
| Less-54 至 Less-65 | 挑战关卡 | 各种绕过技巧的混合应用 |
建议不要按顺序硬刷。先集中打1到22关,把注入类型全部过一遍,建立“注入点位置决定攻击方式”的思维框架;然后回头打23到37关,重点理解过滤绕过和编码原理;后面的堆叠注入和挑战关卡,等基础稳固了再去冲刺。
5.2 大量重复练习的意义
很多初学者问我,靶场这么多关,打完了就忘怎么办。我的建议是重复打,每一关至少打三遍。
第一遍:看Writeup,跟着别人走通一遍流程,感受每个输入值的变化。
第二遍:不看Writeup,自己从零推导。注入点在哪个位置、属于什么类型、用什么方式利用,每一步都要想明白为什么。
第三遍:尝试用多种方式解题。Less-1你既可以用UNION,也可以用报错注入,甚至可以转换成布尔盲注;能在一个注入点上玩出多种花样,说明你真的理解了而不是背了语句。
重复三遍之后,你对SQL语句的敏感度会明显提升,看一条SQL语句时会产生“这里能不能注入、该怎么闭合、用什么姿势”的条件反射。
6. 常见问题与排查技巧实录
6.1 环境类问题排查
页面显示“Database Error”但不确定哪里错了,这种问题最考验排查思路。先看db-creds.inc里的账号密码和MySQL实际是否一致,再确认MySQL服务是否启动,最后强制刷新一下浏览器缓存,因为老页面缓存的概率很高。
如果是PHP版本过高导致页面抛出一大堆Deprecated警告,页面上会出现红字,这会干扰盲注时的判断。我的建议是直接换PHP 7.4,一劳永逸;不想换的话,可以在php.ini里把error_reporting改为E_ALL & ~E_DEPRECATED,压掉干扰信息。
数据库导入失败的情况也很常见。可能是MySQL 8.0的认证插件问题,也可能是MySQL版本高了以后去掉了对mysql_native_password的默认支持。按前面说的ALTER USER命令换一下认证方式,基本都能解决。
6.2 注入语句失效的排查思路
你写了一条注入语句,但页面表现完全不符合预期,这种时候先别急着怀疑工具,按顺序排查:
第一步,确认闭合方式。你写在代码里的SQL语句和你猜测的SQL语句真的一致吗?去翻一下对应关卡的源码,看看原始语句到底是什么。
第二步,确认注释方式。--+在URL里会被解析成--加空格吗?我见过很多人在URL中用--+没问题,但到了POST请求的body里+会被解析成空格,和预期的注释不符合。POST环境下更推荐用#做注释。
第三步,确认报错函数是否存在版本限制。updatexml()是MySQL 5.1.5及以上版本才有的,极老版本的MySQL不支持。如果你在练习时用的是MySQL 5.1以下的数据库,就需要换extractvalue()或其他函数。
6.3 给初学者的三点建议
第一,打靶场之前先把SQL语句基础过一遍。不用学太深,但SELECT、WHERE、ORDER BY、UNION、GROUP BY、COUNT()、SUBSTR()、ASCII()这些基本语句和函数必须非常熟悉,否则打靶场时你看不懂后台在干什么。
第二,手注优先,工具辅助。前30关强烈建议全部手打,把每条注入语句在脑子里过一遍逻辑;从30关开始,可以尝试用sqlmap配合手工分析。一上来就用工具刷,打完65关你会发现除了“会用工具”,什么都没学会。
第三,安全边界要牢牢守住。靶场搭建的本意是让你在本地或授权环境中练习安全技能。不要把这套思路用在未经授权的系统上,这是职业底线,也是法律底线。SQLi-Labs这类靶场能长期存在,恰恰说明安全圈是用“合法靶场+授权测试”的方式来培养人才的。
从环境搭建到65关通关,整个过程其实就是对一个核心问题反复琢磨:后台的SQL语句是如何拼接用户输入的,而你该如何通过控制输入来改变这条语句的执行结果。把这个思路吃透了,SQL注入这道坎就算真正迈过去了。
