news 2026/10/21 17:09:38

SQL注入靶场实战:sqli-labs less-2数字型注入全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SQL注入靶场实战:sqli-labs less-2数字型注入全解析

如果你正在折腾SQL注入靶场,大概率会在sqli-labs的less-2上卡上一会儿。这一关从外面看跟less-1几乎一样,都是一个id参数控制内容回显,但真要拿着第一关的Payload直接打,十有八九会碰一鼻子灰。问题不在于思路不对,而在于你还没搞清楚less-2和less-1在SQL拼接方式上的细微区别。

这篇文章就把less-2的核心逻辑拆透:从探测注入点、判断数字型还是字符型,到order by猜列数、union select定位回显,最后把整张表拖出来。整套链路走完,你会对SQL注入里最基础的“拼接—闭合—注入”思维有一个非常扎实的理解,而不是只会照着网上Payload抄。

1. 先搞清楚:SQL注入到底是什么,less-2到底在练什么

1.1 注入的本质:把外部输入变成SQL逻辑的一部分

SQL注入听起来玄乎,本质其实就是一句话:程序把用户输入的内容,直接拼进了SQL查询语句里,导致输入被数据库当成SQL代码执行了。

我打个比方。正常业务里,用户在页面上输入一个id,程序做的事是:把id值取出来,塞进一条查询语句,然后去数据库查对应的记录。这就好比你去车站窗口买票,你告诉售票员你要去哪个城市,窗口就把你报的站名打到票上。问题是,如果售票员不校验你说的是什么内容,你报“北京,顺便把售票系统里的存款余额都打印出来”,系统可能真的把这整句话当成目的地去执行了。

在SQL里,这个过程就是变成了:

SELECT * FROM users WHERE id = 1;

这个1本来是用户提交的参数,但如果开发者图省事,直接做字符串拼接:

$sql = "SELECT * FROM users WHERE id = " . $id;

那用户传什么,数据库就看什么。传1,正常查;传1 or 1=1,变成查全表;传1 union select ...,变成了联合查询。这就是注入的底层逻辑——你以为你在传数据,其实你在改写程序原本的SQL语义。

1.2 less-2在sqli-labs里的定位:从字符型到数字型的过渡

sqli-labs是一个非常出名的开源SQL注入练习靶场,less编号从1到几十,每一关都对应一种注入场景。less-1是字符型注入,less-2是数字型注入。

这两关看起来页面长得一样,传入参数也都是?id=x,但SQL语句有一个关键区别:

  • less-1的SQL是字符串比较:WHERE id='$id',参数被单引号包住了。
  • less-2的SQL是数字比较:WHERE id=$id,参数直接裸露在SQL里。

一句话概括:less-1你需要在闭合掉单引号上面做文章,less-2压根没有引号可以闭合,你拿到的是一个“裸奔”的整型参数。

很多新手卡在less-2,就是因为在less-1里养成了一个习惯——先打一个单引号看报错,然后想办法用单引号和注释符把SQL语句凑回合法的样子。到了less-2,单引号一打,页面照样报错,但报错的内容完全不一样,于是就开始懵了,不知道下一步该干嘛。

这关真正的价值,是逼你从“背Payload”转向“理解SQL语句拼接后的真实面貌”。你只有看清了后端到底在拼什么,才能知道什么时候该闭合引号,什么时候不该闭合,什么时候单引号只是单纯引发了一个语法错误。

2. 数字型注入的关键判断:为什么单引号一打就报错

2.1 拿不到源码时,怎么判断后端SQL的拼接方式

正规渗透测试或者自己练靶场,大多数时候是没有源码可以看的。那么问题来了:你发了一个?id=1,页面正常回显;发了一个?id=1',页面报错。你怎么知道这个报错是因为单引号没有闭合,还是因为传入了一个非法字符?

关键就在报错回显的内容上。

针对less-2,如果你输入:

http://your-target/sqli-labs/less-2/?id=1'

页面上除了”You have an error in your SQL syntax“,通常还会带着一句:

... WHERE id=1' LIMIT 0,1

注意看这句话的末尾,id=1'后面直接跟着的就是单引号,然后才是LIMIT 0,1。这说明什么?说明SQL语句是WHERE id=1',而1'在MySQL里不是一个合法的数字,所以MySQL解析到单引号时直接报错,并且把用户输入原封不动地拼在了SQL语句里。

这就是数字型注入最典型的信号:单引号不是用来闭合什么字符串的,它纯粹就是来捣乱的。但恰恰是这个捣乱动作,暴露了后端拼接方式。

有对比才有伤害。你再看less-1的报错:

... WHERE id='1'' LIMIT 0,1

注意这里多了一个单引号,id='1'',说明原本SQL里就有一对引号包裹住用户输入,用户再传一个单引号进去,SQL变成'1'',前两个单引号被当成一对,后面那个单引号成了多余的闭合引号,于是语法报错。

这两个报错一对比,你就能非常清楚地看出less-2与less-1的差别。less-2的id参数没有任何包裹,这就是数字型注入的底层特征。

2.2 数字型注入为什么可以直接上逻辑运算

既然知道了less-2是数字型,那下一步就好办了。

数字型注入最大的好处,就是你不需要费心思处理引号闭合,直接往id后面拼接SQL逻辑就行。比如你想判断这个参数是否真的能被SQL解析器理解,传:

?/id=1 and 1=1

如果页面正常显示id为1的记录,说明and 1=1被数据库当成真值条件接受了。再传:

?id=1 and 1=2

如果页面什么都不显示,但没有报错,说明and 1=2被数据库当成假值条件,查询结果被过滤掉了。这就是逻辑判断注入点是否可用的标准手法。

你可能会问,为什么不直接用1'的报错来判断呢?因为有些应用会做简单的字符过滤,或者报错信息被全局关掉了。用and 1=1 / and 1=2这种对比法,是靠页面内容变化来判断SQL逻辑是否生效,适用性广得多。

而且这里还有一个细节值得注意:less-2你传?id=1 and 1=1,页面显示的是id=1那条记录,而不是id = (1 and 1=1)这种奇怪的结果。这又进一步验证了,id在SQL里是数值类型,1 and 1=1整体被MySQL计算成1,然后当成id的比较值。

到了这一步,你已经可以非常自信地判断:这是一个数字型注入点,不需要单引号闭合,可以开始走注入流程了。

2.3 URL编码、引号与注释符那些你不知道的细节

在实际动手注入时,有几个小细节特别容易坑人。

第一个是URL编码问题。你直接在地址栏里输入?id=1',浏览器一般会自动帮你转成%27传递,服务器拿到后再解码成单引号。大多数测试场景没问题,但如果遇到一些严格的服务端过滤,你最好手动把单引号写成%27,把空格写成%20,把注释符的加号写成%2B,避免中间环节被吃掉。

第二个是注释符的选择。SQL注入里常用--+、-- -或者#。在less-2里,因为不存在引号闭合,注释符的作用主要就是把SQL语句尾巴上原有的内容(比如LIMIT子句)干掉,让你拼接的union查询能顺利执行。很多新手没用对注释符,导致SQL语句后面多了一段多余代码,一直报错却不明白怎么回事。

第三个是空格和下划线。当你从浏览器地址栏提交请求时,URL里的空格会被编码成%20或者+,某些服务端接收参数时可能会把+误解析成空格。如果你发现注入语句明明逻辑没问题却怎么都不对,可以先检查一下URL里有没有+号被错误合并。

3. less-2 完整注入实操:从探测到拖库全流程

3.1 第一步:用order by确定返回列数

拿到了数字型注入点,下一步要搞清楚这张表的字段数量。为什么要知道字段数?因为后面用union select联合查询时,前后两个select语句的列数必须一致,否则MySQL直接报错,根本不会执行。

用order by猜列数是最经典的做法:

?id=1 order by 1 ?id=1 order by 2 ?id=1 order by 3 ?id=1 order by 4

