5.3.1 人工预测

访问/会话令牌预测是对Web应用授权的最直接攻击。它实际上涉及以针对性的方式操纵令牌,以便绕过访问控制。首先,我们将讨论人工预测,下一小节,我们将描述能够加速预测似乎无法辨识的令牌的自动化分析技术。

人工猜测对于预测以人类可读的语法或者格式构建的访问令牌和会话ID值往往很有效。例如,在第1章中,你了解了如何简单地在Foundstone的Hacme银行Web应用示例中将“account_type”值从“Silver”改为“Platinum”,造成权限提升攻击。本小节将描述针对下列常见会话状态跟踪机制的人工篡改攻击:

·查询串

·POST数据

·HTTP首部

·Cookie

查询串人工预测

正如第1章中讨论过的,客户提供的HTTP查询串可能包含多个在URI中的问号之后以&号分隔的属性/值对,这些内容被传递给应用服务器处理。访问令牌和会话ID经常出现在查询串中。例如:

http://www.mail.com/mail.aspx?mailbox=joe&company=acme

这个URI的查询串部分包含用户提供的参数mailbox=joe&company=acme mail.aspx,这些参数将传递给mail.aspx。在这种场景下,修改查询中的mailbox参数值为另一个用户名(也就是/mail.aspx?mailbox=jane&company=acme)是明显的一种攻击,试图在验证为Joe的情况下查看Jane的邮箱。这个查询串可见于浏览器的地址栏,不需要特殊的Web工具就能轻松地修改。记住,URI中某些具有特殊意义的字符如=、&、#等,要求进行URL编码之后才能正确地传递给远程服务器。

对敏感数据使用POST!

一般不鼓励在查询串中转发会话ID,因为这很容易被任何注意浏览器地址栏的人修改。而且,和POST数据不同,URI和查询串往往被处理请求的中介设备(如代理)记录在客户端浏览器历史中,以及远程的Web和应用服务器上。这种记录带来了更多暴露给攻击者的机会。不熟练的用户不知道存储在查询串中的数据的敏感特性,可能也就不知不觉地通过电子邮件和公共论坛共享URI而暴露账户。最后,有趣的一点是在这些场景中即使使用SSL,查询串仍然会暴露。

因为存在这些问题,许多Web应用编程人员宁愿使用POST方法来转发敏感的会话和授权相关数据(这种方法在HTTP请求主体中携带参数值,不容易让简单的篡改看清),而不采用GET方法(在查询串中携带数据,在浏览器缓存、日志等地方更容易遭到攻击)。

警告  不要只因为客户端不能“看到”,就愚蠢地认为操纵POST数据很困难。我们在第1章中已经说明过,这其实很容易。

当然,在任何情况下,敏感的授权数据不应该仅仅用不予公开的方法进行保护,而应加上其他的安全手段。但是,正如我们在本书其他地方所说的,安全加上不公开没有害处。

POST数据人工预测

POST数据常常包含授权和会话相关信息,因为许多应用需要将客户提供的所有数据与提供这些数据的会话相关。下面的例子展示了如何使用cURL工具构建一个指向银行账户应用的POST操作,这个应用包含一些有趣的域,名为authmask(不确定它是什么,但是auth这个片段看上去确实有趣)、uid,还有一个值为viewacct的参数a。


$ curl -v -d 'authmask=8195' -d 'uid=213987755' -d 'a=viewacct' \
> --url https://www.victim.com/
*  Connected to www.victim.com (192.168.12.93)
> POST / HTTP/1.1
User-Agent: curl/7.9.5 (i686-pc-cygwin) libcurl 7.9.5 (OpenSSL 0.9.6c)
Host: www.victim.com
Pragma: no-cache
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*
Content-Length: 38
Content-Type: application/x-www-form-urlencoded
authmask=8195&uid=213987755&a=viewacct

POST参数在以上文本中的最后一行。和查询串的情况类似,属性/值对使用&字符分隔,如果参数名或者值包含特殊字符也需要编码。这个示例中有趣的一点是cURL自动地计算Content-Length HTTP首部,这个首部值必须与POST数据中的字符数相同。如果POST载荷遭到篡改,这个域必须重新计算。

“隐含”表单域: 另一种经典的“不公开安全性”技术是使用HTML表单中所谓的隐含值来传递敏感数据,如会话ID、产品价格或者销售税,尽管这些域对于通过浏览器查看网站的用户是隐藏的;但是,它们在网页的HTML源代码中当然仍旧可见。攻击者常常会检查实际的表单域标记;因为域名或者HTML注视可能提供这些域功能的更多线索。

提示  第1章中讨论过的WebScarab工具提供了一个极好的“揭示隐含域”功能,使这些域在常规的浏览器会话中出现。

我们来看看从应用登录页面中提取的HTML表单的一部分,看看授权攻击中如何利用它:


