☰
SQLi-Labs靶场搭建与SQL注入通关排坑指南
2026/9/29 7:11:37 网站建设 项目流程

折腾靶场这事儿我前前后后干过不下十次,光 SQLi-Labs 就在三台配置完全不同的机器上重装过。每次帮新人远程搭环境,卡点几乎都一模一样:PHP 版本太高,页面刷出来一堆 deprecated 警告;数据库连不上,setup 页面点半天没反应;好不容易跑起来了,输个 payload 却因为一个注释符被浏览器吃掉而一脸茫然。所以这篇文章不讲虚的,就是把 SQLi-Labs 这套 SQL 注入靶场从零到能打通关的完整过程摊开来讲,包括版本怎么选、配置怎么改、坑在哪、怎么排。

SQLi-Labs 本质上是一套开源的 SQL 注入练习环境,作者把常见的注入场景拆成了 65 个关卡,从最基础的联合查询回显,到报错注入、布尔盲注、时间盲注,再到 POST 注入、Cookie 注入、堆叠查询、二次注入、WAF 绕过,基本把 Web 端 SQL 注入的常见形态覆盖了一遍。它不像 DVWA、Pikachu 那样追求"全类型漏洞覆盖",而是死磕注入这一个点,所以深度上更适合想真正把注入吃透的人。

这篇内容适合三类人:刚入门 Web 安全、需要有个能随便折腾的练习环境的学生;从运维或开发转安全、想补注入这块短板的从业者;以及需要给团队做内部培训、要一个可复现演示环境的讲师。前提说清楚,所有练习只在你自己搭建的本地环境里进行,未经授权去测试别人的系统是不被允许的,这条线不能碰。

1. 为什么要自建 SQLi-Labs 而不是用在线靶场

1.1 在线靶场的三个现实问题

网上确实有不少提供在线 SQLi-Labs 的站点,点开就能用,看起来省事。但真拿它当主力练习环境,问题很快会暴露出来。第一个是数据不留痕。你今天的注入进度、自己整理的 payload 笔记、临时改的配置,服务端一重启或者维护一次就全没了,下次打开又是从零开始。练注入这种需要反复试错的东西,没有连续性是很要命的。

第二个是改不动。SQLi-Labs 有个 setup 页面可以重置数据库,但在线站一般不会给你这个权限,更别说去改 db-creds.inc 里的连接参数、手动往数据库里插几条测试数据了。而恰恰是这些"改配置"的动作,才能让你真正理解关卡背后的数据流是怎样的。

第三个是不稳定和互相干扰。公用环境的资源是共享的,你正在跑时间盲注,隔壁有人用脚本批量刷关卡,结果你的 sleep 计时全是噪声,判断逻辑完全乱套。更别提有些在线站点本身挂着广告或者做了访问限制,体验一言难尽。所以我的建议很直接:在线靶场拿来快速看看某个关卡长什么样还行,真要系统性练,必须本地自建。

1.2 本地环境带来的可控性价值

本地搭起来之后,最先感受到的变化是"随便造"。你可以直接打开数据库客户端,看到 security 库里 users 表的每一条记录长什么样,知道 id=1 对应 admin、id=2 对应 Angelina,那么当你注入出用户名的时候,就能立刻验证对不对,而不是只能盯着页面上的回显猜。这种"心里有底"的感觉,对建立注入的直觉帮助极大。

其次是可调试。PHP 的错误日志、MySQL 的慢查询日志、Web 服务器的访问日志,全都在你手边。某个关卡死活不出结果,你可以去日志里看看到底是 SQL 语法错了,还是被引号转义了,还是根本没执行到那一步。这种排查能力,其实比记住几个 payload 值钱得多。

再就是可以随时回滚。VMware 或者 VirtualBox 打个快照,改坏了十秒钟还原。我自己的习惯是每通关十个 Less 打一次快照,这样一旦后面把配置改崩了,不用从头重装。

1.3 开工前的环境清单

在动手之前,先把要准备的东西列清楚,避免装到一半发现缺东西。需要的是一个能跑 PHP 的 Web 环境(Apache 或 Nginx 都行,靶场源码更适配 Apache)、一个 MySQL 数据库、以及 SQLi-Labs 的源码包。源码在主流开源代码托管平台搜索 sqli-labs 就能找到,认准作者是 Audi-1 的那个仓库,别下到二次打包改过的版本,有些魔改版动了源码逻辑,反而会让关卡行为和官方不一致。

