当心,你以为固若金汤的数据库可能已遭到了入侵。你需要重新思考一下自己公司的网站是否真得不会遭到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的细节。
攻击者寻找突破口
首先,攻击者可能查看应用程序是否能够正确地处理错误条件。有许多方法可以检查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查询)。其方法就是用分号(;)分开并用两个破折号结束。因而,在完成最初的查询后,攻击者就可以发送并运行自己选择的一个完整查询。
自动攻击
攻击者可以使用两种主要的攻击方法来利用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服务器即可。别再自欺欺人的相信“我的网站很小,谁会愿意攻击它呢?”这种愚蠢的谎言。如果你正在通过互联网做商务,不管企业大小,都易于遭到攻击。
接管
在很多情况下,攻击者能够完全控制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注入一样的功能,但它更难以执行,为了获取信息也需要更多的时间。
让盲目攻击起作用
假设在示例页面中,我们已经发现了盲目注入,我们希望发现关于表和数据库设计的细节。请看下面的请求:
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应用程序的数据库的完整的表结构。