原理很简单:order by 3表示按第3列排序,如果这张表只有2列,那按第3列排序时MySQL会报错“Unknown column '3' in 'order clause'”,说明没有第3列。反之,如果顺利显示,说明列数大于等于3。

在less-2上实测:

  • ?id=1 order by 3:页面正常。
  • ?id=1 order by 4:页面报错。

结果非常明确:这张表一共3列。

有人可能会问,为什么从1开始一个个试,不能直接猜一个大一点的数字?可以是可以,但一是报错信息很容易被日志记录,二是效率不如线性从1往上试探来得稳。靶场环境无所谓,真实测试时这种探测动作要尽量少而精准,最好是结合后续union select同时验证。

3.2 第二步:用union select确定回显点位

知道了有3列,接下来就可以用联合查询了。union的作用是把两条select的结果拼成一张表返回,但前提是两条语句的列数要一致,且列的数据类型要兼容。

我们先构造:

?id=0 union select 1,2,3

这里让id指向一个不存在的记录(比如id=0),这样一来前面那句select查不到数据,页面显示的内容就会完全来自后面的union查询,方便我们判断哪一列的数据会显示在页面上。

实测结果:页面上的某个位置显示了2,另一个位置显示了3,唯独没有显示1。这说明什么呢?说明后台SQL取出了第2列和第3列的数据,并渲染到了页面上,第1列可能被用于内部逻辑但没有直接输出。

这一步极其关键。如果你漏掉了它,直接傻乎乎地用union select username,password之类的组合,很可能因为字段位置没放对,导致数据出不来,或者页面显示的不是你想要的内容。

有了回显点位,后续查数据时,只需要把2和3的位置替换成我们想查的内容即可。

3.3 第三步:查数据库名与表名

接下来开始拖库。先确认当前用的是哪个库。把回显点位的其中一个替换成database()函数:

?id=0 union select 1,database(),3

页面上会直接打印出当前数据库名,这里是security。

有了库名,下一步就是查这个库里有哪些表。MySQL的系统库information_schema里有一张tables表,记录着所有数据库的表名和所属库。我们可以这么查:

?id=0 union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database()

为什么用group_concat?因为一个库里通常不止一张表,如果不做聚合,多条数据会撑爆页面上一行显示位,一长串表名互相挤在一起根本看不清。group_concat能把所有查出来的值用逗号拼在一行里,非常适合这个场景。

实测之后,你能看到users、emails、referers、uagents等一系列表。其中users表明显是存用户账号密码的,接下来重点就是它。

3.4 第四步:查字段名

拿到了表名,下一步查users表里有哪些字段。还是从information_schema取,这次换到columns表:

?id=0 union select 1,group_concat(column_name),3 from information_schema.columns where table_schema=database() and table_name='users'

这一步要注意两点:

第一,table_name='users'里的单引号不能省,因为表名字符串是字符类型,必须用引号包裹。这里单引号是我们主动写进Payload的,跟题目本身的闭合逻辑没关系,不要搞混。

第二,information_schema.columns这张表存了所有库所有表的字段信息,数据量非常大。写查询时一定要带上table_schema条件,否则条件过于宽泛也容易查询超时或返回过多数据,造成过滤困难。

实际反馈的字段名通常是id、username、password,这就是我们最终要拖的数据。

3.5 第五步:把账号密码拖出来

字段名齐了,直接select就完事:

?id=0 union select 1,group_concat(username),group_concat(password) from users

页面会把所有用户名跟密码拼接后显示出来。less-2的users表里一般存了十几组数据,用group_concat能看到密文形式存储的密码字段,有些是明文,有些是简单的加密,靶场里一般也就是练个语法,实际场景还要继续做解密和分析。

到这里整套手注流程就走完了:判断注入点、判断数字型、猜列数、定位回显点位、查库名、查表名、查字段名、查数据。这条链路看起来简单,但它是后面所有复杂注入的基础,盲注、堆叠注入、时间盲注,本质上都是在这条链路上加一些旁路手段而已。

4. 踩坑实录:报错、回显、注释的几类典型问题

