避开客户端控件

在第1章中,我们提到,由于客户端可提交任意输入,Web应用程序的核心安全因此受到威胁。尽管如此,大部分的Web应用程序仍然依靠在客户端执行各种措施,对它提交给服务器的数据进行控制。通常,这种做法造成一个基本的安全缺陷:用户能够完全控制客户端和由其提交的数据,并可以避开任何在客户端执行但服务器并不采用的控件。

应用程序依靠客户端控件限制用户输入表现在两个方面:首先,应用程序可通过客户端组件,使用某种它认为可防止用户修改的机制传送数据。其次,应用程序可在客户端执行保护措施,控制用户与其客户端的交互,从而对功能实施限制,并(或)在提交用户输入之前对这些输入进行控制。我们可通过使用HTML表单功能、客户端脚本或浏览器扩展技术实现这种控制。

我们将在本章中举例说明各种客户端控件并分析避开这些控件的方法。

通过客户端传送数据

应用程序通常以终端用户无法直接查看或修改的方式向客户端传送数据,希望客户端在随后的请求中将这些数据送回服务器。通常,应用程序的开发者简单地认为所采用的传输机制将确保通过客户端传送的数据在传送过程中不会遭到修改。

由于客户端向服务器传送的一切内容都完全处于用户的控制范围内,认为通过客户端传送的数据不会被修改,这种看法往往是错误的,并致使应用程序易于遭受一种或几种攻击。

你肯定想知道这其中的原因。如果一个特殊的数据已知,并由服务器指定,则应用程序需要向客户端提交这个值,然后读取回该值。实际上,对开发者而言,以这种方式编写应用程序往往更加简单,原因如下。

这样做不必追踪用户会话中的各种数据。减少每次会话保存在服务器上的数据量,同时还能提高应用程序的性能。

如果将应用程序部署到几台不同的服务器上,那么,在执行多级操作时,用户可能需要与多台服务器进行交互,这时在处理相同用户请求的主机之间共享服务器端数据就会遇到困难。那么,使用客户端传送数据就成为解决这个问题的一个颇具吸引力的方案。

如果应用程序在服务器上采用任何第三方组件,如购物车,则可能很难或无法修改这些组件,因此,通过客户端传输数据就成为集成这些组件的最简单方式。

在某些情况下,跟踪服务器上的新数据可能需要更新核心服务器端API,因而会触发正式的变更管理流程和回归测试。这时,实施包含客户端数据传输的更加细化的解决方案可以避免这种情况,从而满足紧凑的完工期限要求。

但是,以这种方式传送敏感数据通常并不安全,并且会在应用程序中造成大量漏洞。

隐藏表单字段

隐藏HTML表单字段是一种表面看似无法修改,通过客户端传送数据的常用机制。如果一个表单标记为隐藏,它就不会显示在屏幕上。但是,用户提交表单时,保存在表单中的字段名称和值仍被送交给应用程序。

在隐藏表单字段中保存产品价格的零售应用程序就是存在这种安全缺陷的典型示例。在Web应用程序发展的早期阶段,这种漏洞极其普遍,现在也绝没有消失。典型的表单如图5-1所示。

图5-1 典型的HTML表单

创建这个表单的代码如下:

注意,表单字段名为price,其被标记为隐藏。用户提交表单时,这个字段将被送交给服务器:

尝试访问

http://mdsec.net/shop/28/

现在,虽然price字段并未显示在屏幕上,用户无法对其进行编辑,但这只是因为应用程序指示浏览器隐藏该字段而已。因为在客户端进行的一切操作最终将由用户控制,用户需要编辑价格时就可解除这个限制。

要实现编辑操作,一种方法是保存HTML页面的源代码,编辑字段的值,然后将源代码重新载入浏览器,并单击Buy按钮。但是,使用拦截代理服务器(intercepting proxy)对数据进行动态修改更加简单方便。

在攻击Web应用程序时,拦截代理服务器极其有用,它是一种不可或缺的工具。我们可以找到大量拦截代理服务器工具,本书使用其中一位作者编写的Burp Proxy工具。

代理服务器位于Web浏览器和目标应用程序之间。它拦截应用程序发布和收到的每一个HTTP或HTTPS请求和响应。用户可通过它拦截任何消息,对其进行检查或修改。如果之前从未用过拦截代理服务器,请参阅第20章了解有关拦截代理服务器的运行机制,如何配置和使用拦截代理服务器的详细信息。

安装拦截代理服务器并进行相应配置后就可以拦截提交表单的请求,随意修改price字段的值,如图5-2所示。

图5-2 使用拦截代理服务器修改隐藏表单字段的值

如果应用程序根据表单提交的价格处理交易,就能够以选择的任何价格购买该产品。

 提示 如果发现应用程序易于受到这种攻击,看看是否可以提交一个负数价格值。有些时候,应用程序居然接受使用负数价格值的交易。攻击者不仅收到订购的货物,信用卡还会收到退款——一种两面得利的情况(如果出现这种情况的话)。

HTTP cookie

HTTP cookie是通过客户端传送数据的另一种常用机制。和隐藏表单字段一样,HTTP cookie一般并不显示在屏幕上,也不可由用户直接修改。当然,用户可使用拦截代理服务器,通过更改设置cookie的服务器响应或随后发布这些cookie的客户端请求,对HTTP cookie进行修改。

下面以前面的示例(稍作修改)为例进行说明。消费者登录应用程序后,收到以下响应:

DiscountAgreed cookie是依靠客户端控件(基于cookie一般无法被修改这个事实)保护通过客户端传送的数据的典型示例。如果应用程序信任DiscountAgreed cookie返回给服务器的值,那么消费者修改这个值就可获得任意折扣。例如:

尝试访问

http://mdsec.net/shop/92/

URL参数

应用程序常常使用预先设定的URL参数通过客户端传送数据。例如,用户浏览产品目录时,应用程序会向他们提供指向下列URL的超链接:

http://mdsec.net/shop/?prod=3&pricecode=32

如果包含参数的URL显示在浏览器的地址栏中,任何用户不需要使用工具就可任意修改其中的参数。但是,在许多情况下,应用程序并不希望普通用户查看或修改URL参数。例如:

使用包含参数的URL加载嵌入图像时;

使用包含参数的URL加载框架内容时;

表单使用POST方法并且其目标URL包含预先设定的参数时;

应用程序使用弹出窗口或其他方法隐藏浏览器地址栏时。

当然,如前所述,我们可以使用拦截代理服务器修改上面的任何URL参数。

Referer消息头

浏览器在大多数HTTP请求中使用Referer消息头。浏览器使用这个消息头指示提出当前请求的页面的URL——或者是因为用户单击了一个超链接或提交了一个表单,或者是因为该页面引用了其他资源(如图像)。因此,我们可以利用这个消息头通过客户端传送数据,这是因为应用程序处理的URL受其控制,开发者认为Referer消息头可用于准确判断某个特殊的请求由哪个URL生成。

以帮助忘记密码的用户重新设置密码的机制为例。应用程序要求用户按规定的顺序完成几个步骤,然后再通过以下请求重新设置密码值:

GET /auth/472/CreateUser.ashx HTTP/1.1

Host: mdsec.net

Referer: https://mdsec.net/auth/472/Admin.ashx

应用程序可以使用Referer消息头证实这个请求是在正确的阶段(Admin.ashx)提出的,然后才允许用户访问请求的功能。

但是,因为用户控制着每一个请求,包括HTTP消息头,他可以直接进入CreateUser.ashx,并使用拦截代理服务器将Referer消息头的值修改为应用程序需要的值,从而轻易避开这种控制。

实际上,根据w3.org标准,Referer消息头完全是可选的。因此,虽然大多数浏览器执行这个消息头,但是,使用它控制应用程序的功能应被视为是一种“陈腐”的做法。

