安全简报: 视图状态安全


在 Web 应用程序中有效管理用户状态需要在性能、可扩展性、可维护性和安全性之间取得精妙的平衡。在管理客户端上储存的用户状态时,安全考虑就显得额外重要。我的一个同事曾经说过,处理客户端的状态数据就像把蛋筒冰激淋交给一个 5 岁的孩子:您可以把冰激淋拿回来,但是您绝对不能期望拿回来的时候冰激淋的形状还像给出去的时候一样!

  在这个月的专栏中,我们会围绕 ASP.NET 应用程序中的客户端状态管理来探讨一些安全隐患,特别会关注视图状态安全。(请注意:本文假设您熟悉有关 ASP.NET 视图状态的概念。)。

  如果您认为应用程序的视图状态中存储的任何数据都不值得保护,请再仔细考虑一下。敏感信息甚至会在您尚未察觉的时候进入视图状态中。即使您非常警惕,采取了防止敏感信息通过视图状态丢失的措施,攻击者仍然能够篡改视图状态,甚至会给您和您的用户带来更大的麻烦。幸运的是,ASP.NET 带有一些内置的防御组件,用于防御这些攻击。让我们看看如何正确使用这些防御组件。

  威胁 1:信息泄露

  在 Microsoft,开发团队使用 STRIDE 模式对威胁进行分类。STRIDE 是首字母缩写,分别代表:

  假冒

  篡改

  否认

  信息泄露

  拒绝服务

  提升权限

  视图状态安全所涉及到的两个主要 STRIDE 类别是信息泄露和篡改(成功的篡改攻击可能会导致提升权限,我们会在后面详细进行讨论)。信息泄露是这些威胁中比较容易说明的,因此我们就先从它开始讨论。

  有关视图状态的一个最令人遗憾、最常见的误解就是,它经过了加密或其他方式处理,无法被用户读取。毕竟,视图状态字符串看起来肯定不是可分解的:

<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwULLTE2MTY2ODcyMjkPFgIeCHBhc3N3b3JkBQlzd29yZGZpc2hkZA==" />

  然而,这个字符串只是 base64 编码的,没有采用任何强大的密码算法。我们能够通过受限对象序列化 (LOS) 格式化程序类 System.Web.UI.LosFormatter 轻松对该字符串进行解码和反序列化:

LosFormatter formatter = new LosFormatter(); 
object viewstateObj = formatter.Deserialize("/wEPDwULLTE2MTY2ODcyMjkPFgIeCHBhc3N3b3JkBQlzd29yZGZpc2hkZA==");

  快速看一下调试器(请参见图 1),就能发现反序列化的视图状态对象实际上是一系列 System.Web.UI.Pair 对象,以包含值“password”和相应字符串值“swordfish”的 System.Web.UI.IndexedString 对象结尾。

  图 1 调试器揭示的秘密视图状态数据

  如果您不打算自己去费劲编写代码来反序列化视图状态对象,Internet 上有几种很好用的视图状态解码器可供免费下载,包括 Fritz Onion 的 ViewState Decoder 工具,网址为:alt.pluralsight.com/tools.aspx。

  加密视图状态

  在“The Security Development Lifecycle: SDL: A Process for Developing Demonstrably More Secure Software”(Microsoft Press,2006)一书中,Michael Howard 和 Steve Lipner 讨论了可缓解 STRIDE 威胁的技术。图 2 显示了威胁类型及相关的缓解技术。

  图 2 缓解 STRIDE 威胁的技术

威胁类型 缓解技术
假冒 身份验证
篡改 完整性
否认 认可服务
信息泄露 机密性
拒绝服务 可用性
提升权限 授权

  因为我们要处理针对视图状态中存储的数据的信息泄漏威胁,因此我们需要应用机密性缓解技术。在这种情况下,最有效的机密性缓解技术就是加密。

  ASP.NET 2.0 版有一个用于为视图状态加密的内置功能:ViewStateEncryptionMode 属性。该功能可通过一个页面指令或应用程序的 web.config 文件来启用:

<%@ Page ViewStateEncryptionMode="Always" %>

  或

<configuration> 
  <system.web> 
   <pages viewStateEncryptionMode="Always">

  ViewStateEncryptionMode 有三个可能的值:Always(视图状态始终加密)、Never(视图状态从不加密),和 Auto(只有在页面的某个控件显式要求时,视图状态才加密)。Always 和 Never 值都非常容易理解,但是对于 Auto 值,需要多一些说明。

  如果服务器控件坚持要求敏感信息进入其页面的视图状态,控件可以通过调用 Page.RegisterRequiresViewStateEncryption 方法请求页面加密视图状态(请注意,在这种情况下,整个视图状态都会被加密,而不仅仅是与发出请求的控件相对应的视图状态):

