小心你的WEB应用程序成为数据窃贼的帮凶

安全
当心,你以为固若金汤的数据库可能已遭到了入侵。你需要重新思考一下自己公司的网站是否真得不会遭到SQL注入攻击。本文将阐述攻击者如何通过这种方法来利用Web应用程序的漏洞。有时,即使攻击者也不了解自己正在利用的漏洞的性质。

当心,你以为固若金汤的数据库可能已遭到了入侵。你需要重新思考一下自己公司的网站是否真得不会遭到SQL注入攻击。SQL注入是最流行也是最危险的Web应用程序漏洞利用技术,它可以攻击存储着珍贵企业信息的后端数据库,且“简约高效”。

本文将阐述攻击者如何通过这种方法来利用Web应用程序的漏洞。有时,即使攻击者也不了解自己正在利用的漏洞的性质。

何为SQL注入

就其最基本的意义来说,SQL注入只不过是操纵一个已有的SQL查询,执行一个并非开发人员意图的动作。这种动作通常是通过Web应用程序的用户界面完成的。

但这种攻击是如何进行的?它为什么屡屡得逞?

Web应用程序和数据库之间的正常交互

所有的SQL注入漏洞都是由某些未经验证的用户输入开始的。用户输入可以采取多种形式,它可以包括一个攻击者操纵的由服务器处理的任何东西,例如:用户代理、HTTP报头、POST参数、cookies、GET参数,甚至网址标头等。是什么令未经验证的用户输入如此特殊呢?答案是:应用程序并没有对其进行充分的检查,从而不能确保所收到的输入就是所期望的类型和方式。例如,虽然你的应用程序的编制目的是为了接收可以包括字母、数字的字符串作为用户名,但此程序并没有验证输入,从而使得黑客可以插入SQL注入的数据库查询:

比如,一个典型的网站会要求你的用户名,并希望这个结果是“Zhangsan”:

Wangzhan.com/usertetail.asp?username=Zhangsan

在这个例子中,有可能会发生这样的情况:某个查询促使网站在后台与数据库交互,从而获取关于用户(如,Zhangsan)的信息:

SELECT uname,fname,lname,phone,street,city,state,zip FROM users WHERE user =$var_username

当Web应用程序代码处理这个请求时,为了完成查询,来自用户名的值(Zhangsan)被传递给$var_username。服务器应当将SQL查询的结果变成标准格式,并显示此结果以便于用户查看Zhangsan的细节。#p#

攻击者寻找突破口

首先,攻击者可能查看应用程序是否能够正确地处理错误条件。有许多方法可以检查SQL错误消息,每一个方法都依赖于数据库自身。最常见的例子是“‘”( 撇号)。攻击者可能会尝试插入“‘”而不是一个合法的用户名:

Wangzhan.com/userdetail.asp?username=‘

如果出现错误,攻击者就可以了解一些信息。例如,下面的错误就会使攻击者知道这是一个MySQL数据库,而且表明数据库将“‘”解释为查询的一部分,从而揭示出这可能是一个SQL注入点,值得进一步调查。

错误:您的SQL语法有一个错误,请检查您的MySQL服务器版本对应的手册,查看正确的语法…在第4行

在此例中,我们使用了一个“‘”,但任何“保留”字符,即在测试数据库错误时可以使用的为特别目的而保留的一个字符。保留字符对每种数据库类型来说都是独一无二的。

借助上面显示的MySQL错误消息,我们可以看出黑客是多么聪明,而且能够发现应用程序正在访问的数据库表的其它细节。请看:

Wangzhan.com/userdetail.asp?username=Zhangsan order by 1

如果我们没有收到错误,就可以知道用户名要么是SQL WHERE语句中的最后一个变量(允许我们从一个数据库表中重新获取数据,同时又排除其它的无关数据),或者是WHERE语句中的唯一变量。我们可以让数字每次增加1,直至收到一个错误。例如,可能在到达“Zhangsan order by 9”,就可以看到:

错误:用户警告:“order clause”查询中有无法确认的列:SELECT

现在可以确认,直至提交了“9”,我们才收到了错误消息,所以可以断定表中有8列。这个信息很有用,但我们只是想获得尽量多的数据。假设没有提供输入验证,通过在用户名的位置使用一个通配符,我们实际上可以返回所有用户的细节:

Wangzhan.com/userdetail.asp?username=%

在该例中,我们将执行下面的查询,返回所有用户的细节:

SELECT uname,fname,lname,phone,street,city,state,zip FROM users WHERE user = %

如果攻击使用此伎俩,势必会造成数据损害,使大量的有价值的客户信息处于风险之中。其中可能包括应当被加密的用户口令,当然攻击者可以在日后再进行破解。遭到泄露的客户信息还有可能包括电子邮件地址,攻击者可以将其用于钓鱼攻击。

其实,我们可以不用插入简单的通配符,而是终止查询,并让查询做一些查询之外的事情:

Wangzhan.com/userdetail.asp?username=zhangsan;DROP users—

为便于比较,我们将SQL Server的数据库语句列示如下:

SELECT uname,fname,lname,phone,street,city,state,zip FROM users WHERE user = ‘zhangsan’;DROP users—

还要注意,此例允许你在同一行上提交多个查询(在此例中,即SELECT和DROP查询)。其方法就是用分号(;)分开并用两个破折号结束。因而,在完成最初的查询后,攻击者就可以发送并运行自己选择的一个完整查询。#p#

自动攻击

攻击者可以使用两种主要的攻击方法来利用SQL注入漏洞:自动攻击和手动攻击。从字面上看似乎很容易理解,但这两种攻击在机制上是非常不同的。自动攻击一般是为特定目的而编制的一种工具所导致的结果。例如,有的攻击使用大规模的注入攻击,其目的是为了让其攻击范围最大化并任意扩散其代码。这种大规模的攻击一般针对非常具体的应用程序架构,如运行ASP的IIS服务器等。而Asprox攻击,其目的是为了将一个JavaScript或iframe标记注入到网页中,从而传播病毒。

自动SQL注入举例

下面是一个高级SQL注入攻击的例子,它能够注入到某些字段类型中。很长一段时间以来,此类代码可以有效的帮助攻击者传播病毒。

wangzhan.com/ssp.asp?username=zhangsan’;DECLARE @T VARCHAR  (255),@C VARCHAR(255) DECLARE TABLE_CURSOR CURSOR FOR SELECT   A.NAME,B.NAME FROM SYSOBJECTS A,SYSCOLUMNS B WHERE A.ID=B.ID AND   A.XTYPE=‘U’ AND (B.XTYPE=99 OR

B.XTYPE=35 OR B.XTYPE=231 OR B.XTYPE=167) OPEN TABLE_CURSOR FETCH   NEXT

FROM TABLE_CURSOR INTO @T,@C WHILE(@@FETCH_STATUS=0) BEGIN

EXEC(‘UPDATE [‘+@T+’] SET

[‘+@C+’]=RTRIM(CONVERT(VARCHAR(4000),[‘+@C+’]))+’<script   code>‘) FETCH NEXT FROM TABLE_CURSOR INTO @T,@C END CLOSE   TABLE_CURSOR DEALLOCATE TABLE_CURSOR;--

下面,我们简单地看一下这段代码是如何针对后端数据库实施攻击的。首先,攻击者声明了Table (T) 和 Column (C)这两个变量。

DECLARE @T VARCHAR(255),@C VARCHAR(255)

并声明了一个可以保存查询结果的表cursor:

DECLARE TABLE_CURSOR CURSOR FOR

下面的SELECT语句通过“text”、“sysname”、“varchar”等列来检索所有的用户对象,并且将结果存储在前面创建的CURSOR中。

SELECT A.NAME,B.NAME FROM SYSOBJECTS A,SYSCOLUMNS B WHERE A.ID=B.ID AND A.XTYPE=‘U’ AND (B.XTYPE=99 OR B.XTYPE=35 OR B.XTYPE=231 OR B.XTYPE=167)