硬件上没什么要求,一台 2 核 4G 的虚拟机足够。真正需要花心思的是版本匹配问题,这部分放到下一节详细说,因为它决定了你后面是顺风顺水还是一路报错。

2. 环境选型与版本匹配的底层逻辑

2.1 三种主流搭建方案横向对比

搭建方式我试过四种,各有适用场景,先用一张表把差异摆出来,你看完基本就能对号入座。

方案上手难度资源占用可移植性适用人群
phpStudy 集成包极低中低Windows 新手,只想快速跑起来
XAMPP低中中跨平台,需要一个通用 LAMP 环境
Linux 手动装 LAMP中高低高想顺带练 Linux 和运维的人
Docker 容器中低极高有容器基础,追求环境隔离和秒级重建

Windows 用户想省事,phpStudy 这类集成包确实最快,装完就有 Apache、PHP、MySQL 三件套,图形界面点点就能切版本。但它的缺点是版本切换是全局的,如果你同时还要跑别的项目,容易打架。XAMPP 类似,胜在跨平台。

我个人现在更推荐 Docker 或者 Linux 手动装。原因很简单,容器化的靶场可以一条命令重建,出问题直接删掉重来,不会污染宿主机环境。而且很多公开的靶场镜像里已经预置好了兼容的 PHP 版本,省掉了踩版本坑的环节。如果你对容器完全没概念,那就老老实实走 Linux 手动装,过程虽然长一点,但每一步你都知道在干什么,出问题也知道从哪查。

2.2 PHP 版本才是最大的坑

这是我要重点强调的一块,因为 SQLi-Labs 的原版源码对高版本 PHP 兼容性很差。具体表现在哪:源码里大量使用了 PHP 早期风格的写法,在 PHP 7.x 下会刷出成片的 Notice 和 Warning,虽然不影响核心逻辑,但页面上全是红字,回显都被挤乱了;到了 PHP 8.x 更麻烦,某些在旧版本里还能用的函数被彻底移除,直接触发 Fatal error,页面白屏。

所以版本选择上有两条路。第一条路是降级,用 PHP 5.6 或者 5.4 来跑,这样源码可以原封不动直接用,体验最接近作者设计时的样子。很多集成环境里都还保留着 5.x 的版本可以切。第二条路是升 PHP 但改代码,把 sql-connections 目录下那几个连接文件里的旧写法改成新写法,同时把display_errors打开看清具体报什么错,再逐个修。这条路更贴近真实工作场景,因为你迟早要面对"老代码跑在新环境上"这种问题,但前期会花掉不少时间。

我的建议是,第一次搭就用 PHP 5.6,先把 65 关过一遍,把注入本身的逻辑搞明白。等你对注入已经熟了,再换成 PHP 7.x 或者 8.x 重装一遍,专门体验修兼容性问题的过程,那时候你的心态会完全不同。

2.3 MySQL 8 的认证插件会卡住你

另一个高频坑点出在数据库这边。MySQL 8.0 之后,默认的身份认证插件从早年的方式换成了caching_sha2_password。而 PHP 5.6 自带的数据库扩展不认识这套新认证方式,表现出来就是连接被拒绝,报类似 "The server requested authentication method unknown to the client" 这样的错。很多人在这卡一两个小时,以为是自己密码写错了。

解决办法有两个。一是在 MySQL 里把你的账号认证方式改回兼容老客户端的模式,用这样一句:

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码'; FLUSH PRIVILEGES;

二是干脆用 MySQL 5.7 或更低版本,从根上避开这个问题。我第一次搭的时候就是被这个坑住,后来养成个习惯:只要是用老 PHP 配新 MySQL,先去确认认证插件,再谈其他。

顺带提一句字符集。数据库和表统一用utf8mb4,排序规则用utf8mb4_general_ci,这样注入出来的数据不会出现问号乱码。原版建表脚本用的是较老的字符集设置,如果你后面要自己插中文测试数据,记得改一下。

3. 从源码到浏览器跑通首页的完整实操

3.1 源码获取与目录结构解读