public class MyServerControl : WebControl 
{ 
  protected override void OnInit(EventArgs e) 
  { 
   Page.RegisterRequiresViewStateEncryption(); 
   base.OnInit(e); 
  } 
  ... 
}

  但是,有一点需要特别注意。该方法被命名为 RegisterRequiresViewStateEncryption 而不是像 EnableViewStateEncryption 这样的名称,是因为页面可以选择忽略请求。如果页面的 ViewStateEncryptionMode 设置为 Auto(或 Always),控件的请求将会被批准,视图状态会被加密。如果 ViewStateEncryptionMode 设置为 Never,控件的请求会被忽略,视图状态不受保护。

  如果您是一名控件开发人员,请务必注意这一点。您应当考虑将可能的敏感信息排除在视图状态之外(这始终是正确的想法)。在极端情况下,如果这样做不可行,您可以考虑忽略控件的 SaveViewState 和 LoadViewState 方法,手动对视图状态进行加密和解密。

  服务器场注意事项

  在单服务器环境中,只启用 ViewStateEncryptionMode 就足够了,但是在服务器场环境中,还有其他工作要做。对称加密算法(与 ASP.NET 用来加密视图状态的方法类似)需要一个密钥。您可以在 web.config 文件中显式指定密钥,或者让 ASP.NET 为您自动生成密钥。同样,在单服务器环境中,让框架来处理密钥的生成是没有问题的,但是在服务器场中就不行。每个服务器都会生成自己唯一的密钥,因此在不同服务器之间的负载平衡请求会失败,因为解密密钥不匹配。

  您可以显式设置在应用程序的 web.config 文件的 machineKey 元素中使用的加密算法和密钥:

<configuration> 
  <system.web> 
   <machineKey decryption="AES" decryptionKey="143a...">

  对于加密算法,您可以选择 AES(默认值)、DES 或 3DES。在这些算法中,DES 被 Microsoft SDL 加密标准明确禁止,而 3DES 则强烈建议您不要使用。我建议您坚持使用 AES 以获得最大程度的安全保护。

  选择算法之后,您需要创建一个密钥。但是,请记住,系统的安全强度取决于该密钥的强度。请勿使用宠物的名字、对您非常重要的人的生日,或任何容易猜测到的值!您需要使用一个密码强度高的随机数字。以下是一段代码,使用 .NET RNGCryptoServiceProvider 类创建符合 machineKey 元素格式要求(仅十六进制字符)的密钥:

RNGCryptoServiceProvider csp = new RNGCryptoServiceProvider(); 
byte[] data = new byte[24]; 
csp.GetBytes(data); 
string value = String.Join("", BitConverter.ToString(data).Split('-'));

  至少,您应该为密钥生成 16 字节随机值,这是 SDL 加密标准所允许的最小值。在 Microsoft .NET Framework 3.5 和更早版本中,AES 支持的密钥最大长度是 24 个字节(48 个十六进制字符),在 .NET Framework 4 中则是 32 个字节(64 个十六进制字符)。DES 支持的密钥最大长度仅有 8 个字节,而 3DES 则是 24 个字节,无论框架的版本是多少。同样,我建议您避免使用这些算法,只使用 AES。

  威胁 2:篡改

  篡改是另外一种严重威胁。您可能会认为,防止攻击者窥探视图状态的加密防御方法也可以防止视图状态不被更改,这是错误的。加密并不能提供针对篡改的防御:即使数据被加密,攻击者仍有可能在加密的数据中翻转位。

  请看一下图 2。要缓解篡改威胁,我们需要使用数据完整性技术。最好的选择仍是使用一种加密形式,而且 ASP.NET 中也提供了相应功能。但是,我们将使用一种哈希算法为数据创建消息身份验证代码 (MAC),而不是使用对称算法来加密数据。

  ASP.NET 应用 MAC 的功能叫做 EnableViewStateMac,而且就像 ViewStateEncryptionMode 一样,您可以通过页面指令或应用程序的 web.config 文件来应用它:

<%@ Page EnableViewStateMac="true" %>

  或