4.1 单引号测了没反应,问题出在哪

很多人练第一个靶场时习惯用'去试探,结果到了数字型注入点,发现body报错消失、页面空白,以为没有注入点,直接放弃了。

其实这时候要冷静。你传?id=1',页面没有正常内容也没有明显报错,原因大概率有两个方向:要么是应用把'过滤或转义了,要么是全局关闭了错误回显。数字型注入不一定要靠单引号来识别,用?id=1 and 1=1和?id=1 and 1=2的页面差异来判断,是更稳的手法。记住这个思路顺序:先逻辑判断,再报错利用,不要把报错当成唯一信号。

4.2 union select两边的字段数不一致导致报错

这是我见过新手最容易犯的错。明明order by猜出来是3列,写union select时少打了一列,或者多打了一列,MySQL立刻报错说select的列数不匹配。

这里有一个非常典型的细节:当union select的列数少于原语句列数时,报错信息往往写的是“The used SELECT statements have a different number of columns”,当列数相等但数据类型不匹配时,报错可能是“Illegal mix of collations”。两种错误的含义完全不同,前者是列数问题,后者是字符集或类型问题,排查的方向也不一样。

4.3 注释符在URL里被浏览器转义

前面说过,less-2这个靶场的SQL语句尾部有LIMIT 0,1,你在union select后面如果不加注释符,后面残留的LIMIT 0,1会把union查询结果从第一条开始只显示一条,看起来就像没执行成功。

注释符最常见的写法是--+。在URL里直接传--+会有一个坑:+在URL编码里是空格的合法表示,服务端解码后可能变成空格,而MySQL里--注释符要求后面必须有空格才能生效。好在--+在解码后实际变成了--(两个短横线加空格),多数情况下能正常工作。如果不行,直接改用#注释符,但记得URL编码成%23再传,比如:

?id=1 order by 3%23

4.4 无回显时怎么办

less-2是典型的回显注入,每一步都能看到页面变化,练起来思路清晰。但如果你后面练到某些过滤了union或者不直接展示查询结果的靶场,这套流程就行不通了,那就要切换到盲注逻辑,通过页面真假来逐字符猜数据。less-2没到那一步,但你应该有这个意识:回显点位不是总有,能用union直接在页面上打印数据的注入点反而是比较友好的一种。

4.5 手注和工具怎么取舍

刚开始练手注时,我强烈建议铅笔手写,不要一上来就开sqlmap。sqlmap当然强大,但你如果连注入类型、字段数、回显点位都不理解,工具跑通了你也说不清它跑的是什么,遇到一个变种就废了。等手注五步走熟练了,再用工具做复杂场景的补充和验证,这样你的姿势一旦固定下来就会非常扎实。

5. 打得了靶场,更要守得住业务:防御侧的几个硬经验

5.1 参数化查询是根本解法

前面所有手注技巧能生效,依赖的是SQL拼接。那防护上最根本的一条,就是不要拼接SQL。

参数化查询(Prepared Statement)的思路是:把SQL语句的结构(模板)和参数分开传给数据库,数据库先解析SQL结构,再把你传的参数当成纯数据值填入。比如:

$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?'); $stmt->execute([$id]);

这时你再传1 union select ...,数据库只知道?位置是一个数值类型的数据,不会把它解析成SQL关键字,数据就永远是数据。这是目前公认的最有效的注入防御手段,没有之一。

5.2 输入校验与类型强制双保险

参数化查询之外,还要习惯性地做输入校验。对于数字型参数,直接强制整型转换:

$id = (int)$_GET['id'];

这样一个id=1 order by 3在进入SQL之前就变成了id=1,后面所有字符串内容全部被丢掉,注入自然无从谈起。对于字符串参数,则要严格控制允许的字符集合,并绑定参数化查询,而不是依赖黑名单过滤。黑名单过滤思路很容易被各种奇奇怪怪的编码绕过去,像大小写混淆、注释符穿插、十六进制编码、空白字符替换等,都是经典绕过手段,只靠正则匹配keyword迟早被人穿。