把源码包下载下来之后,解压到一个 Web 可访问的目录里。Linux 下通常是网站的根目录,Windows 集成环境一般是集成包指定的 www 或 htdocs 目录。文件夹名建议保持sqli-labs不变,因为有些关卡内部跳转用的是相对路径,改了名字虽然一般也能跑,但万一遇到跳转异常,多一个变量就多一层排查成本。

解压完先别急着访问,花两分钟看看目录结构,这对后面排错很有用。根目录下的index.html是总入口,列出全部关卡;sql-connections目录是心脏,里面放着数据库连接配置文件和建表脚本;然后就是一排以 Less 开头的文件夹,每个对应一关。看懂这个布局,你后面遇到"页面 404"就知道是路径问题,"连不上数据库"就知道要去 sql-connections 里翻配置。

源码里还有几个说明文档,虽然写得不算详细,但快速扫一眼能知道作者的意图和版本信息,值得花时间读读。

3.2 数据库连接文件到底改什么

sql-connections目录下有个数据库连接配置文件,原版大概是这个样子:

<?php $host = "localhost"; $dbuser = "root"; $dbpass = "root"; $dbname = "security"; $dbname1 = "challenges"; ?>

你要改的就是$dbuser和$dbpass,把它换成你本地 MySQL 的真实账号密码。很多人卡在这里的原因是:集成环境里 MySQL 的 root 密码可能是空的,也可能是集成包自己设的,还有人是用 phpMyAdmin 建了个新用户,结果忘了给这个用户对 security 库的权限。

我的经验是,练习环境里最省事的做法是直接用 root,密码设一个你记得住的,比如 root123,然后确认两件事:一是 MySQL 服务确实在跑,二是这个账号能从本机连上。可以用命令行验证:

mysql -u root -p -h 127.0.0.1

能进去说明账号密码没问题,进不去就是账号或服务的问题,跟靶场源码没关系,先把这一层解决掉再往下走。

注意这里有个细节容易被忽略:$host写localhost和写127.0.0.1,在某些 PHP 环境下走的连接方式不一样,一个是 Unix socket,一个是 TCP。如果 socket 路径配得不对,就会连不上。遇到莫名的连接失败,先把 host 改成127.0.0.1试试,这是我踩过的坑。

3.3 执行建库脚本的两种方式

配置改好之后,要初始化数据库。SQLi-Labs 提供了两种方式,一种是访问首页后点击页面上的初始化入口,它会带你去执行sql-connections下的建表脚本,自动创建security和challenges两个库以及相关的表。另一种是直接访问那个脚本的地址,效果一样,但在页面点击没反应的时候特别有用。

建表脚本执行成功后,页面上会有一段提示,告诉你数据库重置完成了。如果这一步报错,八成是三种原因之一:连接配置里的账号密码错了、MySQL 服务没启动、或者账号没有建库的权限。逐个排除就行。

建完之后强烈建议你做一件事:打开数据库客户端,连进去看看security库里的表。你会看到 users 表里有一批用户记录,emails、uagents、referers 这些表也都有内容。把这些内容扫一眼记住大概,后面注入的时候你就能拿注入结果和真实数据对照,验证自己的 payload 到底对不对。这个动作能极大加快你建立注入直觉的速度,很多人跳过这一步,练了几十关还是靠"看页面有没有报错"来判断,效率很低。

3.4 访问首页与初始配置确认

数据库建好之后,在浏览器里访问你的靶场地址,正常应该能看到一个列出了所有关卡的首页。到这里环境就算通的,剩下的就是练习。

有个小细节值得注意:如果你把 Web 服务端口改成了非 80 端口,比如 8080,那么所有 URL 都要带上端口号,包括后面注入时用的 payload 地址。看着是废话,但我见过不止一个人在浏览器里改了端口,复制 payload 的时候又忘了带,然后对着一个 404 页面怀疑是不是 payload 写错了。

首页上还有几个辅助入口,比如重置数据库的链接、查看源码的链接、以及一些说明页面。这些在日常练习里用得不多,但重置数据库那个键建议记住位置,因为你在做某些需要插入数据的关卡时,把库搞脏了要能一键还原。

4. 通关前必须搞懂的核心原理

4.1 判断闭合方式是一切的前提

不管哪一关,第一步永远是搞清楚注入点的闭合方式。SQLi-Labs 的关卡之所以有难度梯度,很大一部分原因就是闭合方式在变。你可能遇到的形态包括:数字型,直接拼在id=后面;字符型,用单引号包裹;带括号的,比如id=('1')这种多层嵌套;还有用双引号包裹的。