尝试访问

http://mdsec.net/auth/472/

 错误观点 不知何故,相比于请求的其他部分(如URL),人们常常认为HTTP消息头具有更强的“防篡改”能力。这会导致开发者实施信任由Cookie和Referer消息头提交的值的功能,而对其他数据(如URL参数)执行严格的确认。这种认识是错误的——因为任何业余黑客在攻击应用程序时都可以使用大量免费的拦截代理服务器工具轻松修改所有请求数据。打个比方,假如老师准备搜查你的书桌,你觉得把水枪藏在抽屉底下会更加安全,因为她需要弯下腰才能发现它。

渗透测试步骤

(1)在应用程序中,确定隐藏表单字段、cookie和URL参数明显用于通过客户端传送数据的任何情况。

(2)根据数据出现的位置以及参数名称之类的线索,确定或猜测它在应用程序逻辑中发挥的作用。

(3)修改数据在应用程序相关功能中的值。确定应用程序是否处理在参数中提交的任意值,以及这样做是否会导致应用程序易于遭受任何攻击。

模糊数据

有时候,通过客户端传送的数据被加密或进行了某种形式的模糊处理,因而变得晦涩难懂。例如,下面的产品价格并不保存在隐藏字段中,而是以隐含值(crytic value)的形式传送。

如果发现这种情况,可以据此推断,提交表单后,服务器端应用程序将检查模糊字符串的完整性,或对其进行解密或去模糊处理,然后处理它的明文值。这种深层次处理可能易于造成各种漏洞;但是,要探查或利用这种漏洞,首先必须对有效载荷进行适当的处理。

尝试访问

http://mdsec.net/shop/48/

 注解 应用程序的会话处理机制通常通过客户端传送模糊数据。在HTTP cookie中传送的会话令牌、在隐藏字段中传送的反CSRF令牌,以及用于访问应用程序资源的一次性URL令牌,全都是在客户端篡改的潜在目标。我们将在第7章详细讨论针对这些令牌的注意事项。

渗透测试步骤

有几种方法可以对通过客户端传送的模糊数据实施攻击。

(1)如果知道模糊字符串的明文值,可以尝试破译模糊处理所使用的模糊算法。

(2)如第4章所述,应用程序的其他地方可能包含一些功能,攻击者可以利用它们返回由自己控制的一段明文生成的模糊字符串。在这种情况下,攻击者可以向目标功能直接提交任意一个有效载荷,获得所需要的字符串。

(3)即使模糊字符串完全无法理解,也可以在其他情况下重新传送它的值,实现某种恶意效果。例如,前面显示的表单的pricing_token参数中可能包含一个加密的产品价格。尽管攻击者无法对选择的任意价格以相同的算法进行加密,但是,他们可以把另一个更加便宜的产品的加密价格复制过来,放在这里提交。

(4)如果其他所有方法全都无效,还可以通过提交畸形字符串——如包含超长值、不同字符集等错误的字符串——尝试攻击负责对模糊数据进行解密或去模糊处理的服务器端逻辑。

ASP.NET ViewState

ASP.NET ViewState是一种通过客户端传送模糊数据的常用机制。它是一个由所有ASP.NET Web应用程序默认创建的隐藏字段,其中包含关于当前页面状态的序列化信息。ASP.NET平台使用ViewState提高服务器的性能——服务器通过它在连续提交请求的过程中保存用户界面中的元素,而不需要在服务器端维护所有相关的状态信息。例如,服务器会根据用户提交的参数填充下拉列表。用户随后提交请求时,浏览器并不向服务器提交列表的内容。相反,浏览器提交隐藏的ViewState字段,其中包含该列表的序列化格式。然后,服务器对ViewState进行去序列化处理,并重新建立相同的列表,再将其返回给用户。

除这种核心功能外,开发者还在连续提交请求的过程中使用ViewState保存任意信息。例如,应用程序可以不将产品价格保存在隐藏表单字段中,而是将其保存在ViewState中,如下所示:

string price = getPrice (prodno);

ViewState.Add(“price”, price);

返回给用户的表单如下所示:

当用户提交表单时,浏览器将发送以下请求:

很明显,上面的请求中并不包含产品价格——只有订购的数量和模糊处理后的ViewState参数。随意更改这个参数会导致应用程序显示错误消息,并因此终止购买交易。

ViewState参数实际上是一个Base64编码字符串,用户可以轻松对这个字符串进行解码,以查看其代表的价格参数,如下所示:

 提示 在对一个可能为Base64编码的字符串进行解码时,用户常常会犯一个错误,即从字符串的错误位置开始解码。鉴于Base64编码的特点,如果从错误的位置开始解码,解码后的字符串中会出现乱码。Base64采用基于数据块的格式,每4字节的编码数据解码后会变为3个字节。因此,如果解码后的Base64字符串并无意义,请尝试从编码字符串中的4个相邻的偏移值位置开始解码。

默认情况下,ASP.NET平台通过在ViewState中加入一个密钥散列(称为MAC保护)来防止篡改。但是,一些应用程序禁用了这项默认启用的保护,这意味着攻击人员可以修改ViewState的值,以确定其是否会对应用程序的服务器端处理产生影响。

Burp Proxy提供一个指示ViewState是否受MAC保护的ViewState解析器,如图5-3所示。如果ViewState未受到保护,则攻击人员可以使用ViewState树下的十六进制编辑器在Burp中编辑ViewState的内容。在向服务器或客户端发送消息时,Burp将发送经过更新的ViewState,具体到前面的示例,这样就可以更改购物时商品的价格。

图5-3 如果未设置EnableViewStateMac选项,Burp Proxy可解码并显示ViewState,允许攻击者查看其内容并对它们进行编辑

尝试访问

http://mdsec.net/shop/76/

渗透测试步骤

(1)如果要攻击ASP.NET应用程序,确定是否对ViewState启用了MAC保护。如果ViewState结构末尾存在一个20字节的散列,即表示应用程序启用了MAC保护。可以使用Burp Suite中的解析器确定上述散列是否存在。

(2)即使ViewState受到保护,还可以解码各种不同应用程序页面中的ViewState参数,了解应用程序是否使用ViewState通过客户端传送任何敏感数据。

(3)尝试修改ViewState中某个特殊参数的值,但不破坏它的结构,看看是否会导致错误消息。

(4)如果能够修改ViewState而不会造成错误,则应该分析ViewState中每个参数的功能,以及应用程序是否使用这些参数保存任何定制数据。尝试用专门设计的值代替每一个参数,探查常见的漏洞,就像检查通过客户端传送的其他数据项一样。

(5)注意,不同页面可能启用或禁用MAC保护,因此有必要测试应用程序的每一个重要页面,了解其中是否存在ViewState攻击漏洞。如果在启用被动扫描的情况下使用Burp Scanner, Burp将自动报告任何使用ViewState但未启用MAC保护的页面。

收集用户数据:HTML表单

应用程序使用客户端控件限制客户端提交的数据的另一个主要控制对象,是最初不由服务器指定,而是由客户端计算机自己收集的数据。

HTML表单是一种最简单、最常用的机制,主要用于从用户收集输入并将其提交给服务器。用户在已命名的文本字段中输入数据,再将它们以名/值对的形式提交给服务器,是这种方法的最基本应用。但是,表单还有其他用法,即对用户提交的数据施加限制或执行确认检查。当应用程序使用这些客户端控件作为安全机制,防御恶意输入时,攻击者通常能够轻易避开这些控件,致使应用程序非常易于受到攻击。

长度限制

下面对本章开头部分的HTML表单稍作修改,规定quantity字段的最大长度为1:

