1. 从零开始:为什么选择sqli-labs作为SQL注入的“第一课”?
如果你刚开始接触Web安全,或者想系统性地学习SQL注入,那么“sqli-labs”这个靶场几乎是绕不开的名字。它不像DVWA那样功能庞杂,也不像Pikachu那样场景多样,sqli-labs的核心目标非常纯粹:用最直接、最经典的方式,带你一层层剥开SQL注入的“洋葱”。我当年入门时,也是从它开始的,现在回头看,这个选择非常正确。它把复杂的注入技术,拆解成一个个由浅入深的关卡,让你能清晰地感受到从最简单的字符型注入,到需要绕过多重过滤的复杂注入,整个技术栈是如何搭建起来的。
很多人一上来就想搞懂盲注、堆叠注入这些高级技巧,结果往往在基础的概念和手工测试流程上就卡住了。sqli-labs的前几关,恰恰就是帮你夯实这些基础。Less-1到Less-6,这六关看似简单,却涵盖了SQL注入中最核心、最基础的两种类型:基于错误的字符型注入和基于错误的数字型注入。通过亲手操作这几关,你能真正理解“单引号闭合”、“永真条件”、“联合查询”这些概念到底在浏览器和数据库之间发生了什么,而不是仅仅停留在理论层面。
更重要的是,sqli-labs的搭建极其简单。它就是一个基于PHP和MySQL的本地环境,你不需要复杂的Docker配置,也不需要去折腾有各种限制的在线靶场。在自己的电脑上搭起来,想怎么“破坏”就怎么“破坏”,可以放心大胆地尝试各种Payload,观察每一次请求的响应,这种即时反馈的学习体验是无可替代的。接下来,我就带你从环境搭建开始,一步步通关前六关,过程中我会分享很多当年我踩过的坑和总结出来的“肌肉记忆”般的测试思路。
2. 靶场搭建与核心工具准备:打造你的专属“黑客实验室”
工欲善其事,必先利其器。在开始注入之前,一个稳定、干净的实验环境是首要条件。sqli-labs靶场本身结构简单,但对运行环境有明确要求。
2.1 环境搭建:避开版本兼容的“暗礁”
sqli-labs官方推荐使用PHP 5.x版本和MySQL。虽然在新版的PHP 7+和MySQL 8上通过修改代码也能运行,但对于初学者,我强烈建议使用与靶场最兼容的环境,避免把时间浪费在解决环境报错上。最省心的方案是使用集成环境包,比如XAMPP(Windows/macOS)或LAMP(Linux)。
以XAMPP为例,下载安装后,启动Apache和MySQL服务。接着,从GitHub下载sqli-labs的源码,将其解压到XAMPP的htdocs目录下(例如C:\xampp\htdocs\sqli-labs)。然后,在浏览器访问http://localhost/sqli-labs/,点击页面上的“Setup/reset Database for labs”链接。这一步会自动创建所需的数据库和表。
注意:如果点击链接后页面长时间空白或报错,大概率是数据库连接配置问题。你需要检查
sqli-labs/sql-connections目录下的db-creds.inc文件,确保里面的数据库主机(默认localhost)、用户名(默认root)、密码(默认为空)与你的XAMPP的MySQL配置一致。这是我遇到的第一个坑,很多新手会忽略这里。
环境搭好后,你会看到一个索引页面,从Less-1到Less-65+,这就是我们的“闯关地图”。
2.2 核心武器库:Burp Suite与浏览器的开发者工具
对于前六关,我们主要进行基于错误的注入(Error-Based),手工测试结合浏览器工具就足够了。但养成使用专业工具的习惯至关重要。
浏览器开发者工具(F12):这是你最直观的“眼睛”。重点关注“网络”(Network)标签页。进行注入测试时,勾选“保留日志”(Preserve log),这样你能看到每次页面跳转或表单提交发出的具体HTTP请求和响应。查看响应体,特别是HTML源码,有时错误信息或关键数据会直接藏在里面。这是分析注入结果的第一步。
Burp Suite:安全测试的“瑞士军刀”。即使在前几关,我也建议你打开Burp Suite的代理功能(Proxy),让浏览器流量经过它。这样做有两个巨大好处:
- 请求历史与重放:所有请求都会被记录在Proxy -> History里。你可以随时找到某次测试的请求,右键“Send to Repeater”,然后在Repeater标签里随意修改参数、重复发送,无需在浏览器里来回刷新。这能极大提升测试效率。
- 结果对比:在Repeater中,你可以同时发送修改前和修改后的请求,并排对比响应差异,精准定位注入点。
HackBar浏览器插件(可选但推荐):这是一个集成在浏览器里的简单工具,可以方便地对URL参数或POST数据进行编码(如URL编码、Base64)、快速添加常见的SQL注入Payload。对于快速测试非常方便。
准备好这些,我们的“实验室”就算正式启动了。记住,我们的目标不是盲目地输入Payload,而是理解每一次输入背后,应用程序与数据库的交互逻辑。
3. Less-1到Less-3:字符型注入的闭合艺术与联合查询实战
前四关都是字符型注入,但闭合方式略有不同,这正是sqli-labs设计的精妙之处——它强迫你去关注那些容易被忽略的细节。
3.1 Less-1:单引号闭合的经典入门
访问Less-1,页面是一个简单的用户查询界面,URL形如http://localhost/sqli-labs/Less-1/?id=1。
第一步:探测注入点与闭合方式
我们的标准起手式是在id参数后加一个单引号:?id=1'。页面返回了数据库错误信息:You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''1'' LIMIT 0,1' at line 1
这个错误信息是黄金线索。它告诉我们:
- 存在SQL注入漏洞(因为我们的输入改变了SQL语法)。
- 原始SQL语句中,参数
1很可能被单引号包裹,形如SELECT ... FROM ... WHERE id='1' LIMIT 0,1。我们输入1'后,语句变成了WHERE id='1'' LIMIT ...,多出的那个单引号破坏了语法。
第二步:构造永真条件,绕过查询
知道是单引号闭合后,我们需要“修复”这个SQL语句。目标是让WHERE条件永远为真,从而返回所有数据。我们输入:?id=1' or '1'='1。 此时,SQL语句变为:WHERE id='1' or '1'='1' LIMIT 0,1。注意,我们巧妙地用or '1'='1补充了最后一个单引号,使整个字符串闭合。因为'1'='1'永远成立,所以这个WHERE条件会忽略id=1,选择所有记录。页面通常会返回第一条数据(受LIMIT 0,1限制)。
第三步:使用联合查询(UNION)获取更多信息
仅仅让页面回显不同还不够,我们需要从数据库“偷”数据。UNION SELECT是关键。但使用UNION有两个前提:
- 前后
SELECT语句的列数必须相同。 - 列的数据类型需要兼容。
首先,判断当前查询的列数。使用ORDER BY子句,它根据列索引排序,如果索引超出列数就会报错。我们从ORDER BY 1开始尝试:?id=1' order by 3--+(--+是MySQL的注释符,用于注释掉后面的LIMIT语句,+在URL中代表空格)。 尝试order by 4时页面报错,说明当前查询结果有3列。
然后,找到页面上可以回显我们查询数据的位置。构造Payload:?id=-1' union select 1,2,3--+这里有个技巧:将原查询的id设为一个不存在的值(如-1),这样原查询结果为空,页面就会完整显示我们UNION SELECT的结果。页面显示数字2和3的位置,说明第2和第3列的数据会被回显到网页上。
最后,就可以在回显位替换为我们想查询的信息了:?id=-1' union select 1, database(), user()--+这将会在页面上显示当前数据库名和数据库用户名。你还可以查询version()看MySQL版本,或者查询information_schema数据库来获取表名、列名。例如,查询所有表名:?id=-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database()--+
实操心得:
--+中的+是为了适应URL编码。在浏览器地址栏或HackBar里直接写--+即可。在Burp Suite的Repeater里修改时,如果直接输入空格,有时需要手动将其编码为%20或+。这是初期容易混淆的地方。
3.2 Less-2:数字型注入的“直来直往”
进入Less-2,同样的界面。我们重复第一步:?id=1'。 发现页面正常显示,没有报错!这立刻提示我们,这一关的注入类型可能不同。尝试?id=1 and 1=2,如果页面内容消失或变化,则很可能是数字型注入。
数字型注入的SQL语句原型通常是WHERE id=1,参数没有被引号包裹。所以我们的Payload不需要考虑闭合引号,更加直接。判断列数:?id=1 order by 3--+,成功。构造联合查询:?id=-1 union select 1,2,3--+。后续的信息获取步骤与Less-1完全一致,只是去掉了用于闭合的单引号和与之配套的注释。
这一关的关键在于,它训练你形成条件反射:当加单引号不报错时,要立刻想到数字型注入,并用and 1=2这类逻辑判断来验证。
3.3 Less-3:单引号加括号的“组合闭合”
Less-3是第一个小难点。输入?id=1',报错信息变为:near ''1'') LIMIT 0,1'注意看,错误提示里有两个右括号))。这说明原始SQL语句可能是WHERE id=('1')这种形式。我们的输入1'使其变成了WHERE id=('1''),语法错误。
因此,我们需要构造的闭合就变成了:先补一个单引号,再补一个右括号。Payload如下:?id=1') or ('1')=('1这样,语句变为WHERE id=('1') or ('1')=('1'),成功闭合且条件永真。后续的ORDER BY和UNION SELECT都需要带上这个闭合格式: 判断列数:?id=1') order by 3--+联合查询:?id=-1') union select 1,2,3--+
这一关的核心教训是:一定要仔细阅读错误信息。错误信息是数据库给你的“诊断书”,它精确地告诉了你SQL语句的原始结构。盲目套用上一关的Payload必然会失败。
4. Less-4到Less-6:双引号、括号与HTTP POST的攻防转换
如果你以为字符型就是单引号,数字型就是没引号,那Less-4会给你上一课。
4.1 Less-4:双引号与括号的“双重奏”
来到Less-4,习惯性地输入?id=1',页面正常。输入?id=1",终于报错了:near '"1"") LIMIT 0,1'错误信息清晰地显示:双引号"和两个右括号))。这说明原语句是WHERE id=("1")。所以闭合方式为双引号加括号。
构造永真Payload:?id=1") or ("1")=("1后续步骤类推: 判断列数:?id=1") order by 3--+联合查询:?id=-1") union select 1,2,3--+
这一关的意义在于打破思维定式。SQL语句的闭合符号不仅仅是单引号,还可能是双引号、括号,甚至是它们的各种组合。测试时,单引号、双引号、括号都需要尝试。
4.2 Less-5:基于布尔的盲注(Boolen-Based Blind)初体验
从Less-5开始,画风突变。无论你输入什么,页面都不会回显数据库数据,只显示固定的“You are in...”或没有任何数据。这就是“盲注”。服务器执行了SQL语句,但不会在页面上直接输出查询结果或错误详情。我们需要像猜谜一样,通过页面行为的细微差异(通常是“正常”与“异常”两种状态)来推断信息。
Less-5是一个典型的基于布尔的盲注。输入?id=1' and 1=1--+,页面正常显示“You are in...”。输入?id=1' and 1=2--+,页面虽然不报错,但“You are in...”这句话消失了(或者返回空)。这就是我们的“布尔值”:正常代表True,异常/空代表False。
利用这个特性,我们可以用length()和substr()函数,像拆解保险箱密码一样,一位一位地猜解数据。例如,猜解当前数据库名的长度:?id=1' and length(database())=1--+(页面空,说明不等于1)?id=1' and length(database())=8--+(页面显示“You are in...”,说明数据库名长度为8)
猜解数据库名的第一个字符:?id=1' and substr(database(),1,1)='a'--+(页面空,不是‘a’)?id=1' and ascii(substr(database(),1,1))=115--+(页面正常,说明第一个字符的ASCII码是115,即字母‘s’)
这个过程极其繁琐,完全依赖手工几乎不可能。所以Less-5真正要你掌握的,是自动化工具的思想。你需要借助Python脚本或Sqlmap这样的自动化注入工具。例如,使用Sqlmap的基本命令:sqlmap -u "http://localhost/sqli-labs/Less-5/?id=1" --batch --current-db工具会自动完成上述猜解过程,并输出结果。这一关是从手工注入向自动化实战过渡的关键,让你理解盲注的原理以及工具为何必要。
4.3 Less-6:双引号闭合的布尔盲注
Less-6是Less-5的“双引号”版本。测试方法相同,只是闭合符号从'变成了"。 使用?id=1" and 1=1--+和?id=1" and 1=2--+来确认布尔盲注点。 后续的猜解Payload也只需将单引号替换为双引号,例如:?id=1" and length(database())=8--+。
至此,前六关的核心挑战已经完成。它们系统地训练了你对SQL注入最基础也是最关键的认知:注入点的探测、闭合方式的判断、联合查询的利用,以及盲注概念的引入。
5. 贯穿始终的“肌肉记忆”:手工测试流程与思维模型
通关不是目的,形成一套可靠的、可复用的测试流程和思维模型才是。无论面对什么类型的注入点,以下这套“组合拳”应该成为你的本能反应。
第一步:信息收集与初步探测
- 观察:页面功能是什么?查询?登录?有哪些参数?(id, name, search等)
- 试探:对每个参数尝试添加
'、"、)、')等符号,观察页面是否出现语法错误、行为异常(如空白、跳转、延迟)。 - 判断类型:根据错误信息或
and 1=1/and 1=2的返回差异,初步判断是字符型(需闭合)还是数字型。
第二步:确认注入与判断闭合
- 构造永真/永假:根据猜测的类型,构造
?id=1' and '1'='1或?id=1 and 1=1,确认页面在True和False条件下表现不同。 - 精确闭合:如果报错信息中有引号或括号提示,严格按照提示构造闭合。如果没有,则系统尝试
'、"、')、")这几种常见组合。
第三步:获取信息(有回显场景)
- 判断列数:使用
ORDER BY n递增n,直到报错,列数=n-1。 - 寻找回显点:使用
UNION SELECT 1,2,3,...,n,并将原查询置空(如id=-1),观察页面哪个位置显示了数字。 - 查询数据:将回显点的数字替换为想查询的函数,如
database(),user(),version(),进而通过information_schema查询表名、列名,最终拖取数据。
第四步:应对盲注(无回显场景)
- 确认布尔状态:用
and 1=1和and 1=2确认页面存在两种可区分的状态。 - 自动化思维:立即想到使用工具(Sqlmap)或编写脚本,利用
length()、substr()、ascii()、if()、sleep()等函数进行条件判断,逐位猜解数据。 - 时间盲注判断:如果页面无任何变化,尝试
and sleep(5),观察响应是否明显延迟,以判断是否存在基于时间的盲注。
这套流程不是死板的,而是帮助你建立有序的测试思维,避免东一榔头西一棒子。在实际的渗透测试或CTF比赛中,面对一个黑盒目标,这就是你最基本的“侦查路线图”。
6. 从靶场到实战:思维跃迁与常见“坑点”复盘
在sqli-labs里,你知道这里一定有SQL注入,只是形式不同。但在真实世界或综合靶场(如DVWA、Pikachu)中,情况要复杂得多。
思维跃迁:从“找注入”到“找可能”
- 参数不一定是
id:可能是user、search、page,甚至是Cookie、HTTP头部(如X-Forwarded-For、User-Agent)。 - 注入点不一定在GET:POST表单提交、JSON格式的API接口、XML数据,都可能存在注入。你需要用Burp Suite拦截这些请求进行测试。
- 可能有防护:真实应用会有WAF(Web应用防火墙)、输入过滤、预编译语句(PDO)等。你的
'、union、select可能被拦截或转义。这时需要学习绕过技巧,如大小写混淆、双写关键字、编码、使用注释符分割、利用数据库特性等。这些在sqli-labs后面的关卡会涉及。
常见“坑点”复盘
- 注释符的使用:在URL中,
--(空格)注释符末尾的空格必须被处理,否则会被浏览器吃掉。所以常用--+(+是URL编码的空格)或%20。在Burp Suite里直接加空格有时需要手动编码。 UNION查询的列数必须一致:这是最常犯的错误之一。一定要先用ORDER BY精确判断列数,否则UNION查询会失败。- 忽略错误信息:数据库抛出的错误信息是宝藏,它可能直接暴露数据库类型、路径、部分SQL语句结构。一定要开启浏览器的错误显示(PHP的
display_errors在测试环境常为On),并仔细阅读。 - 盲注时的“细微”差异:布尔盲注依赖页面差异。有时差异非常微小,可能是一句话里多一个标点,或者HTML源码里某个隐藏字段的值不同。需要用浏览器“查看网页源代码”功能仔细比对,或者用Burp Suite的“对比”(Comparer)功能。
- 工具依赖与手工能力:Sqlmap等工具很强大,但绝不能替代手工理解。工具跑不出来时,往往需要手工分析过滤规则,构造特殊Payload。手工能力是根基,工具是延伸。
通关sqli-labs的前六关,你收获的不仅仅是几个Payload,而是一套关于SQL注入的底层认知框架和测试方法论。这套东西,在你后续挑战更复杂的关卡(如双查询注入、报错注入、堆叠注入、二次注入)以及面对真实世界五花八门的注入场景时,会成为你最坚实的底气。记住,所有复杂的技巧,都是这些基础元素的排列组合。把基础打牢,后面的路才会越走越宽。