在下面的代码中,数据表CURSOR检索结果,并将其分配给表和列变量:

OPEN TABLE_CURSOR FETCH NEXT FROM TABLE_CURSOR INTO @T,@C WHILE(@@FETCH_STATUS=0)

此时,攻击者已经检索了数据库中基于文本的这些列,其意图是为了修改这些列的内容。在这里,攻击者虽然没有篡改数据,但已经完成了所有必要的侦察。然后,执行更新语句,将JavaScript置于列变量中的每一列中。攻击完成后,包含Web内容(这些内容源自数据库的任何字段)的任何网页都会提交攻击者的恶意JavaScript代码。然后,该JavaScript用一个病毒感染Web用户的计算机:

WHILE(@@FETCH_STATUS=0) BEGIN EXEC(‘UPDATE [‘+@T+’] SET [‘+@C+’]=RTRIM(CONVERT(VARCHAR(4000),[‘+@C+’]))+’<script code>‘) FETCH NEXT FROM TABLE_CURSOR INTO @T,@C END

在注入的结尾,攻击者执行清理,覆盖所有的攻击痕迹:

CLOSE TABLE_CURSOR DEALLOCATE TABLE_CURSOR;--

这些攻击是完全自动化的;黑客攻击只需使用搜索引擎简单地搜索互联网,查找运行经典的ASP代码的Web服务器即可。别再自欺欺人的相信“我的网站很小,谁会愿意攻击它呢?”这种愚蠢的谎言。如果你正在通过互联网做商务,不管企业大小,都易于遭到攻击。#p#

接管

在很多情况下,攻击者能够完全控制SQL服务器的底层操作系统;攻击者甚至可以接管Web应用程序,并最终接管Web服务器。

接管数据库服务器可以导致损害其它的应用程序,甚至损害DMZ中的其它服务器。现代的Web应用程序架构一般都有数据库集群,与其它系统共享数据存储,或者位于网络中不太安全的地方。如果攻击者考虑到这个方面,他就可以使用前面提到的方法绕过防火墙中一般的IP源过滤规则,从而攻击内部网络,甚至还可以使用SQL服务器来存储病毒、黄色图片或其它非法内容。

此外,通过首先接管数据库服务器,攻击者可以篡改Web应用程序的行为。通常,这种行为包括借助Web服务器的服务账户在本地服务器上运行命令。如果服务账户有了被提升的特权,攻击者就可以通过数据库服务器,将命令直接发送给Web服务器的操作系统。

有时,数据是根据计划例程从DMZ数据库服务器析取出来的。如果通过公司Intranet(内联网)中的Web接口来查看数据,情况就更危险,因为多数内联网的Web应用程序在运行时,其信任等级更高。

不要小看盲目攻击

通常,控制框架会把SQL错误搞得不易分辨,如Java或.NET框架。有时,这些错误是由错误处理代码自动处理的,或者是由底层的代码解释程序处理的。例如,攻击者可以使用撇号(‘)而不是合法的输入:

wangzhan.com/userdetail.asp?username=‘

其结果为:用户未找到。

这就告诉我们,Web应用程序正在正确地检查用户输入,或者是程序框架正在阻止显示明显的错误消息。在这种情况下,执行SQL注入就变得困难了,但并不是不可能。这就引出了下一种攻击:盲目SQL注入。

盲目SQL注入攻击是如何工作的呢?它要查看数据库服务器是否真正地处理请求,或者查看该请求是否会触发来自Web服务器或SQL服务器的其它响应。

例如,我们已经看到了能够向我们提供张三(Zhangsan)用户细节的请求,能够看出该攻击是否成功。不过,“WAITFOR DELAY”命令要求SQL服务器在用查询结果响应之前,先暂停一分钟。

wangzhan.com/userdetail.asp?username=zhangsan;WAITFOR DELAY ‘0:1:0’—