5.3 最小化权限与错误信息收敛

生产环境应有专门的数据库账号,绝不使用root等级权限。账号只开通必要的库表权限,并且关闭堆叠查询等可能被利用的高级特性。这样即使某条SQL被注入成功,攻击者能操作的范围也非常有限。

同时,一定要关掉页面上的SQL报错回显。less-2靶场里你看到的... WHERE id=1' LIMIT 0,1这种错误输出,在生产环境里等于直接把底裤翻开送人。正确的姿势是记录到后端日志里,页面只显示通用错误页。很多人觉得报错无所谓,实际它暴露的是SQL方言、表名、字段结构等一系列信息,给攻击者省了太多事。

5.4 这些手艺,应该用在自测和防御上

我个人的看法是:靶场练手注,真正的目标不是去搞破坏,而是建立对SQL拼接问题的敏感性。你亲手走一遍从探测到拖库的流程,才会设身处地体会到参数校验有多重要,才会在code review里一眼识别出哪些查询在裸拼字符串。

我也见过一些测试工程师,把工具上的高危漏洞截图丢给开发后,开发回一句“我在本地测试是好的”就完事了。漏洞毕竟不是开发造出来的,问题是整个系统链路缺少校验。如果手里没有SQL注入的深刻理解,很难高质量地说服开发按照参数化查询的标准来修改。所以别嫌这关入门,所有深入的安全认知都从这里起步。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 17:09:38

两足机器人ZMP预览控制行走模式生成Matlab实现解析

简介:双足机器人的ZMP预览控制行走模式生成在步行稳定性研究中十分关键,这套开源实现恰好提供了可直接学习的完整代码。资源面向自动控制、机器人工程等方向的学生与研究者,帮助理解如何通过预览控制计算增益矩阵,并用前馈控制生成…

作者头像 李华
网站建设 2026/10/21 17:09:24

MySQL隐式转换导致索引失效?原理、排查与修复实战指南

1. 隐式转换到底是什么:MySQL内部的类型“翻译官”先聊一个我在实际排障中反复遇到的场景:一张几百万行的用户表,手机号字段建了普通索引,查询长这样。SELECT * FROM user WHERE phone 13800000000;在数据量小的时候,…

作者头像 李华
网站建设 2026/10/21 17:08:36

建筑3D打印的产业化瓶颈:技术之外,更缺产业链协同

1. 一天的展台转下来,建筑3D打印的进度条停在哪如果不是专门冲着建筑增材制造去的,很多人逛这种大型增材制造展会时,都会下意识忽略角落里那些喷头比人还高的大家伙。今年这届亚洲展我倒是有意多留了半天,专门把自己钉在建筑3D打印…

作者头像 李华
网站建设 2026/10/21 17:08:35

Java求职Day51:并发编程深水区与面试复盘指南

今天是我java求职学习day51的第51天。说实话,能坚持到这个数字的人并不多。身边同期备考的朋友,有的在第二周就转投考研,有的在第四周陷入"越学越焦虑"的循环,真正按计划推进到这里的,一只手数得过来。Day51…

作者头像 李华
网站建设 2026/10/21 17:08:27

长时对话理解评测基准:大模型长期记忆能力测试与实操指南

1. 长时对话理解,为什么成了大模型评测的“硬骨头”1.1 从“单轮问答”到“长期记忆”的评测鸿沟大模型评测这件事,做过的人都知道,早期基本就是刷榜逻辑:给一道题,模型答对就得分,答错就扣分。MMLU、GSM8K…

作者头像 李华
网站建设 2026/10/21 17:06:08

时序逻辑电路设计:从触发器、计数器到Verilog实战

简介:这份《数字电路与逻辑设计》第四章课件聚焦时序逻辑电路,面向计算机、电子信息类学生及相关自学者,系统讲解触发器结构、同步/异步时序电路的分析与设计方法。课件从组合电路与时序电路的区别切入,逐步展开基本RS、同步、主从…

作者头像 李华