Sql注入(手工注入思路、绕过、防御)

一、Sql注入思路

1、判断注入点

GET参数、POST参数、以及HTTP头部等,包括CookieRefererXFF(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-1and 1=1and 1=2、注释符等与其各种变种,如与各种符号结合的and 1=1and '1'='1等等判断参数数据类型。先判断是否是整型,如果不是整型则为字符型,字符型存在多种情况,需要使用单引号'、双引号"、括号()多种组合方式进行试探。
理论上来说,只有数值型和字符型两种注入类型。SQL的语法,支持使用一个或多个括号包裹参数,使得这两个基础的注入类型存在一些变种,例如:数值型+括号、单引号字符串+括号、双引号字符串+括号等,有时可能会有多个括号包裹。

单引号'注入不成功的时候尝试1%df' 是否为宽字节注入

Sql注入(手工注入思路、绕过、防御)_第1张图片

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、过滤空格

  • 使用注释/**/绕过
    # 例如sql查询
    select user() from security
    # 我们用注释替换空格,就可以变成:
    select/**/user()/**/from/**/security
    
    如果在sqlmap中,使用注释绕过,对于mysql数据库需要使用--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))
    
    Sql注入(手工注入思路、绕过、防御)_第2张图片

4、过滤等号

当注入语句中把=过滤了,可以使用使用like、rlike、regexp进行绕过

like:不加通配符的like执行的效果和=一致,所以可以用来绕过。
rlike:模糊匹配,只要字段的值中存在要查找的 部分 就会被选择出来,用来取代=时,rlike的用法和上面的like一样,没有通配符效果和=一样
regexp:MySQL中使用REGEXP操作符来进行正则表达式匹配

Sql注入(手工注入思路、绕过、防御)_第3张图片

也可以用<>来绕过

Sql注入(手工注入思路、绕过、防御)_第4张图片

5、过滤引号

  • 十六进制编码绕过
    一般会使用到引号的地方是在最后的 where 子句中,比如:

    select * from t_student where name = "admin";
    

    当引号被过滤了的话, 'admin' 或者 "admin" 就没法用了,我们可以用 admin 的16进制 0x61646d696e 代替。

    Sql注入(手工注入思路、绕过、防御)_第5张图片

  • ASCII编码绕过
    Sql注入(手工注入思路、绕过、防御)_第6张图片

  • 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等。

你可能感兴趣的:(Web漏洞原理,数据库安全,sql注入防御,sql注入绕过)