于是,浏览器将阻止用户在输入字段中输入任何超过1个字符的值,而且服务器端应用程序也认为它收到的quantity参数将小于10。但是,通过拦截提交表单的请求,并在其中输入任意值;或拦截包含表单的响应,并删除maxlength属性,就可以轻易避开这种限制。

拦截响应

试图拦截并修改服务器响应时,攻击者可能发现代理服务器显示以下相关消息。

HTTP/1.1 304 Not Modified

Date: Wed, 6 Jul 2011 22:40:20 GMT

Etag: “6c7-5fcc0900”

Expires: Thu, 7 Jul 2011 00:40:20 GMT

Cache-Control: max-age=7200

产生这个响应是因为浏览器已经在缓存中保存了所请求资源的副本。当浏览器请求一个已存入缓存的资源时,它通常会在请求中添加另外两个消息头,分别为If-Modified-Since和If-None-Match消息头,如下所示:

GET /scripts/validate.js HTTP/1.1

Host: wahh-app.com

If-Modified-Since: Sat, 7 Jul 2011 19:48:20 GMT

If-None-Match: “6c7-5fcc0900”

这些消息头告诉服务器浏览器上次更新缓存副本的时间。Etag字符串(由服务器随资源副本一起提供)是一种序列号,服务器为每个可缓存的资源分配一个Etag,如果资源被修改,它也会随之更新。如果服务器拥有比If-Modified-Since消息头中指定日期更新的资源,或者如果当前版本的Etag与If-None-Match消息头中指定的Etag不匹配,那么服务器就会在响应中提供最新的资源。否则,它将返回和本例相同的304响应,通知浏览器资源没有被修改,浏览器应使用缓存中的副本。

如果是这样,必须拦截并修改浏览器保存在缓存中的资源,可以拦截相关请求并删除If-Modified-Since和If-None-Match消息头,让服务器在响应中提供所请求资源的完整版本。Burp Proxy中有一个从每个请求中删除这些消息头的选项,可覆盖由浏览器发送的所有缓存信息。

渗透测试步骤

(1)寻找包含maxlength属性的表单元素。提交大于这个长度但其他格式合法的数据(例如,如果应用程序要求数字,则提交一个数值)。

(2)如果应用程序接受这个超长的数据,则可以据此推断出服务器并没有采用客户端确认机制。

(3)根据应用程序随后对参数进行的处理,可以通过确认机制中存在的缺陷利用其他漏洞,如SQL注入、跨站点脚本或缓冲区溢出。

基于脚本的确认

HTML表单内置的输入确认机制极其简单,而且不够详细,不足以对各种输入执行相关确认。例如,用户注册表单中可能包含姓名、电子邮件地址、电话号码和邮政编码字段,所有这些字段都要求不同的输入。因此,开发者通常在脚本中执行定制的客户端输入确认。下面对本章开头的示例进行一些修改,以说明这个问题:

尝试访问

http://mdsec.net/shop/139/

form标签的onsubmit属性指示浏览器在用户单击“提交”按钮时运行ValidateForm函数,并且只有在该函数返回“真”时才提交表单。这种机制帮助客户端阻止提交表单的企图,对用户的输入执行定制的确认检查,进而决定是否接受该输入。上面示例中采用的确认机制极其简单,只检查在amout字段中输入的数据是否为介于1到50之间的整数。

这种类型的客户端控制非常容易解除,但通常足以禁用浏览器中的JavaScript。如果是这样,并且忽略onsubmit属性,那么,不需要任何定制确认就可以提交表单。

但是,如果应用程序依靠客户端脚本执行正常操作(如构造部分用户界面),完全禁用JavaScript可能会终止应用程序。另一种更加合理的办法是在浏览器的输入字段中输入一个良性(已知无恶意)值,然后用代理服务器拦截确认后提交的表单,并将其中的数据修改成想要的值。通常,这是解除基于JavaScript的确认的最简单有效的方法。

另外,可以拦截包含JavaScript确认程序(validation routine)的服务器响应,修改其脚本使其失效——在前面的示例中,更改每一个ValidateForm函数使其返回“真”即可。

渗透测试步骤

(1)确定任何在提交表单前使用客户端JavaScript进行输入确认的情况。

(2)通过修改所提交的请求,在其中插入无效数据,或修改确认代码使其失效,向服务器提交确认机制通常会阻止的数据。

(3)与长度限制一样,确定服务器是否采用了和客户端相同的控件;如果并非如此,确定是否可利用这种情况实现任何恶意意图。

(4)注意,如果在提交表单前有几个输入字段需要由客户端确认机制检验,需要分别用无效数据测试每一个字段,同时在所有其他字段中使用有效数据。如果同时在几个字段中提交无效数据,可能服务器在识别出第一个无效字段时就已经停止执行表单,从而使测试无法到达应用程序的所有可能代码路径。

 注解 使用客户端JavaScript程序确认用户输入的做法在Web应用程序中非常普遍,但这并不表示这种应用程序全都易于遭受攻击。只有当服务器并未采用和客户端相同的确认机制,以及能够避开客户端确认的专门设计的输入可在应用程序中造成某种无法预料的行为时,应用程序才存在风险。

在绝大多数情况下,在客户端确认用户输入有助于提高应用程序的性能,改善用户体验。例如,在填写详细的注册表单时,普通用户可能会犯许多错误,如忽略必要的字段或电话号码格式出现错误。如果不采用客户端确认机制,更正这些错误可能需要多次加载注册页面,反复向服务器传送消息。在客户端执行基本的确认检查可使用户体验更佳,减轻服务器的负担。

禁用的元素

如果HTML表单中的一个元素标记为禁用,它会在屏幕上出现,但以灰色显示,并且无法像常规控件那样编辑或使用。而且,提交表单时,表单也不向服务器传送这个元素。以下面的表单为例:

这个表单中的产品价格位于禁用的文本字段中,并出现在屏幕上,如图5-4所示。

图5-4 包含禁用的输入字段的表单

提交该表单时,应用程序只向服务器传送quantity参数。但是,存在禁用字段表示应用程序最初可能已经使用过price参数(可能在开发阶段用于测试目的)。这个参数很可能已经提交给服务器并经过应用程序处理了。在这种情况下,应当测试服务器端应用程序是否仍然会处理这个参数。如果确实如此,可以尝试对这种情况加以利用。

尝试访问

http://mdsec.net/shop/104/

渗透测试步骤

(1)在应用程序的每一个表单中寻找禁用的元素。尝试将发现的每一个元素与表单的其他参数一起提交给服务器,确定其是否有效。

(2)通常,如果提交元素被标记为禁用,其按钮即以灰色显示,表示相关操作无效。这时应该尝试提交这些元素的名称,确定应用程序是否在执行所请求的操作前执行服务器端检查。

(3)注意,在提交表单时,浏览器并不包含禁用的表单元素;因此,仅仅通过浏览应用程序的功能以及监控由浏览器发布的请求并不能确定其中是否含有禁用的元素。要确定禁用的元素,必须监控服务器的响应或在浏览器中查看页面来源。

(4)还可以使用Burp Proxy中的HTML修改功能自动重新启用应用程序中的任何禁用的字段。

收集用户数据:浏览器扩展

除HTML表单外,另一种收集、确认并提交用户数据的主要方法是使用在浏览器扩展中运行的客户端组件(如Java或Flash)。最初用于Web应用程序时,浏览器扩展通常用于执行简单而基本的任务。如今,已经有越来越多的公司使用浏览器扩展来创建功能强大的客户端组件。这些组件在浏览器中运行,跨越多个客户端平台,提供相关反馈,提高灵活性,并与桌面应用程序协作。使用浏览器扩展的一个副作用是:由于速度和用户体验方面的原因,之前在服务器上执行的处理任务现在将在客户端完成。对某些应用程序(如网上交易应用程序)而言,速度至关重要,因此,许多关键的应用程序任务需要在客户端完成。为了提高速度,在开发应用程序时可能需要“有意”以牺牲安全为代价,这可能是因为开发者误以为交易者全都是可信用户,或者浏览器扩展会自行防御恶意企图。但是,如我们在第2章以及本章前面部分讨论核心安全问题时所述,客户端组件不可能为自己的业务逻辑提供防御。