如果网站能够正确地处理用户输入,它应当返回一个消息,说明找不到用户,或者发出其它的通知,说明“zhangsan;WAITFOR DELAY ‘0:1:0’--”不是一个合法用户。

必须指出,只有在将用户名变量的值被提交给SQL服务器时,并且Web应用程序能够正确地处理用户名变量的值时,才会出现这种情况。

传统的SQL注入将会返回错误,让用户知道有关查询失败的原因的细节。从本质上讲,盲目SQL注入依赖的是一种来自服务器的布尔逻辑响应(是/否,真/假等):查询请求要么被处理,要么遭遇失败。盲目SQL注入攻击一旦得以发现,它就会提供与典型的SQL注入一样的功能,但它更难以执行,为了获取信息也需要更多的时间。#p#

让盲目攻击起作用

假设在示例页面中,我们已经发现了盲目注入,我们希望发现关于表和数据库设计的细节。请看下面的请求:

wangzhan.com/userdetail.asp?username=zhangsan AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE xtype=0x55),1,1)),0)>65—

如果我们分解一下,会看出这个查询的结果实际上是一个是或否的回答,“这个表名的第一个字符是一个比65大的ASCII值吗?”先看最里面的查询:

SELECT TOP 1 name from sysObjects WHERE xtype=0x55

这意味着:获取来自sysObjects(所有的表)列表最上面的一个名字,其xtype(对象类型)等于ASCII码0x55(大写的字母“U”),转换成用户(user)表。所以,基本而言,我们获取了用户在数据库中所创建的第一个表。为简单起见,假设这个表就是“users”:

SUBSTRING(users),1,1

现在,我们创建了一个子串,它包含了表名users的首个字符。其值为“u”:

ISNULL(ASCII(u),0)>65

在这里我们查看u的ASCII码值的结果是否大于65,确认表名以字母开头。如果答案是肯定的,查询就会被处理。否则,查询就不会被处理。

使用这种方法,我们可以对每个查询简单地增加数字,从而发现首个用户名的第一个字母。例如,假设我们在大于117时(>117)得到了一个否定的响应,我们知道第一个字母是字母“u”,因为“u”的ASCII值是117。在发现这一点之后,我们可以简单地改变SUBSTRING参数来选择第二个字母,然后是第三个字母,等等。

最后,在没有错误代码时,我们借助于盲目注入就可以发现有漏洞的Web应用程序的数据库的完整的表结构。#p#

防御措施:保证应用程序编码的安全

安全的编码技术可以清除造成SQL注入攻击的漏洞。下面说的是Web应用程序安全编码的三个基本方法。

方法一:输入验证

在Web应用程序这一层防止SQL注入最常见的方法是使用输入验证。无论是何种语言或平台,这种方法都很有用。其实质就是在验证用户输入的大小和类型前,不对其采取行动。如果你期望用户输入的是数字,就不要接受非数字的东西。

例如,下面的URL:

wangzhan.com/userdetail.asp?userid=9899

很明显,在这个GET请求中,我们期望输入一个整数。一个简单的类型检查会告诉我们这是不是一个合法的字符,从而确保应用程序不会处理非数字值的输入。

if (is_numeric($userid)){}

此外,如果用户的ID总是四个字符的长度,我们就可以进一步采取措施,除了使用常规的表达式来执行整型检查,还可以强化边界检查。

if (preg_match(‘\d{4}’, $string)) {}

在对待如何验证用户输入值的这个问题上,我们仅受到自己创造力的限制。这里的关键是验证由用户发送并由系统使用的每一个值。

执行类型检查的另外一种方法是通过ASCII字符。用户名一般是由字母和数字组成的,所以我们不希望看到类似“@”、“;”之类的字符。因而,我们可以解析用户名变量,看看是否存在着不属于48-57,65-90,97-122的任何ASCII字符。在这个范围之外的任何ASCII字符对于用户名变量来说,都是不合法的,应当拒绝接受。这就是所谓白名单的一个例子。