判断方法很朴素:先输一个单引号,看页面有没有报 SQL 语法错误。如果有,说明单引号能打破原有结构,大概率是字符型;如果没反应,可能是数字型,也可能报错被关掉了,那就试试and 1=1和and 1=2看回显差异。有报错信息最好办,报错里往往会直接把拼接后的 SQL 片段回显出来,那你连猜都不用猜。

这里有个必须记住的操作细节,也是新手踩得最多的坑:注释符。在浏览器地址栏里,#会被当作 URL 的锚点符号,浏览器根本不会把它发到服务端。所以你在 URL 里做注入,注释要用--+(注意是符号后面跟一个加号,加号在 URL 里会被解码成空格),或者把#写成%23。这个点不知道,你会觉得"我一样的 payload 别人能用我不能",然后就卡在那了。

4.2 联合查询注入的完整链路

过关的时候,联合查询是你最该先掌握的思路,因为只要有回显,它就是最快的。以最经典的字符型关卡为例,完整流程是这样:先用单引号确认注入点存在,然后用order by判断列数,比如?id=1' order by 3 --+正常、order by 4报错,说明查询返回三列。这一步的原理是order by后面跟数字时,数据库会按第 N 列排序,超出列数就报错,等于告诉了你查询结果的列宽。

拿到列数之后,把前面的查询条件置为假,让原查询没结果,这样页面上显示的就全是你的联合查询结果。常见的做法是把 id 写成一个不存在的值,或者用负号,然后接union select。再下一步是找回显位置,先随便填几个数字,看页面上哪个位置显示了数字,那个位置就是你可以往外输出数据的地方。

有了回显位,剩下的就是查数据。常规顺序是:先查当前库名和数据库版本,确认环境;再去系统信息库里查表名;拿到表名后查字段名;最后查具体数据。取字段的时候如果一列要拼多个值,用聚合函数把结果连成一串比较方便。这一整套链路看着步骤多,但熟手做下来就是几十秒的事,关键在于你要理解每一步在问数据库什么问题,而不是背 payload。

4.3 报错注入与盲注的取舍逻辑

当页面不回显数据,只回显报错信息的时候,报错注入就派上用场了。它的思路是故意构造一个会报错的表达式,把想查的数据拼进错误信息里,让数据库自己把数据"喊"出来。常见的手法是把查询结果拼接到会产生格式错误的函数参数里,这样数据库在报错时就会把你拼进去的数据一起显示。

而当页面既不回显数据、也不回显报错的时候,就进入盲注领域了。盲注分两种:布尔盲注和时间盲注。布尔盲注靠的是页面在两个不同条件下的细微差异,比如页面正常显示和有内容缺失,你通过构造真假条件来一个字符一个字符地推断数据。时间盲注更极端,页面完全没差异,只能靠构造条件让数据库在条件成立时延迟响应几秒,通过响应时间来判断真假。

这里要提醒的是,时间盲注虽然看起来很酷,但对环境稳定性要求高。如果你在虚拟机里跑,宿主机同时还在干别的重活,响应时间会抖,容易误判。所以练盲注的时候尽量把机器空出来,或者把延迟时间设大一点,宁可慢也别误判。另外,布尔盲注自动化脚本写起来不复杂,但我的建议是前期一定手写几遍,搞清楚二分查找是怎么缩短猜测次数的,这个思维训练比脚本本身有价值。

5. 启动失败与常见故障排查

5.1 症状对照速查表

搭环境出问题的时候,最忌讳漫无目的地试。先把症状和可能原因对上号,效率会高很多。

症状最可能的原因优先排查动作
首页 404目录名不对或端口不对确认访问路径大小写、URL 是否带端口
页面全白PHP 致命错误打开错误显示,看日志第一条 Fatal
满屏警告PHP 版本过高降版本或关闭错误显示
数据库连接失败账号密码错或认证插件不兼容命令行先测连通性
setup 点击无反应前端跳转被拦直接访问建库脚本地址
注入无回显闭合方式或注释符不对换单双引号、改注释写法
数据乱码字符集不统一数据库与连接字符集都设 utf8mb4
端口冲突80 被其他服务占用换端口并同步改所有 URL