浏览器扩展可以通过输入表单、或者在某些情况下通过与客户端操作系统的文件系统或注册表交互,以各种不同的方式收集数据。在将收集到的数据提交给服务器之前,它们可以对这些数据执行任何复杂的确认和处理。而且,由于它们的内部工作机制与HTML表单和JavaScript相比更加不透明,开发者认为它们执行的确认更加难以躲避。为此,通过浏览器扩展查找Web应用程序中存在的漏洞往往能够获得更大的成果。

赌博组件是应用客户端控件的典型浏览器扩展。如前所述,客户端控件并不可靠,因此,如果使用在潜在攻击者的机器上本地运行的浏览器扩展来执行在线赌博应用程序,这种做法将非常具有诱惑力。如果游戏的任何一个部分由客户端而非服务器控制,攻击者就可以非常精确地对游戏进行控制,以提高获胜机率、改变规则、或更改返回给服务器的得分。这种情况将导致以下几种攻击。

可能会使用客户端组件来维护游戏状态。这时,攻击者就可以在本地篡改游戏状态,从而在游戏中获得优势。

攻击者能够避开客户端控件,并执行非法操作,以在游戏中获得优势。

攻击者能够发现隐藏的功能、参数或资源,一旦调用这些功能、参数或资源,攻击者将可以非法访问服务器端资源。

如果游戏中还有其他玩家,客户端组件可能会接收并处理其他玩家的信息,攻击者获知这些信息就能够在游戏中获得优势。

常见的浏览器扩展技术

常见的浏览器扩展技术包括Java applet、Flash和Silverlight。由于这些技术的用途基本相同,因此,它们也提供类似的安全功能:

它们均编译成中间字节码;

它们在提供沙盒执行环境的虚拟机中运行;

它们可能会使用远程框架,这类框架采用序列化来传输复杂数据结构,或通过HTTP传送对象。

Java

Java applet在Java虚拟机(JVM)中运行,并采用由Java安全策略应用的沙盒。因为Java在Web发展的早期就已存在,并且其核心概念仍基本不变,因此,有大量知识和工具可用于对Java applet实施攻击或进行防御(如本章后面部分所述)。

Flash

Flash对象在Flash虚拟机中运行。和Java applet一样,Flash也要在主机上的沙盒中运行。此前,Flash主要用于传送动画内容。但随着较新版本的ActionScript的推出,现在Flash已经能够传送成熟的桌面应用程序。Flash最近的主要更新为ActionScript 3以及采用动作信息格式(AMF)序列化的远程功能。

Silverlight

Silverlight是微软开发的与Flash类似的产品。同样,该产品主要用于启动各种桌面应用程序,允许Web应用程序在浏览器内的沙盒环境中提供精简的.NET体验。从技术上讲,任何兼容.NET的语言,从C#到Python,都可用于开发Silverlight,但C#是开发Silverlight最常用的语言。

攻击浏览器扩展的方法

针对使用浏览器扩展组件的应用程序实施攻击时,需要采用以下两种常用的技巧。

首先,可以拦截并修改浏览器扩展组件提出的请求及服务器的响应。在许多情况下,这是对浏览器扩展组件进行测试的最简单也是最快速的方法,但这时你可能会遇到各种限制。正在传输的数据可能经过模糊处理或加密,或者使用专门针对所用技术的方案进行了序列化。仅仅查看组件生成的流量,可能会忽略一些关键的功能或业务逻辑,而这些功能或逻辑只需对组件本身进行分析就可以发现。另外,在正常使用拦截代理服务器时也可能会遇到障碍;但是,通常情况下,通过仔细配置(如本章后面部分所述),完全可以克服这些障碍。

其次,可以直接针对组件实施攻击,并尝试反编译它的字节码,以查看其源代码;或者使用调试器与组件进行动态交互。这种方法的优点在于,如果进行得非常彻底的话,将能够确定组件支持或引用的所有功能。还能修改组件向服务器提交的请求中的关键数据,而无论这些数据采用何种模糊处理或加密机制。其缺点在于,这种方法可能相当费时,并且需要深入了解浏览器扩展组件所使用的技术和编程语言。

许多时候,最好是结合使用上述两种技巧。下面我们详细介绍这种技巧。

拦截浏览器扩展的流量

如果浏览器已配置为使用拦截代理服务器,并且应用程序使用浏览器扩展加载客户端组件,这时,该组件提出的请求将经过代理服务器。在某些情况下,这时就可以开始测试相关功能,因为攻击者能够以常规方式拦截并修改组件提出的请求。

在需要避开在浏览器扩展中实施的客户端输入确认的情况下,如果组件以透明方式向服务器提供经过确认的数据,那么,如5.2节所述,可以使用拦截代理服务器修改这些数据。例如,支持身份验证机制的浏览器扩展可能会收集用户证书,并对这些证书进行确认,然后在请求中以明文参数的形式向服务器提交这些证书。这时,攻击者不需要对组件本身进行任何分析或攻击,就可以轻松解除这种确认。

在其他情况下,测试浏览器扩展组件可能会遇到各种障碍。以下几节将讨论这些问题。

处理序列化数据

应用程序可能会首先对数据或对象进行序列化处理,然后再通过HTTP请求传送这些数据或对象。当然,通过检查原始的序列化数据,可以解译一些基于字符串的数据,但是,通常而言,需要对序列化数据进行解压缩才能了解这些数据。如果希望修改这些数据,以破坏应用程序的处理过程,首先,需要解压缩序列化内容,对其进行必要的编辑,然后重新对其进行序列化处理。几乎可以肯定,直接编辑原始的序列化数据将破坏其格式,并在应用程序处理请求时导致解析错误。

每种浏览器扩展技术都具有各自处理HHTTP消息中数据的序列化方案。因此,通常渗透测试员可以根据所采用的客户端组件推断出相关数据的序列化格式,但是,任何时候,仔细检查相关HTTP消息才能确认序列化格式。

Java序列化

Java语言本身支持对象序列化,而且,Java applet可能会以这种方式在客户端与服务器应用程序组件之间传送序列化数据结构。通常,包含序列化Java对象的消息很容易辨别,因为它们使用以下Content-Type消息头:

使用代理服务器拦截原始的序列化数据后,就可以通过Java对这些数据进行去序列化处理,以查看其中包含的原语数据项。

Dser是Burp Suite中的一个有用插件,该插件提供一个框架,可用于查看和处理Burp拦截的序列化Java对象。该工具将拦截到的对象中的原语数据转换为XML格式,以便于进行编辑。编辑相关数据后,Dser将重新对对象进行序列化,并对HTTP请求进行相应的更新。

可以在以下URL下载Dser并详细了解它的运行机制:

Flash序列化

Flash使用自己的可用于在服务器和客户端组件之间传输复杂数据结构的序列化格式。通常,可以通过以下Content-Type消息头辨别动作信息格式(AMF):

Burp本身支持AMF格式。确定包含序列化AMF数据的HTTP请求或响应后,它会解压缩并以树状形式显示相关内容,以便于查看和编辑,如图5-5所示。在修改结构中的相关原语数据项后,Burp将重新对消息进行序列化,然后就可以将该消息转发给服务器或客户端,由它们进行处理。

图5-5 Burp Suite支持AMF格式并允许查看和编辑去序列化数据

Silverlight序列化

