1. 项目概述:为什么Sqli-labs是Web安全入门的必修课
如果你刚接触Web安全,或者想系统性地提升SQL注入实战能力,那么Sqli-labs这个靶场绝对是你绕不开的“新手村”和“训练场”。我第一次接触它的时候,感觉就像拿到了一张藏宝图,里面布满了精心设计的谜题,每一关都对应着一种或多种SQL注入的典型场景。这个靶场之所以经典,是因为它几乎涵盖了SQL注入的所有基础类型:从最简单的字符型、数字型注入,到需要层层绕过的盲注、报错注入,再到需要组合拳的堆叠注入和二次注入。它不只是一个让你“打点”的工具,更是一个让你理解数据库、应用程序和后端代码如何交互的绝佳沙盒。
很多新手会问,网上那么多靶场,为什么偏偏是Sqli-labs?我的体会是,它的设计非常“教学化”。它的代码是开源的,你可以直接看到每一关的后端PHP源码,理解漏洞是如何产生的,以及开发者(或者说,出题人)在哪些地方设置了过滤,又留下了哪些“后门”。这种“上帝视角”对于建立完整的攻击者思维至关重要。你不会只停留在“这个payload能用”的层面,而是会去思考“为什么这个payload能用”以及“开发者为什么会犯这个错”。理解了漏洞的根源,你才能在未来自己开发或审计代码时,有效地避免同类问题。
这次我们要聊的,不仅仅是按部就班地通关,而是聚焦于一个实战中非常有趣且实用的技巧:利用科学计数法绕过过滤。在不少CTF比赛和真实世界的WAF(Web应用防火墙)规则中,单纯的数字或字符串拼接可能会被拦截,但像1e1(表示10)这样的科学计数法表示,却常常成为规则盲区。掌握这个技巧,相当于在你的注入武器库里,又多了一把轻巧而锋利的“手术刀”。
2. 环境搭建与靶场初探:不只是启动一个Docker
工欲善其事,必先利其器。搭建一个稳定、可反复“破坏”的实验环境是第一步。虽然网上有很多一键Docker的教程,但我建议,尤其是初学者,最好能手动从GitHub拉取源码,在本地配置一个LAMP(Linux + Apache + MySQL + PHP)或WAMP环境。这个过程本身就能让你熟悉Web应用的基本运行架构。
2.1 手动部署Sqli-labs的细节与避坑
直接从GitHub克隆项目是最稳妥的方式:
git clone https://github.com/Audi-1/sqli-labs.git将克隆下来的sqli-labs文件夹整个放到你的Web服务器根目录(例如Apache的/var/www/html/或 XAMPP 的htdocs)。接下来是关键的一步:数据库初始化。很多新手卡在这里,因为README里的说明可能不够详细。
修改数据库配置文件:找到
sqli-labs/sql-connections目录下的db-creds.inc文件。用文本编辑器打开,你会看到类似下面的内容:<?php //give your mysql connection username and password $dbuser ='root'; $dbpass =''; $dbname ="security"; $host = 'localhost'; $dbname1 = "challenges"; ?>你需要根据自己本地MySQL的实际情况修改
$dbpass(你的MySQL root密码)和$host(通常是localhost)。创建数据库并导入数据:通过phpMyAdmin或者命令行登录MySQL,然后顺序执行以下操作:
-- 创建名为 security 的数据库 CREATE DATABASE security; -- 使用这个数据库 USE security; -- 导入 sqli-labs 目录下的 SQL 文件来创建数据表 -- 你需要找到 sql-lab 目录下的 .sql 文件,例如: SOURCE /你的路径/sqli-labs/sql-lab/less-1.sql;注意:Sqli-labs的SQL文件可能分散在多个地方,主要的数据表结构通常在
sql-lab目录下。如果导入失败,检查文件路径和MySQL用户的权限。一个常见的坑是,Windows系统下的文件路径需要使用反斜杠或双斜杠。访问安装页面:在浏览器中输入
http://localhost/sqli-labs/(具体路径根据你的放置位置调整),点击页面上的链接进行安装。如果一切顺利,你会看到所有关卡(Less-1, Less-2...)的链接列表。
2.2 核心工具链:Burp Suite与浏览器的协同
对于SQL注入实战,Burp Suite不是可选项,而是必需品。它不仅仅是一个抓包工具,更是你观察HTTP请求与响应、修改参数、进行自动化测试的“作战指挥中心”。
- Burp Suite配置:确保你的浏览器代理设置正确指向Burp(默认127.0.0.1:8080),并在Burp中安装好CA证书以拦截HTTPS流量。对于Sqli-labs这类本地HTTP靶场,HTTP流量就足够了。
- 浏览器开发者工具:与Burp互补。我习惯用开发者工具的“网络(Network)”标签页实时查看请求响应,用“控制台(Console)”执行一些简单的JavaScript来辅助测试(比如快速生成一个长字符串)。两者结合,能让你对数据流有立体的感知。
搭建好环境,你的“黑客实验室”就准备就绪了。接下来,我们将从最基础的关卡开始,逐步深入,并最终亮出“科学计数法绕过”这个绝招。
3. 从Less-1到Less-4:理解注入的基本类型与闭合
Sqli-labs的前几关是精心设计的入门教程,它们分别代表了不同的注入点类型和SQL语句闭合方式。理解这些,是写出有效payload的前提。
3.1 Less-1:基于错误的字符型注入与单引号闭合
打开Less-1,页面通常有一个输入框让你输入ID。随便输入一个数字,比如1,页面会返回对应的用户信息。我们的第一步永远是探测。
- 探测注入点:输入一个单引号
‘。如果页面返回了数据库错误信息(如“You have an error in your SQL syntax...”),那么这里很可能存在SQL注入漏洞,并且原始SQL语句使用了单引号来包裹用户输入。 - 判断列数:使用
ORDER BY子句。在Burp中拦截请求,将ID参数修改为1‘ ORDER BY 3 --+。--+是注释符(在URL中+号代表空格),用于注释掉原SQL语句中后续的部分。逐渐增加ORDER BY后面的数字(4,5...),直到页面报错或返回异常。如果ORDER BY 3正常而ORDER BY 4报错,说明当前查询结果有3列。 - 确定回显点:使用
UNION SELECT语句。将payload修改为-1‘ UNION SELECT 1,2,3 --+。这里ID设为-1或一个不存在的值,是为了让原查询结果为空,从而确保页面显示的是我们UNION查询的结果。观察页面,看数字“1”、“2”、“3”哪个位置被显示了出来。假设数字2和3的位置被显示,那它们就是我们可以用来回显数据库信息的“回显点”。 - 获取信息:利用回显点。将payload修改为
-1‘ UNION SELECT 1, database(), user() --+。这样,我们就能在页面上直接看到当前数据库名和数据库用户名了。后续可以进一步查询表名、列名和数据。
实操心得:
--+里的+在Burp的Repeater模块中发送时,有时需要手动编码为%2b,或者直接使用--(后面跟一个空格)。也可以使用#注释,但在URL中需要编码为%23。多试试几种,确保注释生效。
3.2 Less-2:数字型注入的简洁性
Less-2的页面和Less-1看起来一样,但输入单引号‘可能不报错。这时可以尝试输入1 and 1=2。如果页面正常返回ID=1的数据,说明and 1=2这个假条件没起作用,可能不是数字型。但如果页面返回空或错误,则可能是数字型注入。
对于数字型注入,闭合方式更简单,通常不需要单引号。探测列数的payload直接就是1 ORDER BY 3,UNION注入的payload是-1 UNION SELECT 1,2,3。少了引号的纠缠,整个过程更加直接。这一关的核心是让你意识到:并非所有注入点都需要处理引号闭合,这取决于后端代码如何处理输入参数。如果代码是$id = $_GET[‘id‘]; $sql = “SELECT ... FROM ... WHERE id=$id”;,那么就是数字型。
3.3 Less-3与Less-4:单引号括号与双引号括号闭合
这两关提升了难度,引入了括号()进行闭合。
- Less-3:输入单引号
‘报错信息可能是...near ‘‘‘)‘ LIMIT 0,1‘。这提示我们,原始SQL语句的格式可能是SELECT ... WHERE id=(‘$id‘)。所以我们的闭合需要构造为1‘) --+,先闭合单引号,再闭合括号。完整的探测payload例如:1‘) ORDER BY 3 --+。 - Less-4:输入单引号不报错,试试双引号
“。报错信息可能包含““),说明格式是SELECT ... WHERE id=(“$id“)。闭合方式则为1“) --+。
这两关的训练价值在于,让你学会仔细阅读数据库的错误信息。错误信息是极好的调试工具,它常常会“泄露”出原始SQL语句的结构片段。通过观察错误信息中引号和括号的位置,你可以反推出正确的闭合方式。
| 关卡 | 注入类型 | 推测SQL结构 | 测试Payload(探测) | 关键技巧 |
|---|---|---|---|---|
| Less-1 | 字符型(单引号) | ...WHERE id=‘$id‘ LIMIT 0,1 | 1‘ and ‘1‘=‘1 | 利用单引号闭合,注意注释符使用 |
| Less-2 | 数字型 | ...WHERE id=$id LIMIT 0,1 | 1 and 1=2 | 无需处理引号,直接拼接逻辑语句 |
| Less-3 | 字符型(单引号+括号) | ...WHERE id=(‘$id‘) LIMIT 0,1 | 1‘) and (‘1‘)=‘1 | 需同时闭合单引号和括号 |
| Less-4 | 字符型(双引号+括号) | ...WHERE id=(“$id“) LIMIT 0,1 | 1“) and (“1“)=“1 | 需同时闭合双引号和括号 |
4. 中级挑战:盲注、报错注入与堆叠注入
闯过基础关卡后,你会遇到一些不再直接回显查询结果的挑战。这时候就需要更高级的技巧。
4.1 布尔盲注与时间盲注:当页面“沉默”时
在Less-5和Less-6等关卡,无论输入什么,页面都只返回一种固定的提示(如“You are in…”),不会显示数据库数据,也不会直接报错。这就是盲注(Blind Injection)。
- 布尔盲注:页面虽然不显示数据,但会根据SQL查询语句的真假返回不同的内容(比如一条简单的“存在”或“不存在”的信息)。我们可以通过
and连接条件,像猜谜一样逐位判断。例如,猜解数据库名的第一个字母:1‘ and ascii(substr(database(),1,1))>100 --+。如果页面返回“存在”的状态,说明ASCII码大于100,然后我们再用二分法不断缩小范围(>150, <125...),直到确定准确的ASCII码值,再转换为字符。这个过程极其繁琐,必须借助自动化工具。 - 时间盲注:页面返回内容完全一样,无法通过内容区分真假。这时我们利用
SLEEP()函数。payload如:1‘ and if(ascii(substr(database(),1,1))>100, sleep(5), 0) --+。如果第一个字符的ASCII码大于100,页面会延迟5秒响应;否则立即返回。通过观察响应时间,同样可以逐位猜解。
注意事项:手工进行盲注是低效且痛苦的。实战中一定会使用工具,如SQLmap。但理解其原理至关重要。你可以用Python写一个简单的脚本,自动化发送请求和判断逻辑,这是很好的练习。
4.2 报错注入:让数据库自己“说”出来
在Less-11等关卡,页面会显示数据库的错误信息。我们可以故意构造一个会让数据库报错的语句,并让错误信息中包含我们想窃取的数据。这就是报错注入。
常用的函数有updatexml()、extractvalue()和floor(rand()*2)结合count()产生的重复键错误。
updatexml()示例:1‘ and updatexml(1, concat(0x7e, (select database()), 0x7e), 1) --+。concat(0x7e, ..., 0x7e)中的0x7e是波浪号~的十六进制,用于在报错信息中凸显我们的数据。执行后,错误信息会提示“XPATH syntax error: ‘~database_name~‘”,从而泄露数据库名。- 原理:
updatexml()函数用于更新XML文档,第二个参数需要是合法的XPATH路径。我们传入一个由concat()拼接的非法路径(以~开头),数据库执行时会报错,并将这个非法字符串(即我们拼接的数据)一起显示在错误信息中。
报错注入的优点是一次性能提取较长的数据,效率高于盲注的逐位猜解。
4.3 堆叠注入:执行“多条语句”的威力
从Less-38开始,你会接触到堆叠注入(Stacked Injection)。它的原理是利用;分隔,在一次数据库调用中执行多条SQL语句。这赋予了攻击者更大的破坏力。
例如,一个可能的payload是:1‘; DROP TABLE users --+。如果后端使用了支持多语句查询的数据库驱动(如PHP中的mysqli_multi_query),那么这条语句不仅会执行原查询,还会执行删除users表的操作。
重要警告:在自家靶场可以随便玩,但在任何授权测试之外的真实环境,绝对禁止使用
DROP、DELETE、UPDATE等破坏性语句。堆叠注入常被用于在注入后创建新的数据表、插入后门用户等持久化操作。在Sqli-labs中,你可以安全地尝试1‘; CREATE TABLE test (id int); --+来验证漏洞存在。
5. 高阶技巧:科学计数法绕过的原理与实战
现在,让我们聚焦到标题中提到的“科学计数法绕过”。这个技巧在过滤了某些字符但未过滤e或E的场景下特别有效。
5.1 科学计数法在SQL中的本质
在MySQL、MariaDB等数据库中,科学计数法是一种合法的数字表示形式。1e1等于10,2.5e2等于250,1e0等于1。关键在于,数据库引擎在解析SQL时,会将科学计数法字符串先计算成对应的数字值。
假设后端代码对用户输入进行了过滤,比如将“空格”替换为空,或者过滤了“and”、“or”等关键字,但允许数字和字母e通过。这时,传统的1 and 1=1可能会被拦截。
5.2 绕过场景实战解析
场景一:绕过空格过滤有些WAF或简单的过滤函数会删除或拦截空格。我们可以用科学计数法来替代某些数字,有时可以避免使用空格。
- 原始Payload:
1‘ and ‘1‘=‘1 - 如果空格被过滤,可能变成:
1‘and‘1‘=‘1,这可能导致语法错误。 - 尝试利用科学计数法构造:
1‘and‘1e0‘=‘1e0。这里1e0就是数字1。虽然看起来还有空格(实际上在and前后),但重点是,如果过滤规则是删除空格,这个payload被处理后会变成1‘and‘1e0‘=‘1e0,在SQL中1e0是一个合法的数值令牌,整个语句可能依然能被正确解析。更常见的用法是结合其他无空格技巧,如用括号()、注释/**/代替空格。
场景二:作为数字参与运算,绕过特定字符检测这是更常见的用途。在一些盲注或条件判断中,我们需要用到数字比较。
- 假设我们需要判断数据库名的长度是否大于10:
1‘ and length(database())>10 --+ - 如果
>符号被过滤了怎么办?我们可以利用科学计数法和等式判断。length(database())=11可以写成length(database())=1e1。但如何判断“大于”呢?一个技巧是使用减法:1‘ and length(database())-1e1>0 --+。如果>被过滤,这个思路可能受阻。 - 更巧妙的绕过:利用
between ... and ...语句。1‘ and length(database()) between 1e1 and 2e1 --+。这个语句判断长度是否在10到20之间。between和and是SQL关键字,1e1和2e1是数字,整个payload可能绕过对>、<等比较符的过滤。
场景三:在报错注入中构造数字参数某些报错注入函数需要数字参数。例如exp()函数,当参数过大(>709)时会产生溢出错误。我们可以构造exp(~(select * from (select user())x))来报错输出用户信息。其中~是按位取反,会产生一个很大的数字。如果某些符号被过滤,我们可以尝试用科学计数法构造这个大数,但通常exp()配合~的方式已经足够。
5.3 一个综合案例:Less-?的绕过实战
假设某一关(我们可以设想一个场景)过滤了空格和and关键字,但我们可以使用&&代替and(在MySQL中,&&是逻辑与的另一种写法)。同时,它允许字母e通过。 我们的目标是判断一个条件的真假。
- 正常逻辑:
1‘ && length(database())=10 --+ - 如果
=被过滤:我们可以尝试用like或rlike。但比较数字时,可以尝试用科学计数法配合运算:1‘ && length(database())-1e1=0 --+。这里1e1是10,如果长度等于10,减法的结果就是0。 - 如果
-和=也被过滤(假设过滤得很奇怪):我们可以利用in关键字。1‘ && length(database()) in (1e1) --+。in用于判断一个值是否在列表中,这里列表只有一个值1e1(即10)。
这个案例想说明的是,科学计数法1e1作为一个整体性的“数字令牌”,可以替代纯数字10出现在SQL语句的数值位置。当一些针对数字10的简单过滤规则(比如正则表达式\b10\b)生效时,1e1可能因为不符合该模式而成功绕过。它的价值在于提供了另一种数字表示形式,增加了payload的变体,与注释符、编码、等价函数替换等技巧组合使用,能有效提高绕过成功率。
6. 工具辅助与自动化测试:SQLmap的正确打开方式
手工注入是理解原理的必经之路,但实战中效率至上。SQLmap是每个Web安全工程师的必备神器。然而,直接sqlmap -u “url” --batch往往不是最优解。
6.1 针对Sqli-labs的SQLmap精细化使用
- 指定注入点和参数:
sqlmap -u “http://localhost/sqli-labs/Less-1/?id=1“ -p id-p id指定只测试id这个参数,节省时间。
- 指定数据库类型:
sqlmap -u “...” --dbms=mysql- Sqli-labs用的是MySQL,直接指定可以跳过自动探测,加快速度。
- 设置级别和风险:
sqlmap -u “...” --level=2 --risk=2--level越高,测试的payload越多越全面(也会更慢)。--risk越高,会使用风险更高的payload(如OR布尔注入可能造成大量数据返回)。对于靶场,level=2, risk=2是个不错的起点。
- 获取数据:
- 获取所有数据库:
--dbs - 获取当前数据库:
--current-db - 获取指定数据库的所有表:
-D security --tables - 获取指定表的所有列:
-D security -T users --columns - dump表数据:
-D security -T users -C username,password --dump
- 获取所有数据库:
6.2 结合Burp Suite进行高效测试
我更推荐的工作流是:
- 用浏览器和Burp Suite手工测试,确定存在注入点、类型和闭合方式。
- 将Burp中拦截到的含有注入点的请求,右键保存到文件(例如
request.txt)。 - 使用SQLmap直接加载这个文件进行自动化测试:
sqlmap -r request.txt这种方式会自动识别所有参数、Cookie等信息,最贴近真实请求,成功率很高。
实操心得:SQLmap的
--tamper参数是绕过WAF的利器。它内置了很多脚本,可以对payload进行混淆、编码。例如,--tamper=space2comment会把空格替换成/**/。你可以针对靶场的过滤规则,编写或选择合适的tamper脚本。对于科学计数法绕过,如果SQLmap没有现成脚本,你可以通过观察手工成功的payload,用--tamper自定义一个简单的替换脚本(例如,把特定的数字10替换成1e1),但这需要一定的Python基础。
7. 防御视角:从攻击中学习如何编写安全代码
通过Sqli-labs的实战,我们站在攻击者的角度看到了各种漏洞的成因。现在切换回开发者视角,如何防御?
- 预处理语句(参数化查询):这是最根本、最有效的防御手段。使用像PDO(PHP)或MyBatis(Java)这样的数据库接口,它们会将SQL语句的结构(
SELECT * FROM users WHERE id = ?)与数据(用户输入的id值)分开处理。数据库引擎会先编译语句结构,再将数据作为参数传入,从根本上杜绝了数据被解释为代码的可能性。这是绝对的首选方案。 - 输入验证与过滤:如果因为某些历史原因无法使用预处理,则必须进行严格的输入验证。对于数字型参数,使用
intval()、ctype_digit()等函数确保输入是合法的整数。对于字符型,定义严格的白名单(只允许特定字符集),而非黑名单。黑名单永远会被绕过,科学计数法绕过就是一个例子——如果你只过滤了数字和空格,但没考虑到e,就可能被绕过。 - 最小权限原则:连接数据库的应用程序账号,不应该拥有
DROP、CREATE TABLE、FILE等高危权限。只赋予其完成业务所必需的最小权限(通常是SELECT、INSERT、UPDATE、DELETE),这样即使发生注入,危害也能被限制。 - 错误信息处理:切勿将详细的数据库错误信息直接返回给前端用户。应使用自定义的、模糊的错误页面。这能增加攻击者进行报错注入和盲注的难度。
- 使用Web应用防火墙(WAF):WAF可以作为一道额外的防线,基于规则拦截常见的攻击payload。但切记,WAF是“缓兵之计”,不能替代安全的代码编写。攻击者会不断研究新的绕过技巧(就像科学计数法绕过一样),规则总有滞后的可能。
玩转Sqli-labs靶场,特别是深入理解像科学计数法绕过这样的技巧,最终目的不是为了“黑”掉什么,而是为了在脑海中建立起一道坚固的防线。当你再看到自己写的SQL语句时,你会本能地思考:这里有没有拼接用户输入?我该怎么用参数化查询来重写它?这种从攻击者思维转化而来的防御意识,才是安全实战训练带给你的最宝贵的财富。