<configuration> 
  <system.web> 
   <pages enableViewStateMac="true">

  要了解 EnableViewStateMac 的工作原理,让我们先从更高一级的角度来看看当视图状态 MAC 未 启用时,视图状态是如何写入页面的:

  页面的视图状态和所有参与的控件都被收集到一个状态图形对象中。

  状态图形被序列化为二进制格式。

  序列化的字节数组被编码为 base64 字符串。

  base64 字符串被写入到页面的 __VIEWSTATE 窗体值。

  如果启用了视图状态 MAC,在上文的步骤 2 和步骤 3 之间还有三个额外的步骤:

  页面的视图状态和所有参与的控件都被收集到一个状态图形对象中。

  状态图形被序列化为二进制格式。
a. 个密钥值附加到序列化的字节数组中。
b. 新的序列化字节数组计算出一个加密哈希值。
c. 哈希值被附加到序列化字节数组的结尾。

  序列化的字节数组被编码为 base64 字符串。

  base64 字符串被写入到页面的 __VIEWSTATE 窗体值。

  无论该页面何时发送回服务器,页面代码都能通过接收传入的状态图形数据(由 __VIEWSTATE 值反序列化)、添加相同的密钥值,并重新计算哈希值,来验证传入的 __VIEWSTATE。如果重新计算出的新哈希值与传入的 __VIEWSTATE 结尾的哈希值相匹配,视图状态即被认定为有效并进行处理(请参见图 3)。否则,视图状态即被认定为已经过篡改,并引发异常。

  图 3 应用消息身份验证代码 (MAC)

  此系统的安全性取决于密钥值的秘密性。该值始终存储在服务器的内存或配置文件中(稍后将对此进行详细介绍),从不写入页面。不知道该密钥,攻击者就无法计算出有效的视图状态哈希值。

  从理论上说,如果具备足够的计算能力,攻击者可以对密钥进行反向工程:攻击者具备有关计算得到的哈希值以及相应的纯文本的知识,并且对于哈希算法,没有过多的选项可供选择。他只需尝试所有可能的密钥值,为已知纯文本和当前密钥重新计算哈希值,并将结果与已知的哈希值进行比较。只要这两个值匹配,他就知道找到了正确的密钥值,可以任意攻击系统。唯一的问题就是可能值的完全数量:默认的密钥大小为 512 位,这意味着有 2 的 512 次方种不同的可能性,这是一个很大的数字,通过暴力攻击完全不可能实现。

  利用无 MAC 视图状态

  EnableViewStateMac 的默认值为 true,因此保护您的应用程序与将它不设为 false 一样简单。遗憾的是,有一些文档对于 EnableViewStateMac 的性能影响提出了误导信息,一些 Web 站点鼓励开发人员禁用视图状态 MAC,以提高应用程序的性能。即使关于 PagesSection.EnableViewStateMacProperty 的 MSDN 联机文档也存在这方面的错误:“如果性能是您的首要考虑因素,不要将 EnableViewStateMac 设置为 true。”不要采纳这个建议!(希望在您阅读本文时,上述文档已做更新,更好地反映了安全性因素。)

  任何页面如果禁用了视图状态 MAC,就容易受到针对 __VIEWSTATE 参数的跨站点脚本攻击。这种攻击的首个概念验证是由 Trustwave 的 David Byrne 开发的,并由 Byrne 及其同事 Rohini Sulatycki 在 2010 年 2 月的华盛顿 Black Hat 会议上演示。要执行这个攻击,攻击者要创建一个视图状态图形,将要执行的恶意脚本代码设置为页面窗体元素的 innerHtml 属性的常量值。这个 XML 格式的视图状态图形看起来如图 4 中所示。

  图 4 用于视图状态 MAC 攻击的 XML 代码

<viewstate> 
 <Pair> 
  <Pair> 
   <String>...</String> 
   <Pair> 
    <ArrayList> 
     <Int32>0</Int32> 
     <Pair> 
      <ArrayList> 
       <Int32>1</Int32> 
       <Pair> 
        <ArrayList> 
         <IndexedString>innerhtml</IndexedString> 
         <String>...malicious script goes here...</String> 
        </ArrayList> 
       </Pair> 
      </ArrayList> 
     </Pair> 
    </ArrayList> 
   </Pair> 
  </Pair> 
 </Pair> 