Silverlight应用程序能够利用.NET平台内置的Windows通信基础(WCF)远程框架。使用WCF的Silverlight客户端组件通常采用微软的用于SOAP的.NET二进制格式(.NET Binary Format for SOAP,NBFS)。可以通过以下Content-Type消息头辨别该格式:

Burp Proxy中的一个插件能够自动对NBFS编辑的数据进行去序列化,然后在Burp的拦截窗口中显示这些数据。在查看或编辑已解码的数据后,该插件会对数据重新进行编辑,然后将数据转发给服务器或客户端,由它们进行处理。

用于Burp的WCF 二进制SOAP插件由Brian Holyfield开发,可以从以下URL下载该插件:

拦截浏览器扩展流量时遇到的障碍

如果浏览器已设置为使用拦截代理服务器,代理服务器可能并不会拦截,或无法拦截浏览器扩展组件提出的请求。之所以出现这种情况,可能是由于组件的HTTP代理或SSL出现问题,或者二者同时出现问题。一般情况下,通过仔细配置代理服务器可解决这个问题。

第一个问题是,客户端组件可能并不执行在浏览器或计算机的设置中指定的代理配置。这是因为组件可能会在浏览器本身或扩展框架提供的API以外发出它们自己的HTTP请求。出现这种情况仍然可以拦截组件的请求,但需要修改计算机的hosts文件以实现拦截目的,同时将代理服务器配置为支持匿名代理,并自动重定向到正确的目标主机。更多详细信息,请参阅第20章。

另一个问题在于,客户端组件可能不接受拦截代理服务器提供的SSL证书。即使代理服务器使用的是一般自签名证书,并且浏览器已配置为接受这类证书,但浏览器扩展组件仍有可能拒绝此类证书。这可能是因为浏览器扩展组件不接受浏览器在暂时可信的证书方面的配置,或者因为组件本身以编程方式要求拒绝接受不可信的证书。无论是哪一种原因,都可以将代理服务器配置为使用一个主CA证书(用于为访问的每个站点的每台主机签署有效的证书),并在计算机的可信证书库中安装该CA证书,从而解决这个问题。更多详细信息,请参阅第20章。

有些时候,客户端组件还使用除HTTP以外的协议进行通信,而拦截代理服务器却无法处理这些协议。在这些情况下,仍然可以通过使用网络嗅探器或功能挂钩工具查看并修改相关流量。Echo Mirage就是一个这样的工具,它能够注入进程并拦截套接字API调用,以便查看并修改数据,然后通过网络传送修改后的数据。可以从以下URL下载Echo Mirage:

渗透测试步骤

(1)确保代理服务器能够正确拦截浏览器扩展送出的所有流量。如有必要,使用嗅探器确定任何未正确拦截的流量。

(2)如果客户端组件使用标准的序列化方案,确保拥有解压并修改序列化数据所需的工具。如果浏览器组件使用专用编码或加密机制,则需要解译或调试该组件,对其进行全面测试。

(3)检查服务器返回的触发关键客户端逻辑的响应。通常,及时拦截并修改服务器响应能够“解锁”客户端GUI,从而轻松发现并执行复杂或多步骤特权操作。

(4)如果应用程序执行不得由客户端组件执行的任何关键逻辑或事件(如在赌博应用程序中发牌或摇骰子),这时,可以寻找执行关键逻辑和与服务器通信之间的任何联系。如果客户端在确定事件的结果时不需要与服务器进行通信,这说明应用程序肯定存在漏洞。

反编译浏览器扩展

迄今为止,在对浏览器扩展组件实施攻击时,最彻底的方法,是反编译对象、对源代码进行全面分析、修改源代码(如有必要)以改变对象的行为,然后重新编译源代码。如前所述,浏览器扩展被编译成字节码。字节码是一种由相关解释器(如Java虚拟机或Flash播放器)执行的、不依赖于特定平台的高级二进制表示形式,每种浏览器扩展技术都使用它们自己的字节码格式。因此,浏览器扩展能够在解释器本身可运行的任何平台上运行。

字节码表示形式的高级本质意味着,从理论上讲,最终可以将字节码反编译成类似于最初的源代码的内容。但是,字节码可能采用了各种防御机制,以防止反编译,或者输出非常难以理解或解释的反编译代码。

尽管字节码采取了上述防御机制,但是,在理解和攻击浏览器扩展组件时,反编译字节码仍然是首选方法。通过反编译字节码,可以查看客户端应用程序的业务逻辑、访问它的全部功能,以及有针对性地修改其行为。

下载字节码

第一步是下载要处理的可执行字节码。一般情况下,字节码会从HTML源代码(运行浏览器扩展的应用程序页面)中指定的URL加载到单独的文件中。Java applet通常使用<applet>标签加载,其他组件则使用〈object〉标签加载。例如:

某些情况下,加载字节码的URL可能并不是非常明显,因为组件可能使用不同浏览器扩展框架提供的各种包装脚本(wrapper script)进行加载。确定字节码的URL的另一种方法,是在浏览器加载浏览器扩展后,在代理服务器历史记录中查找该URL。如果采用这种方法,需要了解以下两个可能的问题。

一些代理服务器工具对代理服务器历史记录应用过滤器,以隐藏渗透测试员通常并不感兴趣的视图项目,如图像和样式表文件。如果找不到与浏览器扩展字节码有关的请求,那么应对代理服务器历史记录显示过滤器进行修改,以显示所有项目。

通常,相比于图像等其他静态资源,浏览器会在缓存中更多地存储已下载的扩展组件字节码。如果浏览器已加载某个组件的字节码,那么,即使完全刷新使用该组件的页面,浏览器也不会再次请求该组件。在这种情况下,可能需要完全清除浏览器的缓存,关闭每一个浏览器实例,然后启动新的浏览器会话,才能强制浏览器再次请求字节码。

确定浏览器扩展字节码的URL后,只需将该URL粘贴到浏览器的地址栏中,然后,浏览器会提示你将字节码文件保存到本地文件系统中。

 提示 如果已在Burp Proxy历史记录中确定了与字节码有关的请求,并且服务器的响应中包含完整的字节码(而没有引用以前缓存的副本),这时,可以将字节码直接保存到Burp内的文件中。最可靠的方法是选择响应查看器中的“标题”(Herders)选项卡,右键单击包含响应主体的下方窗格,然后从上下文菜单中选择“复制到文件”(Copy to File)。

反编译字节码

字节码通常以独立文件包的形式发布,可能需要进行解压缩才能获得单个字节码文件,然后再将其反编译成源代码。

正常情况下,Java applet打包成.jar(Java档案)文件,Silverlight对象则打包成.xap文件。这两种文件均使用zip档案格式,因此,只需用.zip扩展名重命名这些文件,然后使用任何.zip读取器就可以将它们解压缩成单个的文件。Java字节码包含在.class文件中,Silverlight字节码包含在.dll文件中。解压缩相关文件包后,需要反编译这些文件才能获得源代码。

Flash对象打包成.swf文件,在使用反编译器之前,不需要对这类文件进行解压缩处理。

实际的反编译字节码需要使用一些特定的工具,这些工具因所采用的浏览器扩展技术的类型而异,我们将在以下几节介绍这些工具。

Java工具

Java字节码可以使用称为Jad(Java反编译器)的工具反编译成Java源代码,该工具的下载地址如下:

Flash工具

Flash字节码可以反编译成ActionScript源代码。另一种更加有效的方法,是将字节码反编译成人类可读的格式,而不是将其完全反编译成源代码。

要反编译和反汇编Flash,可以使用以下工具:

Flasm——www.nowrap.de/flasm;

Flare——www.nowrap.de/flare;

SWFScan——www.hp.com/go/swfscan(此工具针对Actionscript 2和Actionscript 3)。

Silverlight 工具

Silverlight字节码可以使用一种称为.NET Reflector的工具反编译成源代码,该工具的下载地址为:

www.red-gate.com/products/dotnet-development/reflector/

分析源代码

获得组件的源代码或类似代码后,就可以采取各种方法对其实施攻击。通常,第一步是查看源代码,以了解组件的工作机制及其包含或引用的功能。以下是需要寻找的一些项目:

在客户端发生的输入确认或其他安全相关逻辑和事件;

在向服务器传送数据之前用于包装用户提交的数据的模糊或加密程序;

在用户界面中不可见,但可以通过修改组件进行解锁的“隐藏的”客户端功能;

对以前未通过解析应用程序确定的服务器端功能的引用。

通常,查看源代码可以揭示组件中的一些有趣的功能。渗透测试员希望修改或操纵这些功能,以确定潜在的安全漏洞。希望执行的操作包括:删除客户端输入确认、向服务器提交未标准化的数据、操纵客户端状态或事件,或者直接调用组件中的功能。

如以下几节所述,可通过各种方式修改组件的行为。

在浏览器中重新编译并执行

要改变组件的行为,可以对反编译得到的源代码进行修改,重新将其编译成字节码,然后在浏览器中执行修改后的组件。需要操纵关键的客户端事件,如在赌博应用程序中摇骰子时,通常首选这种方法。

要重新编译源代码,需要使用与采用的技术有关的开发者工具。

对于Java,可以使用JDK中的javac程序重新编译修改后的源代码。

对于Flash,可以使用flasm重新汇编修改后的字节码,或使用Adobe的某个Flash开发套件重新编译修改后的ActionScript源代码。

对于Silverlight,可以使用Visual Studio重新编译修改后的源代码。

将源代码重新编译成一个或多个字节码文件后,如果采用的技术需要,可能需要重新打包可分配的文件。对于Java和Silverlight,需要用修改后的字节码文件替换已解压的档案中的文件,使用zip实用程度重新压缩这些文件,然后根据需要将文件扩展名更改为.jar或.xap。

最后,需要将修改后的组件加载到浏览器中,使所做的更改在测试的应用程序中生效。可以通过多种方式达到这一目的。

如果可以在浏览器的磁盘缓存中找到包含原始可执行文件的物理文件,可以用修改后的版本替换该文件,然后重新启动浏览器。但是,如果浏览器并不对每个缓存的资源使用不同的文件,或者浏览器只是将扩展组件缓存在内存中,这种方法可能无法生效。

可以使用拦截代理服务器修改加载组件页面的源代码,并指定另一个指向本地文件系统或受控的Web服务器的URL。正常情况下,这种方法很难奏效,因为更改加载组件的域可能会违反浏览器的同源策略,而且可能需要重新配置浏览器,或采用其他方法弱化同源策略。

可以使浏览器从原始服务器重新加载组件(如5.3.4节所述),使用代理服务器拦截包含可执行文件的响应,并用修改后的版本替换消息主体。在Burp Proxy中,可以使用“从文件中粘贴”(Paste from File)上下文菜单项达到这个目的。通常,这是最简单的方法,也是最不容易遇到上述问题的方法。

在浏览器以外重新编译并执行

有些时候,并不需要在执行组件的过程中修改组件的行为。例如,一些浏览器扩展组件会确认用户提交的输入,对这些输入进行模糊处理或加密,然后将其传送至服务器。在这种情况下,可以对组件进行修改,使其对任何未经确认的输入执行必要的模糊处理或加密,并在本地输出结果。然后,可以使用代理服务器在原始组件提交经过确认的输入时拦截相关请求,并用修改后的组件输出的值替换这些请求。

要实施这种攻击,需要对在相关浏览器扩展中运行的原始可执行文件进行修改,将其更改为可以在命令行中运行的独立程序。进行修改的方式因所采用的编程语言而异。例如,在Java中,只需要实施main方法。“Java applet:可用示例”小节将提供相关示例。

使用JavaScript操纵原始组件

在某些情况下,并不需要修改组件的字节码。相反,可以通过修改HTML页面中与组件交互的JavaScript来达到目的。

通过查看组件的源代码,可以确定组件的所有可直接从JavaScript调用的公共方法,以及组件处理这些方法的参数的方式。通常,除了可以从应用程序页面调用的方法外,还存在其他一些方法;另外,还能够了解有关这些方法的参数的用途及处理方式的详细信息。

例如,组件可能会公开这样的方法:调用该方法可以启用或禁用部分可见的用户界面。使用拦截代理服务器可以编辑加载该组件的HTML页面,修改其中的JavaScrip域在其中添加一些JavaScript,以解锁被隐藏的部分界面。

渗透测试步骤

(1)使用上述技巧下载组件的字节码,解压字节码,然后将其反编译成源代码。

(2)查看相关源代码,了解组件的执行过程。

(3)如果组件包含任何可进行操纵以实现目的的公共方法,可以拦截与该组件交互的HTML响应,并在其中添加一些JavaScript,以使用输入调用相应的方法。

(4)如果组件中不包含公共方法,可以通过修改组件的源代码,重新编译修改后的代码,然后在浏览器中或作为独立的程序执行这些代码,从而达到目的。

(5)如果组件用于向服务器提交模糊或加密数据,则可以使用修改后的组件向服务器提交各种经过适当模糊处理的攻击字符串,以探查其中的漏洞,就像针对任何其他参数实施攻击一样。

字节码模糊处理

由于攻击者可轻松反编译字节码以恢复其源代码,因而人们开发出各种技巧来对字节码进行模糊处理。经过模糊处理的字节码更难以反编译,或者反编译后得到的是可能造成误导或无效的源代码,这些代码非常难以理解,不投入大量精力无法进行重新编译。以下面经过模糊处理的Java源代码为例:

常用的模糊处理技巧包括以下几种。

用没有意义的表达式(如a、b、c)代替有意义的类、方法和成员变量名称。这迫使阅读反编译代码的攻击者只有通过研究表达式的使用方法才能确定它们的用途,因此他们很难明白这些表达式的作用。

更进一步,一些模糊处理工具用new和int之类的保留关键字代替项目名称。虽然从技术上讲,这种字节码是非法的,但大多数虚拟机(VM)允许使用这种非法代码,并正常执行它们。不过,尽管反编译器能够处理非法字节码,但这样得到的源代码比前一种方法生成的源代码更加难以阅读。更重要的是,如果不投入大量精力对非法命名的数据项统一进行重命名,就不能重新编译源代码。

许多模糊处理工具删除字节码中不必要的调试和元信息,包括源文件名和行号(使桟追踪缺乏信息)、局部变量名称(使调试更麻烦)和内部类信息(使反射无法正常进行)。

增加多余的代码,以看似有用的方式建立并处理各种数据,但它们与应用程序的功能实际使用的数据并无关系。

使用跳转指令(jump instruction)对整个代码的执行路径进行令人费解的修改,致使攻击者在阅读反编译得到的源代码时无法判别执行代码的逻辑顺序。

引入非法的编程结构,如无法到达的语句和缺少return语句的代码路径。大多数VM允许在字节码中出现这种结构,但如果不更正非法代码,就无法重新编译反编码得到的源代码。

渗透测试步骤

应对字节码模糊处理的有效策略取决于所分析的源代码使用的技巧和目的。以下提供一些建议。

(1)不必完全理解源代码,只需查看组件中是否包含公共方法。哪些方法可以从JavaScript中调用,它们的签名是什么,这些内容应显而易见。可以通过提交各种输入测试上述方法的行为。

(2)如果已经使用无意义的表达式(并非编程语言保留的特殊关键字)代替类、方法和成员变量名称,可以使用许多IDE中内置的重构功能(refactoring functionality)帮助理解代码。通过研究数据的用法,可以给它们分配有意义的名称。IDE中的rename工具可以帮助完成许多工作,在整个代码库中追踪数据的用法并对每一个数据进行重命名。