<FORM name=login_form action=
https://login.victim.com/config/login?4rfr0naidr6d3 method=post >
<INPUT name=Tries type=hidden> <INPUT value=us name=I8N type=hidden>
<INPUT name=Bypass type=hidden> <INPUT value=64mbvjoubpd06 name=U
type=hidden> <INPUT value=pVjsXMKjKD8rlggZTYDLWwNY_Wlt name=Challenge
type=hidden>
User Name:<INPUT name=Login>
Password:<INPUT type=password maxLength=32 value="" name=Passwd>

当用户提交用户名和密码时,他实际上向服务器提交了7部分信息,但是网页上只有两个可见。表5-6总结了这些值。

表5-6 隐含表单域值示例

从这个例子中看,似乎隐含域U跟踪会话状态信息,但是这时候,是否存在一个漏洞尚不明显。通过本章稍后的自动化会话ID预测的讨论可以得到分析未知值的思路。

HTTP首部人工预测

HTTP首部作为HTTP协议的一部分传递,有时候用于传递授权/会话数据。Cookie可能是最知名的HTTP首部,但是它们常用于授权和会话状态跟踪。有些应用还将根据HTTP Referer:和其他首部的值(不要担心,我们很快会处理Referer的拼写错误)作出(相当不安全的)授权决策。

注意  应用还可能依赖自定义的首部跟踪特定用户属性。

User-Agent: 最简单的授权测试之一是对客户端浏览器类型和版本的检查,这一般通过User-Agent HTTP首部实现。许多工具包括cURL都能让用户指定随意的User-Agent首部,所以这种测试作为授权机制实际上没有意义。例如,如果应用因为政治上而非技术上(例如需要特殊的ActiveX组件)的原因,要求使用Internet Explorer,你可以修改User-Agent首部假扮IE:


$ curl --user-agent "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)" \
> --url www.victim.com

虽然不是非常常见,但是如果这个漏洞出现,可能会出现在不依靠标准Web浏览器(如IE和Firefox),而是依赖发送特殊User-Agent值的定制HTTP客户端实现的应用中。如果处理请求的远程应用服务器实现不安全地信任所有指定特殊User-Agent值的请求,恶意用户就可能绕过授权访问敏感数据和资源。

Cookie: Cookie值可能是存储授权和状态信息的最常见位置。它们使用HTTP Set-Cookie首部设置,例如:


Set-Cookie: NAME=VALUE; expires=DATE; path=PATH;
domain=DOMAIN_NAME; secure

设置之后,客户端简单地使用Cookie首部将cookie重放给服务器,这看上去几乎和Set-Cookie首部完全一样,只是少了外部属性domain、path和secure。

因为cookie常用于授权,我们将在本章后面的一个小节中简短地进行讨论。

Referer: Web应用开发人员常犯的一个错误是信任包含在Referer首部中的部分信息,将其作为一种授权形式。那么,Referer首部是做什么的?为什么这是个安全上的错误?还有一点,为什么有这种错误的拼写?

Referer首部非常简单。本质上,它告诉用于获取服务器请求中的URI的资源URI(也就是,“我从哪里来”)。当你点击链接时,浏览器自动添加这一首部,但是如果你自己输入URI则不被包含。例如,如果你在网站A上,点击一个转移到网站B的链接,Referer首部将包含网站A的URI作为HTTP请求首部的一部分,像这样:


Referer: http://www.siteA.com/index.html

为什么依靠Referer首部进行授权是个错误?它常常在Web应用中实现,每当跟踪一个链接访问新区域,服务器上的一段定制代码检查Referer首部。如果包含在Referer首部中的URL是“预期的”,请求得到授权。如果不是,则请求遭到拒绝,用户被旁路到其他区域,通常是一个错误页面或者相似的页面。

我们可以在下面的代码示例中看到这个过程的工作原理。这是一个.asp页面中包含的简单Referer首部验证协议:


strReferer = Request.ServerVariables("HTTP_REFERER")
If strReferer = "http://www.victim.com/login.html" Then
' this page is called from login..htm!
' Run functionality here
End If

在这个例子中,代码仅仅寻找预期的URL——http://www.victim.com/login.html。如果这个URL存在,请求得到授权。否则,请求被拒绝。为什么开发人员使用包含在Referer首部中的URL作为授权?这主要是作为一条捷径。这种做法依赖于一个假设——能够访问某个特定应用页面的人可以看作已正确验证的人。这有一些明显的负面含义,例如,一个网站包含依靠Referer首部值进行验证和授权的管理区,一旦用户访问了一个特定页面如菜单页面,该区域中的所有附加页面都可以访问。

重要的是,Referer值是客户端控制的,因此,服务器不能依赖它做出安全相关的决策。Referer值很容易使用各种方法伪造。下面的Perl脚本指出伪造Referer值的一种方法:


use HTTP::Request::Common qw(POST GET);
use LWP::UserAgent;
$ua = LWP::UserAgent->new();
$req = POST ' http://www.victim.com/doadminmenu.html ';
$req->header(Referer => ' http://www.victim.com/adminmenu.html ');
$res = $ua->request($req);

在这个例子中,这段代码设置Referer值,使其看上去似乎是来源于adminmenu.html,而实际上明显不是。从这个例子中可以很清楚地看到,将Referer首部设置为任意值是简单的操作。正如老的安全谚语所说,将安全建立在某个东西的名称之上永远不是好主意,因为这种信息可以很容易地假冒、重放或者猜测。相关的一条安全准则也很切题:永远不要相信客户的输入。

那么错误的拼写是怎么回事?在互联网的早期有一种“怎么都好”的思维方式,足够长的能够使破解落空的错误拼写成为了标准。这个拼写就一直沿用至今。关于采用HTTP Referer首部作为验证需要了解的所有信息已经都告诉你了!

人工预测cookie

之前我们提到过,cookie仍然是Web应用中最流行的授权/会话管理形式,尽管历史上在安全方面有各种各样的问题(因为它们的中心角色,多年以来,恶意的黑客们已经发明了许多种方法来捕捉、劫持、盗窃、操纵或者以其他方式滥用cookie)。但是,针对cookie的安全攻击的悠久历史并不意味着cookie中有特别的设计问题,而是说明了应用服务器中对这些数据进行验证、授权和状态管理的重要性。对于使用cookie在Web应用中管理状态感兴趣的读者可以阅读RFC 2109(本章结尾处的“参考与延伸阅读”小节中有指向它和其他关于cookie参考内容的链接)。我们在本章较早关于HTTP首部的部分中已经说过,cookie使用Set-Cookie和Cookie HTTP首部管理。

cookie常用来存储几乎任何数据,所有域都可以很容易地使用第1章中概述过的HTTP代理修改。进行实际评估时,我们首选Burp web代理的原始请求编辑器功能。修改cookie值在拦截请求和响应,或者在重放面板里重放请求时都可以做到。图5-3显示了应用设置的cookie值。图5-4展示了如何使用Burp在重放面板中修改cookie值。

图5-3 使用Burp检查响应中设置的cookie值

图5-4 用Burp编辑cookie值

cookie一般是如何被非法使用以挫败授权的?下面是一个使用cookie实现“记住我”之类的授权/状态跟踪功能的应用实例:


Set-Cookie: autolog=bWlrZTpteXMzY3IzdA%3D%3D; expires=Sat, 01-Jan-2037
00:00:00 GMT; path=/; domain=victim.com

尽管这个cookie中有些加密的内容,但是即使是并不高明的攻击者也能够简单地复制cookie值并且重放,冒充对应的用户。聪明的读者可能注意到了autolog cookie值的最后4个字符是URL编码值%3D%3D。解码后的值为==(两个紧挨着的等号),这种字符组合附加在autolog cookie中令人费解的值后面几乎总是说明使用了Base64编码。解码这个Base64 cookie,得到ASCII字符串mike:mys3cr3t,这显然是对应用户的用户名和密码。最后,这个cookie没有设置secure和HTTPOnly标志。在没有设置secure的情况下,浏览器将通过非加密信道(常规的HTTP连接,与HTTPS相反)发送这个cookie值。HTTPOnly标志用于阻止恶意的JavaScript访问cookie值并将其带到攻击者控制的系统。

绕过cookie期限: 当你注销使用cookie的应用时,通常的表现是将cookie值设置为NULL(也就是Set-Cookie:foobar=)以及一个过去的期限,从而有效地删除cookie。应用也可以使用到期时间来强制用户每20分钟重新验证。从用户第一次验证开始,cookie将只有20分钟的有效期,过了20分钟,客户浏览器将删除它。因为后续的请求不再包含删除了的授权/会话cookie,服务器将把客户重定向到验证页面。这是将无用的会话自动超时处理的有效方法,但是,和任何安全敏感功能一样,它需要小心地实施。

例如,如果应用设置“has password”值在20分钟后到期,攻击者可能试图延长到期时间,查看服务器是否仍然承认这个cookie(注意粗体文本,我们已经将日期改到一年以后):


Set-Cookie: HasPwd=45lfhj28fmnw; expires=Tue, 17-Apr-2010
12:20:00 GMT; path=/; domain=victim.com
Set-Cookie: HasPwd=45lfhj28fmnw; expires=Tue, 17-Apr-2011
12:20:00 GMT; path=/; domain=victim.com

攻击者可能由此确定服务器端在会话时间上是否有限制。如果这个在20分钟加上一年之内有效的新cookie存续时间为一个小时,攻击者就知道20分钟的时间窗口是随意的——服务器实施60分钟的硬性超时期限。