这张表覆盖了九成以上的问题,剩下的那一成通常是多个问题叠加,那就按顺序从底层往上排:先确认数据库服务,再确认 Web 服务,再确认 PHP 能否连库,最后才是靶场源码本身。

5.2 让报错看得见的两个开关

很多新手排查困难的根本原因不是不会修,而是看不到错误信息。PHP 默认在生产配置下会把错误藏起来,页面白屏什么都看不到,等于蒙着眼睛修车。所以搭建练习环境的第一步,应该是把错误显示打开。

找到 PHP 的配置文件,把display_errors设成On,把error_reporting调到显示所有错误。改完重启 Web 服务,再刷新页面,之前藏起来的报错就会直接显示出来,通常会精确告诉你哪个文件第几行出了什么问题。这一招解决白屏问题的效率极高。

同样的道理也适用于数据库。如果连接失败,别只看页面上那句笼统的提示,去翻数据库的错误日志,或者直接在命令行用同样的账号密码连一次,报错会具体得多。我排查连接问题时,永远是先在命令行复现,因为命令行给的错误信息最完整。

5.3 那些文档里不会写的坑

有几个坑我踩过不止一次,写下来给你省时间。第一个是路径里的空格和中文。如果你把源码放在一个带中文或者空格的目录下,某些环境下会出现文件找不到的问题,看着很莫名其妙,实际就是路径编码的锅。老老实实放在纯英文无空格的路径里。

第二个是浏览器缓存。你改了配置文件,刷新页面发现没变化,别急着怀疑人生,先强制刷新或者换个无痕窗口。我有一次为了一个"改了没生效"的问题折腾了半小时,最后发现是浏览器缓存了旧页面。

第三个是把关卡数据搞脏之后的连锁反应。有些关卡你做了插入类操作之后,原查询的返回结果会变,导致后面依赖固定数据的关卡行为异常。遇到这种情况别硬分析,直接用首页的初始化功能重置数据库,回到干净状态再看。所以我现在养成一个习惯:每完成一批关卡就重置一次,保证每次练习的起点都是一致的。

6. 一些能让你少走弯路的实操心得

搭建和练习过程中,有几个习惯上的建议,我觉得比具体的技术点更值钱。

第一个是给虚拟机打快照。环境搭好、数据库初始化完成、还没开始练习的时候,打一个干净快照。之后每过十关再打一个。这样你无论是改崩了配置还是把数据库搞乱了,都能秒级回滚,不用重装。我早期不屑于做这件事,结果有一次为了修一个环境问题重装了三次,浪费的时间够我多过二十关。

第二个是坚持手写记录。每个关卡至少记三样东西:关卡编号和关卡名、你判断出的闭合方式和注入类型、以及最终跑通的 payload。不用写得很正式,一个纯文本文件就够。这份记录的价值在你练到后面几十关的时候会体现出来,因为那时候关卡之间的技术点是组合的,你需要快速回忆前面用过什么手法。别人整理的攻略当然可以参考,但自己踩过一遍写下来的东西,记忆深度完全不一样。

第三个是主动给自己加难度。官方关卡跑通之后,试着不看任何提示重做一遍,只用命令行工具手工构造请求,或者尝试把某些注入改成自动化脚本来跑。这个过程中你会被迫去理解 HTTP 请求的构造、字符编码的处理、以及如何用代码处理响应,这些能力比记住 payload 有用得多。

最后说一个认知层面的东西:靶场和真实场景差距很大。靶场里报错信息是敞开的、没有防护、参数位置固定、数据库结构已知,而真实环境里这些条件几乎都不成立。所以练完靶场别产生"我已经会注入了"的错觉。靶场训练的是你对注入原理的理解和对各种手法的熟悉度,这是一块扎实的地基,但地基之上还要学怎么在信息受限的情况下做判断,怎么应对各种防护和过滤。我自己的做法是,靶场过关之后去找一些允许合法测试的练习平台,在有防护的环境下再练一遍,感受会完全不同。

至于环境搭建这件事本身,我自己最大的体会是:不要追求一次装到完美。先把能跑起来的最小环境搭好,跑通第一关,然后再慢慢优化版本、调整配置。很多人卡在"我要选一个最完美的方案"上,方案调研了两小时,环境一次没装。这些时间拿去过两关,收获大得多。工具是拿来用的,不是拿来挑的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询