(3)选择适当的选项,在模糊处理工具中再次对模糊处理后的字节码进行模糊处理,这样即可撤销许多模糊处理。Jode是一种实用的模糊处理工具,它可删除由其他模糊处理工具添加的多余代码路径,并可为数据分配唯一的名称,为理解模糊处理后的名称提供帮助。

Java applet:可用示例

下面以一个在Java applet中执行输入确认的购物应用程序为例,简要说明如何反编译浏览器扩展。

在这个示例中,提交用户请求的订购数量的表单如下所示:

以数量2提交该表单时,请提出以下请求:

如以上HTML代码所示,提交表单时,确认脚本会向一个名为CheckQuantity的Java applet传递用户提供的数量和obfpad参数的值。很明显,applet会执行必要的输入确认,并向脚本返回经过模糊处理的数量,然后脚本再将该数量提交给服务器。

由于服务器端应用程序确认订购数量为两件,因此,很明显,quantity参数会以某种形式包含我们请求的值。但是,如果我们尝试在不了解模糊算法的情况下修改该参数,攻击将会失败,因为服务器无法正确解析我们提交的经过模糊处理的值。

在这种情况下,可以使用之前介绍的方法反编译Java applet,以此了解它的工作机制。首先,需要从HTML页面的applet标签中指定的URL下载applet的字节码:

由于可执行文件没有打包成jar文件,因此,不需要解压这个文件,可以直接到下载的.class文件运行Jad:

Jad将经过反编译的源代码输出为jad文件,可以在任意文本编辑器中查看该文件:

如经过反编译的源代码所示,Jad进行了大量的反编译工作,而且该applet的源代码非常简单。使用用户提供的quantity参数和应用程序提供的obfpad参数调用doCheck方法时,applet首先确认该数量是否为介于1到50之间的有效数字。如果数字有效,它会使用URL查询字符串格式创建一个由名/值对组成的字符串,在其中包含经过确认的数量。最后,它使用应用程序提供的obfpad字符串对以上创建的字符串执行XOR操作,对该字符串进行模糊处理。这是一种相当简单而常用的方法,它通过对数据进行基本的模糊处理来防止简单的篡改操作。

关于如何反编译和分析浏览器扩展组件的源代码,我们已经介绍了各种方法。在此示例中,解析applet的最简单方法如下所示:

(1)修改doCheck方法,取消输入确认,以便于将任意字符串作为数量提交给服务器;

(2)添加一个main方法,用于从命令行执行经过修改的组件。该方法将调用经过修改的doCheck方法,并在控制台打印经过模糊处理的结果。

进行这些更改后,源代码如下所示:

经过修改的组件以任意数量(999)提供经过模糊处理的有效字符串。需要注意的是,可以在此处使用非数字输入,探查应用程序中是否存在各种基于输入的漏洞。

 提示 Jad程序以.jad扩展名保存其反编译的源代码。但是,如果希望修改并重新编译源代码,需要用.java扩展名重命名每个源文件。

最后,需要使用Java SDK自带的javac编译器重新编译源代码,然后从命令行执行经过修改的组件:

现在,经过修改的组件已对我们提交的任意数量(999)进行了必要的模糊处理。要对服务器实施攻击,只需使用有效的输入以正常方式提交订单,使用代理服务器拦截生成的请求,并用经过修改的组件提供的数量替换经过模糊处理的数量。需要注意的是,如果应用程序在每次加载订单时都发布一个新的模糊包(obfuscation pad),需要确保返还给服务器的模糊包与同时提交的用于对数量进行模糊处理的模糊包相匹配。

尝试访问

以下示例演示了上述攻击以及使用Silverlight和Flash技术的对应攻击:

http://mdsec.net/shop/154/

http://mdsec.net/shop/167/

http://mdsec.net/shop/179/

附加调试器

要了解和攻击浏览器扩展,反编译是最全面可靠的方法。但是,对于包含成千上万行代码、功能复杂的大型组件而言,观察组件的执行过程,并将其中的方法和类与界面中的关键功能进行关联,往往会更加简单。而且,采用这种方法还可以避免在解释和反编译经过模糊处理的字节码时遇到的困难。通常,只需要执行某项关键功能,更改其行为,以消除在组件中实施的控件,即可达到特定目的。

由于调度器在字节码级别运行,因此,可以使用调试器轻松控制并了解组件的执行过程。具体而言,如果可以通过反编译获得源代码,就可以在特定的代码行设置断点,并通过观察组件在执行过程中采用的代码路径来判定通过反编译获得的信息是否正确。

虽然针对所有浏览器扩展技术的高效调试器尚不成熟,但Java applet能够为调度提供有效支持。JavaSnoop是目前最高效的调度器。JavaSnoop是一款Java调试器,它能够与Jad集成,以用于反编译源代码、在应用程序中跟踪变量,并在方法中设置断点来查看和修改参数。JavaSnoop可用于直接“钩住”在浏览器中运行的Java applet(如图5-6所示),并可用于篡改方法的返回值(如图5-7所示)。

图5-6 JavaSnoop可以直接“钩住”在浏览器中运行的Java applet

图5-7 确定适当的方法后,可以使用JavaSnoop篡改方法的返回值

 注解 在加载目标applet之前,最好首先运行JavaSnoop。JavaSnoop将取消Java安全策略设置的限制,以便于针对目标执行操作。在Windows中,JavaSnoop通过向系统中的所有Java程序授予各种权限来取消限制,因此,在执行操作后,需要确保完全关闭JavaSnoop并恢复相关权限。

JSwat是另一个Java调试工具,该工具提供大量配置选项。有时,在包含许多类文件的大型项目中,最好是反编译、修改并重新编译关键的类文件,然后使用JSwat将其“热包装”到正在运行的应用程序中。要使用JSwat,需要使用JDK中的appletviewer工具启动一个applet,然后将JSwat连接到该applet。例如,可以使用以下命令:

处理Silverlight对象时,可以使用Silverlight Spy工具监视组件在运行时的执行情况。在将相关代码路径关联到用户界面中的事件时,该工具可提供很大的帮助。下载Silverlight Spy的URL如下所示:

本地客户端组件

一些应用程序需要在用户的计算机中执行基于浏览器的VM沙盒内无法执行的操作。根据客户端安全控制,以下是这类功能的一些示例:

验证用户是否装有最新的病毒扫描器;

验证代理服务器设置及其他企业配置是否有效;

集成智能卡读取器。

通常,这些操作需要使用本地代码组件,这些组件将集成本地应用程序功能和Web应用程序功能。本地客户端组件一般通过ActiveX控件传送。ActiveX控件是在浏览器沙盒以外运行的定制浏览器扩展。

由于没有对应的中间字节码,相比于其他浏览器扩展,解译本地客户端组件要困难得多。但是,我们在避开客户端控件时采用的方法对于本地客户端组件仍然适用,不过,这时需要采用一组不同的工具。以下是用于完成这个任务的一些常用工具:

OllyDbg是一个可用于遍历本地可执行代码、设置断点,并在磁盘上或在运行时对可执行文件应用补丁的Windows调试器。

IDA Pro是一个反汇编程序,它可以将大量平台上的本地可执行代码反汇编成人类可读的汇编代码。

虽然我们不会在本书中详细介绍有关逆向工程的信息,但是,如果你希望详细了解逆向工程本地代码组件及相关主题,下面是一些有用的资源:

Reversing: Secrets of Reverse Engineering, Eldad Eilam 著;

Hacker Disassembling Uncovered, Kris Kaspersky著;

The Art of Software Security Assessment, Mark Dowd、John McDonald 和 Justin Schuh 著;

Fuzzing for Software Security Testing and Quality Assurance (Artech House Information Security and Privacy), Ari Takanen、Jared DeMott和Charlie Miller著;

