前言
开始 SQL 注入 。推荐使用 sqli-labs 靶场,因为它涵盖了丰富的 SQL 注入相关内容。靶场的具体搭建方法可以自行百度查找。
实操流程
- 本次选择的是 sqli-labs 的 less 2 数字型注入作为起点。

- 进入靶场后,首先看到如下网页界面:

- 可以通过插件了解该网站的开发语言:

- 该网站是用 PHP 编写的,因此后续解题需要结合 PHP 代码进行分析。

- 通过 F12 开发者工具可以发现这是一个 GET 请求 页面。下面介绍一下 GET 和 POST 请求的区别。
一、基础概念:浏览器向服务器提交数据的两种方式
1. GET 提交
- 提交位置:数据直接嵌套在 URL(网址)中,比如 xxx.com?id=1,参数会直接显示在地址栏。
- 核心特点 :
- 数据长度有限制:约 2KB(2048 字节),无法提交大量数据。
- 安全性低:参数明文可见,敏感信息(如账号、密码)会直接暴露在地址栏。
- 速度快:请求处理效率高于 POST。
- 适用场景:适用于数据不敏感、无高安全要求、数据量小的场景(如商品列表查询、文章ID查询)。
2. POST 提交
- 提交位置:数据放在 HTTP 请求体中,地址栏不会显示参数。
- 核心特点 :
- 安全性更高:参数不直接暴露在地址栏,普通用户无法直接看到提交的数据。
- 数据量大:没有 GET 的长度限制,可以提交大量数据(如表单、文件上传)。
- 适用场景:绝大多数网站/APP的主流提交方式,适用于登录、注册、表单提交、文件上传等涉及敏感数据或大数据量的场景。
OK,那我们回到正题,我这边要填写什么猜能过关呢,我先直接写出来:
bash
http://sqli-labs-1202/Less-2/index.php?id=1

为什么可以这样写呢?我们去找一下源码看一下。
不需要每行代码都看得十分明白,只需要知道它是怎么用的就行。

可以看出来,我们的 id=1 被这个 GET 指令接收了,然后这个 GET 指令再向下执行到 SELECT 引导的搜索函数。当然,id 不一样的话,数据也是会发生变化的,这边不做过多演示。
三、手工注入完整流程(数字型 + 回显注入)
步骤 1:判断是否存在注入点
核心逻辑:看后端是否将用户输入的参数直接拼接到 SQL 语句中执行,未做过滤则存在注入点。
- 测试方法:在 URL 参数后拼接逻辑判断语句
- 正常请求:id=1 → 页面显示对应数据

- 测试语句 1:id=1 and 1=1 → 页面正常(条件成立,SQL 执行成功)

这边说一下,这个%20 其实就是空格的意思 - 测试语句 2:id=1 and 1=2 → 页面异常(条件不成立,SQL 执行失败)

- 简单粗暴法:直接输入 id=abc 等非数字内容,页面报错则说明存在注入点

- 原理:后端 SQL 语句为 select * from users where id = 输入的参数,拼接后会执行完整逻辑
步骤 2:用 order by 判断字段数量
核心逻辑:order by 是排序语句,后跟数字表示按第 N 个字段排序,若数字超过实际字段数则报错,以此推断字段总数。
- 测试流程:
- 输入 id=1 order by 1 → 正常(存在第 1 个字段)
- 输入 id=1 order by 2 → 正常(存在第 2 个字段)
- 输入 id=1 order by 3 → 正常(存在第 3 个字段)
- 输入 id=1 order by 4 → 报错(无第 4 个字段)
- 结论 :该表共有 3 个字段
- 补充:URL 中空格可用 %20 代替,避免被过滤
步骤 3:用 union select 判断回显点
核心逻辑 :union select 用于联合查询,要求前后两条查询语句的字段数一致;构造第一条查询无结果,第二条查询的内容就会显示在页面上,显示的位置就是 回显点。
- 测试方法:
- 构造无结果的第一条查询:id=-1(一般无负 ID 数据)
- 拼接联合查询:id=-1 union select 1,2,3
- 页面显示 2 和 3 → 说明第 2、3 个字段是回显点(后续可在这两个位置查询数据)

- 关键特性:union select 前后字段数必须一致;第一条查询无结果时,才会显示第二条查询的内容
步骤 4:信息收集(数据库版本、库名等)
核心逻辑:利用 MySQL 内置函数,在回显点位置查询数据库基础信息,为后续注入铺路。
-
常用函数 :
- version():查询数据库版本(如 5.7.26),判断高低版本(5.0 以上为高版本,有系统库 information_schema)
- database():查询当前数据库名(如 security)
-
测试语句 :
bashid=-1 union select 1,version(),database()- 页面回显:数据库版本 + 当前库名,确认是高版本后,可借助系统库查询表名、字段名

步骤 5:查询数据库中的表名
核心逻辑:高版本 MySQL 的 information_schema.tables 表存储了所有表的信息,通过条件筛选当前库的表名。
-
核心语句 :
bashid=-1 union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database()- 关键说明:
- group_concat():将多个表名合并成一行显示,避免页面只显示单个结果
- table_schema=database():筛选出当前数据库下的所有表
- 页面回显:当前库的所有表名(如 users、emails 等),其中 users 表大概率存储账号密码

步骤 6:查询目标表的字段名
核心逻辑:information_schema.columns 表存储了所有字段的信息,筛选目标表的字段名。
-
核心语句 :
bashid=-1 union select 1,group_concat(column_name),3 from information_schema.columns where table_schema=database() and table_name='users'- 补充:若直接写表名报错,可将 users 转为 16 进制(如 0x7573657273),避免引号被过滤
- 页面回显:users 表的字段名(如 id、username、password)

步骤 7:查询目标字段的具体数据
核心逻辑:直接查询 users 表中 username 和 password 字段的内容,拿到账号密码。
-
核心语句 :
bashid=-1 union select 1,group_concat(username),group_concat(password) from users
- 页面回显:数据库版本 + 当前库名,确认是高版本后,可借助系统库查询表名、字段名