一、Sql注入思路
1、判断注入点
在GET
参数、POST
参数、以及HTTP
头部等,包括Cookie
、Referer
、XFF(X-Forwarded-for)
、UA
等地方尝试插入代码、符号或语句,尝试是否存在数据库参数读取行为,以及能否对其参数产生影响,如产生影响则说明存在注入点。
1)、GET 注入
提交数据的方式是 GET,注入点的位置在 GET 参数部分。例如有这样的一个URL:http://xxx.com/news.php?id=1
,id是注入点。
2)、POST 注入
使用 POST 方式提交数据,注入点位置在 POST 数据部分,通常发生在表单中。
3)、HTTP 头部注入
注入点在 HTTP 请求头部的某个字段中。比如存在 User-Agent 字段中,Cookie 字段中等。
2、判断数据库类型
判断网站使用的是哪个数据库,常见数据库如:MySQL、MSSQL(SQLserver)、Oracle、Access、PostgreSQL、BD2
等等。
1)、端口扫描
如果可以对主机进行端口扫描,可以根据是否开启对应端口,来大概判断数据库类型。
数据库类型 | 默认端口号 |
---|---|
Oracle | 1521 |
SQL Server | 1433 |
MySQL | 3306 |
PostgreSql | 5432 |
2)、网站类型与数据库的关系
网站类型 | 数据库类型 |
---|---|
asp | SQL Server,Access |
.net | SQL Server |
php | Mysql,PostgreSql |
java | Oracle,Mysql |
3)、根据数据库特有的系统表判断
数据库类型 | 特有的系统表 |
---|---|
Oracle | SYS.USER_TABLES |
SQL Server | SYSOBJECTS |
MySQL(MySQL版本在5.0以上) | INFORMATION_SCHEMA.TABLES |
Access | MSYSOBJECTS |
http://127.0.0.1/test.php?id=1
例如,我们可以根据以上URL,拼接Sql语句来查询数据库特有的系统表数据条目,根据其是否返回查询结果来判断数据库类型:
http://127.0.0.1/test.php?id=1 and (select count(*) from sysobjects)>0 and 1=1
4)、根据返回的错误判断
如果我们在进行Sql注入的时候,可以看到数据库的错误信息,也可以通过返回的错误信息来判断数据库的类型
3、判断参数数据类型(闭合方式)
通过+1
、-1
、and 1=1
、and 1=2
、注释符等与其各种变种,如与各种符号结合的and 1=1
、and '1'='1
等等判断参数数据类型。先判断是否是整型,如果不是整型则为字符型,字符型存在多种情况,需要使用单引号'
、双引号"
、括号()
多种组合方式进行试探。
理论上来说,只有数值型和字符型两种注入类型。SQL的语法,支持使用一个或多个括号包裹参数,使得这两个基础的注入类型存在一些变种,例如:数值型+括号、单引号字符串+括号、双引号字符串+括号等,有时可能会有多个括号包裹。
单引号
'
注入不成功的时候尝试1%df'
是否为宽字节注入
4、判断数据库语句过滤情况,从而选择相应的注入方式(联合查询、报错、盲注…)
正常输入Sql语句,通过查看回显来判断语句是否被过滤。如果order by被过滤则尝试绕过,如果无法绕过就无法得到列数,这时就无法使用联合查询注;如果页面没有显示位,同样无法使用联合查询注入;如果没有报错信息返回,则无法使用报错注入。
1)、联合查询注入
联合查询的前提是需要通过order by
获取到列数,且页面上有显示位。判断显示位、获取所有数据库名、获取指定数据库所有表名、获取指定数据库指定表中所有字段名、获取具体数据。
2)、报错注入
报错注入的前提是页面会显示数据库报错信息。得到报错信息、获取所有数据库名、获取指定数据库所有表名、获取指定数据库指定表中所有字段名、获取具体数据。
3)、布尔盲注
Sql注入语句执行之后,可能由于网站代码的限制或者Apache等解析器配置了不回显数据,造成数据不能回显到前端页面。此时,我们需要利用一些方法进行判断或者尝试。当页面返回结果只有正确和错误这两种情况时,可以使用布尔盲注。
4)、时间盲注
当页面上没有显示位,也没有输出SQL语句执行错误信息。 正确执行和错误执行的返回界面一样,但是加入sleep
语句之后,页面的响应速度会明显变慢。此时需要使用时间盲注。因此实际情况下手工时间盲注会花费大量时间,不符合实际,需要用工具或者脚本来进行注入。
5)、DNSlog带外注入
当我们进行注入时,如果页面无回显,且无法进行时间注入,那么就可以利用DNS解析这个通道,把查询到数据通过通道带出去,可以使用DNSlog带外注入。
二、绕过方式
后端语言可能对我们拼接的sql语句,进行过滤,常见的有关键字过滤,一些符号过滤以及函数过滤等。
1、过滤关键字
-
分割关键字
最常用的绕过方法就是使用/**/
,<>
分割关键字sel<>ect sel/**/ect
-
双写绕过
根据过滤程度,有时候还可以用双写绕过 -
编码绕过
有时候可以尝试使用URL编码绕过、16进制编码绕过、ASCII编码绕过 -
大小写绕过
2、过滤逗号
- 简单注入可以使用
join
绕过# 原语句: union select 1,2,3 # join语句: union select * from (select 1)a join (select 2)b join (select 3)c
- 对于盲注的几个函数
substr()、mid()、limit
# substr和mid()可以使用from for的方法解决 # 原语句 substr(str, pos, len) # 截取字符串,从pos位置开始,截取字符串str的len长度 mid(str, pos, len) # 截取字符串,从pos位置开始(从1开始),截取字符串str的len位 # from for语句 substr(str from pos for len) # 在str中从第pos位截取len长的字符 mid(str from pos for len) # 在str中从第pos位截取len长的字符 # limit可以用offset的方法绕过 limit 1 offset 1 # "limit 1"表示只返回1行结果,"offset 1"表示从结果集中跳过1行数据,所以表示从结果集中选择第2行,并返回该行作为结果
3、过滤空格
- 使用注释
/**/
绕过
如果在sqlmap中,使用注释绕过,对于mysql数据库需要使用# 例如sql查询 select user() from security # 我们用注释替换空格,就可以变成: select/**/user()/**/from/**/security
--tamper
参数指定注释绕过空格的脚本:python sqlmap.py -u http://127.0.0.1/index.php?id=1 --batch --dbs --tamper=space2comment.py
- 使用括号绕过
# 例如sql查询 select user() from user where 1=1 and 2=2 # 如何把空格减到最少? # 观察到user()可以算值,那么user()两边要加括号,1=1和2=2可以算值,也加括号,去空格,变成: select(user())from user where(1=1)and(2=2) # 数据库名两边的空格,通常是由程序员自己添加,我们一般无法控制。所以上面就是空格最少的结果。 # 在time based盲注中是一个非常实用的技巧 http://www.xxx.com/index.php?id=(sleep(ascii(mid(user()from(2)for(1)))=109))
4、过滤等号
当注入语句中把=
过滤了,可以使用使用like、rlike、regexp
进行绕过
like
:不加通配符的like
执行的效果和=
一致,所以可以用来绕过。
rlike
:模糊匹配,只要字段的值中存在要查找的 部分 就会被选择出来,用来取代=时,rlike的用法和上面的like一样,没有通配符效果和=一样
regexp
:MySQL中使用REGEXP
操作符来进行正则表达式匹配
也可以用<>
来绕过
5、过滤引号
-
十六进制编码绕过
一般会使用到引号的地方是在最后的 where 子句中,比如:select * from t_student where name = "admin";
当引号被过滤了的话,
'admin'
或者"admin"
就没法用了,我们可以用admin
的16进制0x61646d696e
代替。 -
ASCII编码绕过
-
URL编码绕过
使用URL编码绕过的前提条件是后端在处理接收到的参数进行了URL解码,并且对该URL解码是在使用了过滤函数之后才可以使用URL编码绕过。
6、过滤大于小于号
在使用盲注的时候,会用到二分法来比较操作符来进行操作,如果过滤了比较操作符,那么就需要使用到greatest()
和lease()
来进行绕过。greatest()
函数返回最大值,leaset()
函数返回最小值。
# 原语句
select * from users where id=1 and ascii(substring(database(),0,1))>64;
# 过滤<、>符号后,使用greatest()后的语句
select * from users where id=1 and greatest(ascii(substring(database(),0,1)),64)=64;
7、等价函数绕过
-
当
hex()、bin()
函数被过滤,可以使用ascii()
函数 -
当
sleep()
函数被过滤,可以使用benchmark()
函数,函数benchmark(count, expression)
,参数count
表示要执行表达式的次数,expression
是要执行的表达式或语句,返回表达式的执行时间。 -
当
substring()、substr()
和mid()
函数被过滤时,可以使用strcmp()
函数strcmp(str1, str2)
函数,用于比较两个字符串是否相等。如果 str1 等于 str2,则返回 0。 如果 str1 小于 str2,则返回一个负数。如果 str1 大于 str2,则返回一个正数。strcmp(left('password',1), 0x69) = 1 strcmp(left('password',1), 0x70) = 0 strcmp(left('password',1), 0x71) = -1
-
CONCAT_WS()、CONCAT()
和GROUP_CONCAT()
是用于字符串连接和聚合的函数。CONCAT_WS(separator, str1, str2, ...)
函数用于将多个字符串连接在一起,并使用指定的分隔符进行分隔。CONCAT(str1, str2, ...)
函数用于将多个字符串连接在一起。与CONCAT_WS()
不同,CONCAT()
函数不会插入分隔符。
三、Sql注入的防御方式
1、限制数据类型
Java、C#等强类型语言几乎可以完全忽略数字型注入,例如:请求ID为1的新闻页面,其URL:http://www.secbug.org/news.jsp?id=1
,在程序代码中可能为:
int id = Integer.parseInt(request.getParameter("id"));
//接收 ID 参数,并转换为 int 类型
News news= newsDao.findNewsById(id); //查询新闻列表
攻击者想在此代码中注入是不可能的,因为程序在接收ID参数后,做了一次数据类型转换,如果ID参数接收的数据是字符串,那么在转换时将会发生Exception
。因此,数据类型处理正确后,可以防止数字型注入。像PHP、ASP,并没有强制要求处理数据类型,这类语言会根据参数自动推导出数据类型,假设 ID=1,则推导ID的数据类型为Integer、ID=str,则推导 ID 的数据类型为string,这一特点在弱类型语言中是相当不安全的。如:
$id = $_GET['id'];
$sql ="select*from news where id = $id;";
$news = exec($sql);
攻击者可能把id参数变为 1 and 1=2 union select username,password from users;--
,这里并没有对$id
变量转换数据类型,PHP自动把变量$id
推导为 string类型,带入数据库查询,造成 SQL注入漏洞。防御数字型注入相对来说是比较简单的,只需要在程序中严格判断数据类型即可。如:使用is_numeric()、ctype_digit()
等函数判断数据类型,即可解决数字型注入。
2、特殊字符转义
通过加强数据类型验证可以解决数字型的 SQL 注入,字符型却不可以,因为它们都是 string类型,你无法判断输入是否是恶意攻击。那么最好的办法就是对特殊字符进行转义。因为在数据库查询字符串时,任何字符串都必须加上单引号。既然知道攻击者在字符型注入中必然会出现单引号等特殊字符,那么将这些特殊字符转义即可防御字符型SQL注入。例如,在PHP中最基本的就是自带的magic_quotes_gpc
函数
3、使用预编译语句,绑定变量
使用预编译相当于是将数据于代码分离的方式,把传入的参数绑定为一个变量,用?
表示,攻击者无法改变 Sql 的结构。
String query="select password from users where username='?' ";
在这个例子中,即使攻击者插入类似 admin' or 1=1#
的字符串,如果不做处理直接带入查询,那么query则变成了
query="select password from users where username='admin' or 1=1 ";
闭合了后面的引号,从而执行了恶意代码。而预编译则是将传入的 admin' or 1=1#
当做纯字符串的形式作为 username 执行,避免了上面说到的 Sql 语句中的拼接闭合查询语句等过程,可以理解为字符串与Sql语句的关系区分开。username 此时作为字符串不会被当做之前的SQL语句被带入数据库执行,避免了类似sql语句拼接、闭合等非法操作。
4、框架技术
随着技术发展,越来越多的框架渐渐出现,Java、C#、PHP等语言都有自己的框架。至今,这些框架技术越来越成熟、强大,而且也具有较高的安全性。
在众多的框架中,有一类框架专门与数据库打交道,被称为持久层框架,比较有代表性的有Hibernate、MyBatis、JORM
等。