戴上你的黑帽,现在我们来学习一些关于SQL注入真正有趣的东西。请记住,你们都好好地用这些将要看到的东西,好吗?
SQL注入攻击因如下几点而是一种特别有趣的冒险:
SQL注入攻击因一个非常恰当的原因而被保留在OWasp(Open Web application Security PRoject 开放Web应用安全项目)的十大隐患列表中第一位——它特别常见,非常容易利用,而且影响十分剧烈。一个很微小的注入风险经常就能使整个系统中的所有数据都被泄漏——而我将要展示给你如何运用大量不同的技术自己来这样做。
我几年前写《the OWASP Top 10 for .NET developers》时展示过如何防范SQL注入攻击,所以我不会专注在这些,这都是漏洞利用。受够了那些无聊的防御工具,让我们来攻击别的东西。
如果我们能攻破查询内容,你们的数据就都是我们的了让我们对让SQL注入攻击成为可能的原因做一个快速概括。简而言之,这就是输入查询并解密数据。让我把所说的可视化给你:比如说你有一个包含有类似于“id=1”之类的字符串参数的URL,容纳后那个参数通过如下方式构造了一个SQL查询。
这整个URL可能和这个东西看起来很像:
这是挺基础的东西,而当你能掌控链接中的信息并改变传递给查询的值时会变得有趣。好了,把1变成2会给你另一个你期待的东西,但是如果你这样做呢?
http://widgetshop.com/widget/?id=1 or 1=1
那可能在数据库服务器中存留成这样的:
1 | SELECT * FROM Widget WHERE ID = 1 OR 1=1 |
这告诉我们的是数据没有被净化——在上例中ID应该只是一个整数但“1 OR 1=1”的值也被接受。更重要的是,因为数据只是简单地被添加到查询中,它能够改变语句的功能。这个查询将能够选择所有的记录而不是单个记录,因为”1=1″语句是恒成立的。
或者,我们可以通过把“or 1=1”改成“and 1=2”来强制页面不返回任何记录,因为它一直都不成立所以没有结果返回。在这两个可选的方案中我们能方便地确定程序是否受注入攻击威胁。
这是SQL注入攻击的本质——通过不被信任的数据巧妙地操纵查询的执行——而在开发者做这样子事时发生。
123 | query = "SELECT * FROM Widget WHERE ID = " + Request.QueryString[ "ID" ]; // Execute the query... //执行查询... |
当然他们做的是将不被信任的数据参数化,但本文中我不会过多叙述(如果想要了解防范措施,转回part one of my OWASP series),而将更多谈论如何发动攻击。
好了,于是背景部分介绍了如何展示SQL注入风险存在,但你能拿它怎么办?让我们开始探寻一些普遍的注入模式。
抽丝剥茧:合并基于查询的注入让我们举个例子,表示我们想要返回一堆记录的页面,在这里是一个有一堆带有“TypeId”1的小东西的URL。像这样:
http://widgetshop.com/Widgets/?TypeId=1
页面上的结果会像这样:
我们会期待这个查询进入到数据库时变成像这样的东西:
1 | SELECT Name FROM Widget WHERE TypeId = 1 |
但是如果我们能应用我上述描绘的,也就是说我们可能能够给查询字符串中的数据添加SQL,我们可能会做出这样的东西:
http://widgetshop.com/Widgets/?TypeId=1 union all select name from sysobjects where xtype=’u’
然后它将产生一个如下的SQL查询:
1 | SELECT Name FROM Widget WHERE TypeId = 1 union all select name from sysobjects where xtype= 'u' |
现在记好了系统对象表列举数据库中所有对象,而在这个例子中我们用 xtype “u” 来筛选这个表,换言之,用户表。
当一个注入风险存在的时候将会有如下的输出:
这就是叫做合并基于查询的注入攻击,就像我们刚才简单地像原始结果添加一项,它直接到了HTML输出中——简单吧!既然我们已经知道有一个数据表叫“User”,我们可以做这样的事:
http://widgetshop.com/Widgets/?TypeId=1 union all select passWord from [user]
如果数据表中“user”不被中括号括起来,考虑到“user”这个词在数据库看来有其他含义,SQL服务器会变得不易控制。不管怎样,这是它返回的:
当然,UNION ALL语句只在第一个SELECT语句和第二个有相同的字段时起作用。这很容易被发现,你只需试试一些“union all select ‘a’”,如果它查询失败就试试“union all select ‘a’, ‘b’”之类的,以此类推。根本上你是在不断猜测列数直到你构造的查询发挥作用。
我们可以继续研究这个方面并揪出各种数据,但还是学习下一种攻击方式吧。有时一个基于合并查询的注入不会发挥作用,与输入格式、查询中添加的数据甚至结果如何显示都有关。为了绕开它我们需要变得更有创造性一些。
让程序自己泄密:基于错误信息的注入http://widgetshop.com/widget/?id=1 or x=1
等一下,这不是一个合法的SQL语句,那个“x=1”不会被处理,至少在没有一个叫做x的列时不会被处理。那么它不会抛出一个异常吗?严格地说,事实上你将会看到像这样的异常:
这是一个asp.net的错误,而其他的框架也有类似的样式。但是重要的是这些错误信息暴露了内部的实现方式,换言之,这告诉我们数据库中没有叫做“x”的字段。为什么这很重要?从根本上说,这是因为你一旦确立了一个应用程序在泄漏SQL异常,你就可以做这样的事:
http://widgetshop.com/widget/?id=convert(int,(select top 1 name from sysobjects where id=(select top 1 id from (select top 1 id from sysobjects where xtype=’u’ order by id) sq order by id DESC)))
这有好多需要吸收理解,我等会将回来详细解释。更重要的是通过那条语句你能够在浏览器中得到这样的结果:
现在我们得到了,我们已经发现那数据库里有一个表单叫做“Widget”。你将经常能看到这中注入攻击因依赖于数据库内部的错误而被称作“基于错误信息的注入”。让我们解构URL中的这个查询:
12345678 | convert ( int , (
select top 1 name from sysobjects where id=(
select top 1 id from (
select top 1 id from sysobjects where xtype= 'u' order by id
) sq order by id DESC
)
) ) |