The IDA Pro Book: The Unofficial Guide to the World's Most Popular Disassembler, Chris Eagle著[1];

www.acm.uiuc.edu/sigmil/RevEng ;

www.uninformed.org/?v=1&a=7。

安全处理客户端数据

如前所述,由于客户端组件和用户输入不在服务器的直接控制范围内,Web应用程序的核心安全面临威胁。客户端及其提交的所有数据从本质上讲都不值得信任。

通过客户端传送数据

许多应用程序之所以存在缺陷,是因为它们通过客户端以危险的方式传送产品价格和折扣率之类的重要数据。

如果可能,应用程序应完全避免通过客户端传送这类数据。在几乎任何一种可能出现的情况下,都可以将这类数据保存在服务器上,并在必要时通过服务器端逻辑直接引用。例如,接受用户购买各种产品而提交的订单的应用程序应允许用户提交产品代码和数量,并在服务器端数据库中查询每一种产品的价格。用户没有必要向服务器提交产品价格。即使应用程序向不同的用户提供不同的价格或折扣,也不必抛弃这种模型。价格可按用户分类保存在数据库中,而折扣率则保存在用户资料或会话对象中。应用程序已经拥有计算某一特殊用户所购买的某种产品的价格所需的一切信息——在不安全的模型中,它必须(除非无法做到)将这个价格保存在一个隐藏的表单字段中。

如果开发者认为他们别无选择,只有通过客户端传送重要数据,那么应当对数据进行签名与/或加密处理以防止用户篡改。采用这种操作还必须避免两个重要的威胁。

签名或加密数据可能易受重传攻击(replay attack)。例如,如果在将价格保存到隐藏表单之前对其进行加密,攻击者就可以用一个更加便宜的产品的加密价格代替最初的产品价格。为防止这种攻击,应用程序需要在加密数据中包含足够的上下文,以防止攻击者在另一种情况下重新传送产品价格。例如,应用程序可以将产品代码和价格组合在一起,将得到的字符串单独加密,然后确认随订单提交的加密字符串是否与被订购的产品完全匹配。

如果用户知道并/或能够控制送交给他们的加密字符串的明文值,那么他们就可以实施各种密码攻击,找出服务器使用的加密密钥。之后,他们就能够用密钥加密任意值,完全避开解决方案提供的保护。

对于在ASP.NET平台上运行的应用程序而言,建议决不要将任何定制数据以及任何你不希望在屏幕上向用户显示的敏感数据保存在ViewState中。应总是激活用于启用ViewState MAC的选项。

确认客户端生成的数据

从理论上讲,客户端无法安全确认由客户端生成并且向服务器传送的数据。

可轻易避开HTML表单字段和JavaScript之类的轻量级客户端控件,无法保障服务器收到的输入的安全性。

在浏览器扩展组件中执行的控件有时更难以避开,但这种控件只能暂时阻止攻击者入侵。

使用经强化模糊处理或压缩的客户端代码增添了另一层障碍,但是,蓄意攻击者还是能够克服这些障碍。(其他领域的类似处理是使用DRM技术防止用户复制数字媒体文件。许多公司在客户端控件上投入大量资金,但每一种新型解决方案通常在不久后就被攻破。)

确认客户端生成数据的唯一安全方法是在应用程序的服务器端实施保护。客户端提交的每一项数据都应被视为危险和潜在恶意的。

 错误观点 有时候,人们认为使用任何客户端控件必然会造成不利影响。一些专业渗透测试员甚至把使用客户端控件看作是一个“重大发现”,并不检验服务器是否使用这些控件或者使用它们是否出于非安全考虑。实际上,尽管本章描述的各种攻击能够产生严重的安全警告,但在许多情况下使用客户端控件并不会造成任何安全漏洞。

客户端脚本可用于确认输入,以提高可用性,避免与服务器来回通信。例如,如果用户输入的出生日期格式不正确,通过客户端脚本向他们提出警报可提供更加无缝的使用体验。当然,应用程序必须对之后提交给服务器的数据进行重新确认。

有些时候,客户端数据确认可以与安全措施一样有效——例如,通过它防御基于DOM的跨站点脚本攻击。但是,许多时候攻击的直接目标是另一名应用程序用户,而不是服务器端应用程序。而且,利用潜在的漏洞不一定需要向服务器传送任何恶意数据。请参阅第12章和第13章了解有关这种情形的详细内容。

如前所述,有许多方法可通过客户端传送加密数据,而不会遭到破坏或重传攻击。

日志与警报

虽然应用程序采用长度限制和基于JavaScript的确认之类的机制来提高性能与可用性,但这些机制应与服务器端入侵检测防御工具组合使用。对客户端提交的数据进行确认的服务器端逻辑应认识到,客户端也采用了同样的确认机制。如果服务器收到已被客户端阻止的数据,应用程序可能会据此推断,一名用户正设法避开这种确认,因此这些数据可能是恶意的。应用程序应将异常记录到日志中,适当情况下向应用程序管理员发出实时警报,以便他们能够监控任何攻击企图,并在必要时采取适当的行动。应用程序还会主动采取防御措施,终止用户会话或者暂时冻结其账户。

 注解 有些时候,虽然用户的浏览器禁用JavaScript,但他们仍然能够使用采用JavaScript的应用程序。出现这种情况,主要是因为浏览器完全忽略了基于JavaScript的表单确认代码,提交的是用户输入的原始信息。为避免错误警报,日志与警报机制应了解这种情况会在什么地方出现,会如何发生。

小结

几乎所有的客户端-服务器应用程序都必须接受这样一个事实,即客户端组件和其中发生的所有处理过程都不像我们期待的那样值得信任。如前所述,如果应用程序使用“透明的”通信方法,那么即使经验尚浅的攻击者使用简单的工具都能轻易避开客户端执行的大多数控件。就算是应用程序对客户端数据和操作进行模糊处理,蓄意破坏的攻击者仍然能够突破这些防御。

如果确定任何通过客户端传送的数据,或确认客户端正在执行用户提交的输入,应该测试服务器如何应对避开那些控件的意外数据。许多时候,由于应用程序认为客户端执行的防御能够为其提供保护,因而面临重大威胁。

问题

欲知问题答案,请访问http://mdsec.net/wahh。

(1)通过客户端传送的数据如何阻止破坏性攻击?

(2)应用程序开发者希望阻止攻击者对登录功能发动蛮力攻击。由于攻击者可能以多个用户名为目标,开发者决定将登录尝试失败次数保存在一个加密cookie中,阻止任何失败次数超过5次的请求。有什么办法能够避开这种防御?

(3)某一应用程序包含一个执行严格访问控件的管理页面。该页面上有一个连接到另一台Web服务器的诊断功能链接,只有管理员能够访问这些功能。不执行另一种验证机制,下列哪一种(如果有的话)客户端机制可用于为诊断功能提供安全的访问控件?要选择一个解决方案,还需要了解其他信息吗?

(a)诊断功能能够检查HTTP Referer消息头,证实请求由主管理页面提交。

(b)诊断功能能够验证收到的cookie,证实其中包含访问主应用程序所需的有效会话令牌。

(c)主应用程序可在请求中的一个隐藏字段中设置一个验证令牌。诊断功能能够确认这一点,证实用户在主应用程序中有一个会话。

(4)如果一个表单字段的属性为disabled=true,那么它就不会和表单的其他内容一起提交。如何才能改变这种情况呢?

(5)应用程序可采取什么方法确保客户端执行了输入确认?

注释

[1] 本书中文版《IDA Pro权威指南(第2版)》已由人民邮电出版社出版,读者可登录图灵社区(ituring.com.cn)本书页面查看相关信息。——编者注

浙ICP备11005866号-12