知道白名单和黑名单的区别非常重要。白名单仅接受已知为安全的值或字符,如字母和数字。而黑名单则会阻止或不接受已知为恶意的字符。

方法二:规避技术

到目前为止,类型检查和边界检查似乎很容易,但并非在检查所有的数据类型时都会这么简单。许多情况下,我们还会使用VARCHAR数据类型(VARCHAR是一种比CHAR更加灵活的数据类型,同样用于表示字符数据,但是VARCHAR可以保存可变长度的字符串。)此类型有可能包含危险字符。这在备注字段和其它的长表单文本字段中很常见。

在这种情况下,我们可以利用一种称为规避的技术来确保变量的内容绝对不会被解析为SQL语句的一部分。

请看下面这个例子中的请求:

wangzhan.com/comment.asp?msg=I’m zhangsan.

在这个例子中,有一个撇号(’),这是一种常被认为是恶意字符的字符(请参考上一篇文章提到的撇号),我们并不希望排除它,因为此处它的用法是合法的,我们也不希望拒绝这个消息。

例如,在PHP中,我们可以使用mysql_real_escape_string函数:

mysql_real_escape_string($GET[‘msg’]);

这就不会导致安全问题,而是安全地解决了撇号问题,使其不能用于任何MySQL查询中:

msg=“Hello I\’m zhangsan”

同样地,在微软的.NET架构中,设计者常用REPLACE来保证字符串的安全。在下面的例子中,REPLACE函数将撇号(’)放在引号中,使其成为一种安全字符:

sql = replace(str, “’“, “’“)

方法三:参数化查询

防止SQL注入的第三种方法是一种称为参数化查询的技术。这种方法非常有效,因为它可以非常严密地控制SQL语句的组成结构。此方法在将对SQL语句的任何重要变更交给SQL服务器处理之前就拒绝其操作。

下面的Java例子中,我们简单地将参数添加到已经构建的静态查询中。首先,我们使用问号作为变量建立了真实的SELECT语句:

String query = “SELECT account_balance FROM user_data WHERE user_name = ? “;

下一步,我们调用prepareStatement函数:

PreparedStatement pstmt = connection.prepareStatement( query );

然后,在查询中我们将字符串“custname”指派给变量:

pstmt.setString( 1, custname);

最后,我们执行查询,并将结果存储在一个变量中:

ResultSet results = pstmt.executeQuery( );

通过使用这种方法,我们能够确保在附加的恶意查询或参数被发送给数据库之前,不会被添加到查询中。注意,应当像前面所讨论的那样来验证“custname”变量,因为它是不受信任的用户输入。

【编辑推荐】

  1. 名为LizaMoon的SQL注入攻击正在席卷全球
  2. SQL注入攻击三部曲之入门篇
  3. SQL注入攻击三部曲之进阶篇
  4. SQL注入攻击三部曲之高级篇
责任编辑:于爽 来源: TechTarget中国
相关推荐

2009-10-22 11:03:20

OSGi Web应用程

2022-02-23 15:33:19

前端框架开发Web

2010-09-03 15:17:29

2009-07-09 16:47:26

Servlet的Web

2009-04-01 14:33:33

2011-08-10 10:25:42

iPhoneAndroid应用安全

2009-02-27 17:00:25

2021-11-08 16:18:20

网络犯罪Deepfakes网络攻击

2010-11-11 09:15:08

Web应用程序

2012-04-19 09:34:21

ibmdw

2009-01-16 09:22:40

Web应用程序Web程序管理Web服务

2012-05-29 09:21:21

API

2012-04-25 22:56:10

Android

2021-04-08 08:06:55

SAP应用程序攻击

2010-05-20 09:48:36

2011-03-22 14:12:17

LAMP

2011-11-15 10:28:37

2012-05-14 17:35:28

移动Web

2015-02-02 15:46:59

Web应用架构大数据

2020-08-25 14:03:20

应用程序屏蔽应用程序内保护网络攻击
点赞
收藏

51CTO技术栈公众号