1. Sqli-labs到底是什么——一个SQL注入练习靶场的完整画像
很多做Web开发或安全测试的朋友,都遇到过这么一种尴尬:自己对着教程学SQL注入,看的时候什么都懂,' or 1=1、union select这些payload背得滚瓜烂熟,可一遇到真实环境或者稍微变了点花样的参数,马上就懵了。为什么?因为SQL注入根本不是"会背几条payload"就能解决的问题。它的形态太多了——闭合方式不同、查询位置不同、回显方式不同、过滤规则不同,每个维度一交叉,就能衍生出几十上百种变化。而Sqli-labs,正是为这个问题而生的一个SQL注入专项练习靶场。
Sqli-labs是一套开源的SQL注入学习平台,由印度安全研究人员Audi开发,项目托管在GitHub上,全套基于PHP+MySQL实现。它把SQL注入的各种场景拆解成了几十个关卡,从最简单的单引号字符型注入,一路深入到布尔盲注、时间盲注、堆叠注入、宽字节注入、二次注入、order by注入、报错注入,再到各种过滤绕过场景,层层递进、环环相扣。每一关都是一个刻意构造的漏洞页面,你在本机把它搭起来之后,就像进了一间专门训练SQL注入的"健身房",每个器械对应一种肌肉群,练完一轮下来,对SQL注入的理解完全是两个层级。
这个东西适合谁来刷?我觉得三类人最需要:第一类是刚入门Web安全、想系统化掌握SQL注入原理的安全新人,Sqli-labs能帮你把"听说过但没实际打过"的各种注入类型全部亲手复现一遍;第二类是负责Web开发的后端工程师,很多开发者只在文档里见过"SQL注入"四个字,真让他解释为什么预编译能防注入,他说不清楚,刷完这套靶场,你对参数绑定为什么有效会有肌肉记忆式的理解;第三类是做代码审计、安全测试的从业者,Sqli-labs后半段的绕过关卡,练的是你在代码里识别危险拼接、评估过滤方案强度的那双眼睛。
我自己刷完这套靶场最大的感受是:它帮你建立的不是"记住几十个payload"的记忆力,而是一套"遇到一个注入点该怎么分析、怎么选型、怎么绕过"的方法论。这种能力才是真正能迁移到实战里的东西。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境搭建全流程,以及我踩过的那些坑
Sqli-labs对环境的要求不高,核心就是PHP+MySQL(或MariaDB)+任意Web服务器。Windows上最省事的是用小皮面板(phpStudy)这类集成环境,Linux上就自己装Apache/Nginx+PHP+MySQL。我见过很多人在环境这一步就卡住了,其实原因大多出在几个很具体的地方,我一个个讲。
2.1 源码获取与部署
从GitHub上把项目拉下来,解压之后放到Web根目录。以phpStudy为例,就是放到WWW目录下,目录名建议保持sqli-labs,后面访问也方便。这时候直接用浏览器访问 http://127.0.0.1/sqli-labs/ ,会看到欢迎页面,按照页面提示点击"Setup/reset Database for labs"初始化数据库。
这里有个比较容易踩的第一步:Sqli-labs默认的数据库连接配置在 sql-connections/sql-connect.php 这个文件里,里面写的是 $dbuser ='root'; $dbpass ='root';。如果你本地MySQL的root密码不是root,那初始化的时候就会报数据库连接失败。改掉这个文件里的账号密码就行,数据库名不用动,初始化脚本会自动创建。
注意:如果你用的是MySQL 8.0以上版本,初始化时可能会碰到认证插件不兼容导致连接失败的情况。常见的处理办法是创建一个使用mysql_native_password插件的专用账号给Sqli-labs用,比如
CREATE USER 'sqli'@'localhost' IDENTIFIED WITH mysql_native_password BY 'sqli'; GRANT ALL PRIVILEGES ON *.* TO 'sqli'@'localhost';,然后再把sql-connect.php里的账号改成这个。
2.2 PHP版本兼容性问题
这是新手很容易卡住的一个坑。Sqli-labs的代码基本上是按PHP 5.x和PHP 7.x初期的语法写的,如果你直接用PHP 8.x跑,大概率会报一堆弃用警告,甚至直接白屏。警告还好,白屏就麻烦了。
最省心的方案是用PHP 5.6或7.3/7.4。phpStudy这类集成环境一般支持多版本PHP自由切换,在设置里把网站运行版本切到7.4或者更低就行。如果你用的是Linux自带的PHP 8.0+,建议换PHP 7.4版再跑。说句实在话,Sqli-labs毕竟是老牌靶场,代码风格偏老,没必要为了它去改代码适配新版本,换个低版本PHP是最省时间的。
另外确认一下PHP装了mysqli扩展(老一点的叫mysql扩展)。phpStudy默认通常已经开了,但如果你是自己编译的PHP,很容易漏掉这个扩展。可以在Sqli-labs目录下放一个探针文件,写 <?php phpinfo(); ?> 看看有没有mysqli。没有的话就装上,否则所有关卡都会连接数据库失败。
2.3 关了URL重写和防火墙再动手
Linux下如果配了Nginx,需要确认重写规则不会干扰Sqli-labs的PHP文件访问。多数情况默认配置就够了,但如果你给web根目录配了非常严格的重写规则,可能出现能打开首页但点进关卡就404的情况。保守起见,Sqli-labs单独建一个vhost,不套任何重写规则。
还有一个容易被忽略的坑:电脑上装的杀毒软件或者系统防火墙,有时候会拦截本地PHP环境访问数据库,或者拦截浏览器访问127.0.0.1的请求。我遇到过一例window防火墙拦截Apache端口导致无法访问靶场的情况,排查了半天最后发现是防火墙规则问题。搭建之前先把相关端口放行,或者开发阶段临时关掉本地防火墙,会少很多莫名其妙的烦恼。
环境起来之后,不用急着开始刷题。建议先在浏览器里点开第一关,确认页面能正常加载、能显示数据库查询结果,整个链路才算真正通了。
3. 关卡体系深度拆解:从报错注入到盲注的思路是怎么演进的
Sqli-labs的关卡设计是有清晰脉络的,不是随便堆一堆漏洞页面。理解这条脉络,比单纯一关一关刷过去重要得多。我把整套关卡按类型重新分组,你会发现它其实就是一张SQL注入的知识地图。
| 关卡范围 | 核心主题 | 重点掌握的能力 |
|---|---|---|
| Less-1 到 Less-4 | 联合查询注入的四种闭合方式 | 单引号、双引号、单引号加括号、双引号加括号的识别与逃避 |
| Less-5 到 Less-6 | 报错注入(updatexml、extractvalue) | 无回显场景下通过报错信息泄露数据 |
| Less-7 到 Less-10 | into outfile写文件 / 布尔盲注 | 文件写入的前提条件、页面差异分析 |
| Less-11 到 Less-17 | POST注入与登录场景 | 参数位置变化后如何调整注入思路 |
| Less-18 到 Less-22 | User-Agent、Referer等Header注入 | 非业务参数也可能成为入口 |
| Less-23 到 Less-31 | 注释符绕过、大小写混合、AND/OR绕过 | 基础过滤场景的绕过思路 |
| Less-32 到 Less-37 | 宽字节注入(GBK编码) | 编码层漏洞的成因与利用 |
| Less-38 到 Less-45 | 堆叠注入 | 一条SQL之外还能执行什么 |
| Less-46 到 Less-53 | order by注入 | 排序列名/排序方向的注入面 |
| Less-54 到 Less-67 | 数据库无关的挑战模式 | 脱离固定SQL逻辑后从零开始分析 |
| Less-68 到 Less-87 | 组合过滤与实战环境模拟 | 多种绕过技术的综合运用 |
这张表你多看几遍,会发现它其实在教你一个很重要的方法论:SQL注入的先验知识获取。你拿到一个注入点,第一件事不是急着丢payload,而是判断它是什么类型的注入——字符型还是数字型、闭合符是什么、有没有过滤、回显有没有。Sqli-labs的关卡编排,本质上就是在反复帮你训练这个"先探测、再选型、后构造"的流程。
前四关是基础中的基础,很多人一天就能刷完。它的核心是让你通过不断调整闭合符,找到SQL语句拼接位置的"边界"。比如Less-1是 '$id' 这种单引号闭合,Less-2是数字型直接嵌进去,Less-3是 '($id)',Less-4是 "($id)"。每一种闭合方式,你都要走一遍同样的流程:先用引号闭合掉前半个边界,再用注释符处理掉后面多余的SQL片段,然后才能通过union select控制查询结果。
从Less-5开始,很多刚刷的人会觉得不适应,因为页面不再直接显示查询结果了。这就是SQL注入学习的第一道坎:从"有回显"到"无回显"。Less-5和Less-6使用的是报错注入,通过updatexml或者extractvalue函数,在SQL语句中制造一个语法错误,让数据库把错误信息(往往会携带查询结果的一小段)返回到页面里。这个思路非常关键:注入不一定要拿到完整的数据回显,只要目标系统存在任何"信息出口",都可以想办法利用它把数据带出来。
Less-7引入了into outfile写文件。这一关的意义不仅在于"能不能把数据写到web目录里然后访问",更在于理解文件写入攻击的前提条件:MySQL的secure_file_priv参数是否限制、当前数据库用户是否有FILE权限、web目录是否可写。这个知识点放到真实渗透测试里,是拿webshell的经典路径之一。虽然现在实战里机会越来越少,但这个思路对理解"数据库权限能影响什么"很有帮助。
到了Less-8和Less-9,就是纯正的盲注环节了。Less-8是布尔盲注,页面会因为你构造的查询条件真假而显示不同的内容——比如显示正常页面和显示空页面。你需要通过这种"是/否"的二元差异,用substr、ascii这些函数逐字符把数据库名、表名、字段名提取出来。Less-9是时间盲注,页面在那两种情况间没有可观察的差异,只能通过sleep()函数制造延迟来判断条件真假。这一关把很多人的心态磨得够呛,因为一个字符一个字符地猜,效率极低。所以到后面大家都会去写脚本自动化,或者直接在Burp Suite里用Intruder正则匹配。这其实也是Sqli-labs想让你学到的东西:盲注必须脚本化,人肉跑太痛苦了。
POST注入、Header注入这些关卡,则是把注入思路从"URL参数里的id"拓展到"所有进入后端SQL拼接的用户可控数据"。登录表单里的用户名和密码可以是注入点,User-Agent、Cookie、Referer这些HTTP头也可以是注入点。很多开发者在写SQL拼接时只防了GET参数,忘了HTTP头里的数据同样会被拼进去。我在做代码审计时见过不止一次,后端老老实实把GET参数参数化处理了,结果User-Agent原样拼进了SQL,那叫一个欲哭无泪。
堆叠注入、order by注入、宽字节注入,每一段都在把一个具体的、实战中可能遇见的怪诞场景掰开揉碎给你看。堆叠注入让你思考一条SQL语句的结尾之后还能不能跟上更多语句;order by注入让你意识到即使是排序字段这种看起来人畜无害的位置,只要它被拼进SQL,就能做报错注入;宽字节注入则展示了编码不一致如何在数据库层面"吃掉"转义符,让本被安全处理的引号重新变成注入点。
等到后半段Less-54之后,挑战模式会刻意给你更少的提示,你不再有一个"模板化"的SQL结构可以套用,必须自己通过报错信息、页面差异去推断后端逻辑。这个阶段本质上是在做综合摸底,看你是不是真的把前面的方法内化成自己的分析了。
4. 通关方法论:我从刷题中提炼出来的五步分析框架
Sqli-labs刷到中后期,我发现自己不再依赖"背答案"了,而是形成了一套对任何注入点都适用的分析流程。这套流程不是靶场自带的,是刷多了之后自然沉淀出来的,我把它拆成五步,每一步对应你在浏览器和Burp Suite里实际要做的操作。
4.1 第一步:定位参数与SQL语句的接触面
拿到一个页面,先别看它是不是注入点,先把这个请求里所有能被你控制的数据列出来。URL里的查询参数、POST表单里的字段、Cookie中的值、User-Agent和Referer等HTTP头——这些都是"用户可控数据"。Sqli-labs从Less-11开始专门训练这一点,其实就是在提醒你:不要只盯着id=1这种一眼就是查询参数的入口,任何一个能被后端拿去做逻辑的值,都可能成为注入的接触面。
实操上的建议是:把每一个可疑参数都丢进Burp Suite的Repeater里,逐个改变它的值,观察页面响应是否有差异。你可能会发现,有些参数改了值之后页面毫无变化,那它大概率没有被用于后端逻辑;有些参数改了值页面内容就变了,那它就是重点关注的候选对象。先把入口筛出来,才不会在分析时漏掉真正的注入点。
4.2 第二步:判断闭合方式——这是Sqli-labs前二十关的核心训练点
判断闭合方式说白了就是搞清楚:我们插入的payload,最终是被包在什么引号、什么括号里拼接进SQL的。Sqli-labs的Less-1到Less-4就是四种最常见的闭合形态:单引号、数字型(无引号)、单引号加左括号、双引号加左括号。
实操手法是"试探性报错法"。在参数位置丢一个英文单引号 ',看页面是否报错、报错信息长什么样。如果报错信息里能看到SQL语句片段,比如 You have an error in your SQL syntax near ... 'LIMIT 0,1' 这种东西,那很多时候语法错误信息已经把闭合符的类型暴露给你了。如果没有报错,那就得靠"构造真/假条件"来试探——比如把参数从1改成 1 AND 1=1 和 1 AND 1=2,对比页面结果差异。有差异,大概率是数字型注入,不需要闭合引号;没有差异,就得尝试 1' AND 1=1 --+ 这类带引号的写法。
这里有一个非常实用的技巧:在MySQL里,-- (两个减号后跟一个空格)和 # 都可以作为注释符,但在URL里直接传空格会被编码,所以Sqli-labs和真实渗透里常见的写法是 --+,这个加号在URL编码里代表空格。你自己在Burp里测试的时候,也可以直接用浏览器栏输入 --+,或者在Burp的URL编码里把空格写成 %20。
4.3 第三步:确定注入类型——回显还是盲注,这是分岔路口
闭合方式判断出来后,SQL语句的"拼接形状"已经在你的脑子里成形了。接下来要决定的是:用哪种注入技术来提取数据。决策依据只有一个,就看目标页面是否存在"数据回显通道"。
怎么判断回显?在你构造了一个union select注入且闭合正确的情况下,如果页面出现了你指定的查询结果字段,说明这条通道是通着的,直接用联合查询注入,效率最高。如果页面没有任何查询结果回显,就尝试构造一个明显会报错的表达式(比如updatexml(1,concat(0x7e,database()),1))看页面是否输出错误信息,能输出就转报错注入。报错注入也不行,就只剩下盲注两条路:页面真假差异明显的用布尔盲注逐字符提取;页面啥差异都没有的,用时间盲注靠延迟来判断。
这一个决策分支非常关键,因为选错路线会浪费大量时间。你在Less-5这一关上尝试union查询半天没结果,其实不是payload有问题,而是这关根本不存在可回显的查询结果,得换技术路线。这也是Sqli-labs为什么从Less-5开始就强制你切换思路——它在训练你建立"回显判断优先于payload构造"的意识。
4.4 第四步:构造提取语句——在已知SQL形状上精准下刀
到了这一步,SQL语句的拼接形状已经明确了,比如你知道后端大概是 SELECT username, password FROM users WHERE id='$id' LIMIT 0,1 这种结构。这时你要构造的union select注入,就要让自己查询的字段数和后端原始查询的字段数完全一致,否则MySQL会报"column number doesn't match"之类的错误。
字段数怎么确定?经典手法是用order by逐步增加数字。ORDER BY 1 正常,ORDER BY 2 正常…… 一直试到 ORDER BY n 报错,说明原始查询的字段数就是n-1。拿到这个数字后,UNION SELECT 1,2,3,...,n 再把页面里回显出来的数字位找出来,这些位就是你可以替换成database()、version()、table_name这类函数的位置。
盲注场景下,不需要关心页面的字段回显位置,你只需要在闭合符处理正确之后,把payload里的布尔表达式或时间延迟函数放在合适的位置。比如布尔盲注经典的 1' AND (SELECT ascii(substr(database(),1,1)))>100 --+,它不依赖任何回显位,只依赖页面真假变化。
4.5 第五步:确认信息出口并反复验证
很多人在拿到第一个库名、第一张表名之后就得意忘形,直接开刷下一关。我的建议是:每拿到一个结果,都要做一次对照验证。比如你用布尔盲注猜出了database()的第一位字符是115(对应's'),那你应该再构造一个 =115 的条件确认页面为真,再构造一个 =114 确认页面为假。两次验证都符合预期,这个字符才算是真正确定下来了。
这一个习惯特别重要,因为盲注场景下payload构造容易因为闭合符、编码问题出现"假阳性"——你以为猜对了,其实是页面无论条件真假都显示同一状态。这种错误一旦发生,后面提取的整个数据链就全错了,而且特别难排查。我刷Sqli-labs时吃过一次亏,Less-8里有一个字符猜错了,导致后面几个表名的拼接怎么都对不上,最后回头逐字符复查才找到问题。从那以后我就养成了"每条关键数据至少双验证"的习惯,虽然慢一点,但结果可靠。
5. 绕过过滤器背后的原理,以及防御者应该从中看到什么
Sqli-labs的Less-23之后,大量的关卡在训练一个能力:绕过服务端对输入的黑名单过滤。这些关卡不只是在教你"怎么绕",每一个绕过技巧背后,都对应一类过滤方案的设计缺陷。从防御者的角度重新审视这些缺陷,比单纯学会绕过更有价值。
5.1 注释符被过滤时,SQL语句怎么"闭合"
Less-23过滤了 -- 和 #,这是最简单的过滤手段。很多人第一反应是:payload写不完整了,末尾多余的 ' 怎么办?其实注释只是为了吞掉后面多余的SQL片段,失去注释符之后,我们还可以通过"让闭合符对称"来达到同样的效果——也就是把后面那个多出来的引号也"用掉",而不是注释掉。
举个例子,后端SQL是 WHERE id='$id',当我们输入 1' 时,SQL变成了 WHERE id='1'',多了一个引号导致语法错误。没有注释符的情况下,我们可以输入 1' AND '1'='1,这样拼出来是 WHERE id='1' AND '1'='1',引号成对、语法完整、查询结果恒为真。这个思路在真实渗透里非常常用,因为它不依赖任何过滤规则里对注释符的处理,只要过滤规则没把所有引号都拦死,就能继续构造。
这个绕法给防御者的启示很直接:过滤 -- 和 # 根本没触及注入的根源。注入的根源是"数据被当成了代码执行",而不是"某几个特殊符号出现了"。简单地在输入里删掉注释符、删掉空格、删掉关键字,都是在打地鼠——你永远不知道攻击者下一次会用什么等价写法替代。
5.2 空格被过滤时,用注释符和括号替代
Less-26把空格也过滤了。这个关卡让很多人难受,因为SQL语句里到处需要空格来分隔关键字。合理的替代方案有两个:一是用注释符 / **/ 替代空格,因为MySQL会把注释符当作空白分隔符来处理,所以 SELECT/**/user() 能正常工作;二是利用括号来消除空格需求,比如 UNION(SELECT(1),(2)) 这种写法,括号本身就起到了分隔作用。
从防御角度看,空格过滤之所以可以被绕,是因为它针对的是"表达式的形态"而非"表达式的语义"。你试图把所有可能的注入写法都列成黑名单,这在理论上就是做不到的——同一个SQL语义,可以用极其多样的词法形式来表达。只要数据没有经过参数化处理就进入SQL语法树,过滤规则再复杂也总有漏网之鱼。
5.3 关键字被过滤时,大小写与双写
Less-25过滤了 OR 和 AND,这是最经典的关键字黑名单。绕过方法有两个方向:方向一是大小写混合,比如 Or、oR,因为MySQL的保留字不区分大小写,而很多过滤规则用大小写敏感的字符串匹配来拦截;方向二是双写,比如 anandd,过滤规则把一个 and 删掉之后,剩下的部分又拼成了一个完整的 and——后端处理时进行一次替换,恰好让攻击者占了这个"只替换一次"的便宜。
这套对抗告诉我们:基于黑名单的过滤天然存在"先匹配后删除"的逻辑漏洞,攻击者可以主动构造删除后合法化的输入。这解释了一个非常重要的事实:WAF和程序内输入过滤为什么只能是"安全深度的加分项",而永远不应该成为"唯一的防线"。它们的作用是把低垂的果实挡住,减少自动化工具的海量扫描噪音,但从安全架构的角度看,可靠的查库路径必须依赖参数化查询或者存储过程这样从语义上杜绝注入的方案。
5.4 宽字节注入的启示:编码不一致是安全的大敌
Less-32到Less-37这一组集中展示了宽字节注入。原理说起来其实不复杂:在GBK编码下,一个中文字符占两个字节,而后端通过 \ 这个反斜杠来转义用户输入中的引号时,如果输入了 %df%27(%df是GBK汉字的前导字节,%27是单引号),MySQL会把这个带 \ 的序列 %df%5c%27 解析成一个汉字加一个"自由"的单引号,转义就失效了。
宽字节注入之所以值得反复刷,是因为它暴露了一个非常本质的问题:web应用层和数据库层之间的字符集不一致,会导致安全边界被击穿。应用层以为自己在做UTF-8的转义,但数据库按GBK去解析时,转义符被"吸收"了。这种跨层语义不一致的安全风险,绝不仅仅存在于SQL注入一个领域——你应该在开发时严格要求应用层与存储层使用同一套字符集,不要给这种"吸收"的机会。
从防御者视角看,看到这些绕过方式之后,你应该明白一个道理:任何纯输入侧的过滤方案,都是可以在某个层面被绕过的。这不是说过滤没有用,而是说它的作用是"提高攻击成本"而不是"杜绝攻击"。真正可靠的防线,是在SQL语句的结构层面把数据和代码分开——也就是参数化查询(PreparedStatement)。你输入的参数在数据库端永远是数据,永远进不了SQL语法树,那无论它长什么样——带注释符也好、带宽字节也好、大小写混合也好——都不会改变SQL语句的结构。Sqli-labs刷到后面,再回头看预编译这个基础概念,你会真正理解它解决的到底是个什么问题。
6. 刷完Sqli-labs之后,如何把训练肌肉转化为实战防御能力
很多人把Sqli-labs当成一个"刷完就完事"的挑战任务,觉得全通关就代表自己"会SQL注入了"。我见过不止一个朋友,Less-1到Less-87全绿了,得意得不行,可一到真实的代码审计里还是看不出问题。为什么?因为靶场给了你一个确定的、只有一个漏洞的页面,而真实代码里,SQL注入的藏身之处往往埋在一堆业务逻辑和框架封装下面,没人告诉你"这里有问题,请注入"。
我自己刷完Sqli-labs之后,做了一次从攻击思维到防御思维的主动转换。这个转换过程里有一些经验和体会,我觉得比刷题本身更值得分享。
6.1 用"闭合思维"重读代码里的SQL拼接
Sqli-labs训练出来最重要的一种肌肉记忆,是看到包含变量拼接的SQL语句时,会本能地去追问一个问题:这个变量的值,最终是以什么方式、在什么位置被塞进SQL的?是放在where条件里、order by里,还是表名、列名、LIMIT子句里?它有没有被引号包裹?有没有被括号包裹?如果有过滤,过滤的规则是什么?
我在做代码评审时,现在碰到典型的字符串拼接式SQL查询,第一反应就是找出这个查询用到的所有外部输入,逐一沿着调用链往回查,看到底哪些数据是从HTTP请求里来的。如果你刷完Sqli-labs仍然不知道怎么在代码里识别这样一个查询的注入面,那说明你还没有把那套"闭合思维"迁移到代码阅读里。补课的方法很简单:找个小型PHP或Java项目,挨个搜索拼SQL的代码位置,手动判断每个位置能不能注入、如果能注入是哪种类型。多做几遍,你就能把靶场里的手感迁移到代码审计里。
6.2 从"绕过过滤"反推安全配置的正确姿势
Sqli-labs的绕过关卡,恰恰是最适合用来检验你的安全配置是否合格的地方。刷到Less-23、Less-26这些关卡时,你应该停下来想一想:如果你的项目里也做了类似的关键字过滤,攻击者会不会用同样或者类似的方式绕过去?
我自己给团队的建议是,把Sqli-labs的常用绕过技巧整理成一张测试用例表,覆盖常见的过滤配置:大小写绕过、注释符替代空格、十六进制编码、双写关键字、宽字节注入、等价函数替换(比如用 SELECT ... INTO OUTFILE 替代其他写文件路径)。每次调整生产环境的WAF规则或者做中间件拦截配置时,都拿这张表考一遍新规则。凡是被这张表里的用例绕过去的配置,就等于没过关。这个习惯让我在给几个项目做WAF规则调优时省了很多返工的力气。
6.3 参数化查询之后,还有哪些纵深防御要做
Sqli-labs刷到后面你会越来越明白一个事实:参数化查询是治本方案,但它不是唯一要做的事。拿我实际参与过的项目来说,数据库账号的最小权限设计、数据库错误信息的脱敏输出、对写入web目录的文件权限约束、对异常SQL报错的监控报警,这些环节单独看跟SQL注入没直接关系,但组合起来构成了对注入利用的纵深限制。
举个直观的例子:如果你的应用统一用参数化查询,SQL注入风险已经降到了很低,但数据库账号如果用的是root高权限账号,一旦某条SQL因为代码疏忽出现了拼接(比如动态表名这种没法参数化的场景),攻击者就能直接读文件、写文件,危害被放大了无数倍。反过来说,即使出现了一个漏网的注入点,只要数据库账号只有SELECT权限,攻击者能做的事就非常有限——读不到数据库之外的文件,写不了webshell,注入的杀伤力被大幅压缩。Sqli-labs里Less-7这种文件写入关卡之所以能成立,就是因为数据库权限足够大。给数据库账号降权,等于从权限层面切掉了这条高杀伤路径。
6.4 把Sqli-labs的关卡改造成你的安全自查工具
最后一个体验,也是我刷完Sqli-labs之后觉得收获最大的一个用法:把这套靶场当成企业内网安全培训的基础教材,而不仅仅是个人练习工具。我给团队做过两轮内部培训,组织方式是让参与者每两人一组,在本地环境刷Sqli-labs,但要求他们过关后必须写一份"该关卡对应的漏洞成因和修复方案"说明,而不是只报一个payload。
这样做效果出奇地好。因为刷题只训练了"找到入口并利用",而真正让开发人员理解SQL注入危害的,是让他们亲手利用一次之后,再亲手去看那一段有漏洞的PHP源码——他们能非常直观地看到,自己平时写代码时那个习以为常的 ${$_GET['id']} 拼接方式,是怎么一步步变成数据库里的SELECT语句、再变成页面上的数据泄露的。很多开发者在这次培训之后,写代码时对于"用户输入直接拼进SQL"这件事的警觉性完全不一样了,他们会自发地使用预编译,会在代码评审里主动指出拼接查询的问题。
这套靶场真正的价值,不在于它有多少个关卡、每个关卡的payload是什么,而在于它提供了一个可以反复把玩的安全实验环境。当你把那些"看起来只属于渗透测试"的技术,反过来理解成"防御方案为什么必须这样设计"的论据时,你的安全认知才算真正闭环了。