</viewstate>

  攻击者然后对恶意视图状态进行 base-64 编码,并将该字符串作为一个 __VIEWSTATE 查询字符串参数的值添加到易受攻击的页面。例如,如果有人知道站点 www.contoso.com 的页面 home.aspx 禁用了视图状态 MAC,攻击 URI 将会是 http://www.contoso.com/home.aspx?__VIEWSTATE=/w143a...

  剩下的就是欺骗一个潜在的受害者打开此链接。然后,页面代码将通过传入的 __VIEWSTATE 查询字符串参数反序列化视图状态,并将恶意脚本编写为窗体的 innerHtml。当受害者打开此页面时,攻击者的脚本会立即在受害者的浏览器内使用受害者的凭据执行。

  这种攻击尤其危险,因为它完全绕过了所有常见的跨站点脚本 (XSS) 防御。Internet Explorer 8 中的 XSS 筛选器不会阻止它。ASP.NET 的 ValidateRequest 功能会阻止一些常见的 XSS 攻击平台,但是它不会对传入的视图状态进行反序列化和分析,所以在这种情况下也不起作用。Microsoft Anti-Cross Site Scripting (Anti-XSS) Library(现在是 Microsoft Web Protection Library 的一部分)比 ValidateRequest 在防御 XSS 方面更有效,但是,无论是 Anti-XSS Library 的输入净化功能,还是它的输出编码功能都无法防御这种攻击。唯一有效的防御措施就是确保视图状态 MAC 始终应用到了所有页面。

  更多服务器场注意事项

  与 ViewStateEncryptionMode 相类似,在服务器场环境中部署应用程序时,对 EnableViewStateMac 有特别要注意的地方。用于视图状态哈希值的秘密值必须在场中的所有服务器上一致,否则视图状态验证会失败。

  您可以在与指定视图状态加密密钥和算法相同的位置(web.config 文件的 machineKey 元素)指定要使用的验证密钥和 HMAC 算法:

<configuration> 
  <system.web> 
   <machineKey validation="AES" validationKey="143a...">

  如果您的应用程序构建在 .NET Framework 3.5 或更早版本上,可以选择 SHA1(默认值)、AES、MD5 或 3DES 作为 MAC 算法。如果您运行的是 .NET Framework 4,也可以从 SHA-2 系列中选择 MAC:HMACSHA256、HMACSHA384 或 HMACSHA512。在这些算法中,MD5 被 SDL 加密标准明确禁止,而 3DES 则强烈建议您不要使用。也不建议您使用 SHA1,但是对于 .NET Framework 3.5 及更早版本的应用程序,它是您的最佳选择。.NET Framework 4 应用程序则一定要使用 HMACSHA512 或 HMACSHA256 作为验证算法。

  选择完 MAC 算法后,您还需要手动指定验证密钥。要记得使用密码强度高的随机数字:如果必要,您可以参考先前指定的密钥生成代码。您应当为 HMACSHA384 或 HMACSHA512 使用至少 128 个字节的验证密钥,为其他算法使用至少 64 个字节的密钥。

  您不能隐藏易受攻击的视图状态

  与可隐藏在服务器端代码深处的、易受攻击的文件权限或数据库命令不同,只要通过查找就可以找到易受攻击的视图状态。如果攻击者想要测试某个页面,以查看其视图状态是否受到保护,他只需向该页面发出请求,将 base-64 编码的视图状态值从 __VIEWSTATE 窗体值中拉出即可。如果 LosFormatter 类能够成功反序列化该值,则该值没有加密。要确定是否应用了视图状态 MAC,比较复杂,但并不太难。

  MAC 始终应用在视图状态值的末尾,并且因为对于任何给定的哈希算法,哈希值的大小是恒定的,因此要确定是否存在 MAC 就相当容易。如果使用了 HMACSHA512,MAC 将会是 64 个字节;如果使用了 HMACSHA384,MAC 将会是 48 个字节;如果使用了任何其他算法,MAC 会是 32 个字节。如果您能够将 base-64 解码的视图状态值末尾的 32、48 或 64 个字节剥离,然后通过 LosFormatter 将其中的任意一个反序列化为与之前相同的对象,则应用了视图状态 MAC。如果这些裁切过的视图状态字节数组无法成功反序列化,则没有应用视图状态 MAC,页面易受攻击。

  Casaba Security 为开发人员提供了一个名为 Watcher 的免费工具,能够帮助自动化执行此项测试。Watcher 是 Eric Lawrence 的 Fiddler Web 调试代理工具的插件,通过被动分析流经代理的 HTTP 流量来发挥作用。它能够标记经过的任何潜在易受攻击的资源,例如,带有缺少 MAC 的 __VIEWSTATE 的 .aspx 页面。如果您尚未在测试流程中使用 Fiddler 和 Watcher,强烈建议您试一试。

  总结

  视图状态安全保护绝不容忽视,尤其考虑到最近刚刚演示过的新视图状态篡改攻击。建议您充分利用 ASP.NET 内置的 ViewStateEncryptionMode 和 EnableViewStateMac 安全机制。


« 
» 
快速导航

Copyright © 2016 phpStudy | 豫ICP备2021030365号-3