利用加密 Cookie 获取乐趣和利益

工程 | Rob Winch | 2014年1月20日 | ...

引言

开发人员经常错误地使用加密来尝试提供身份验证。例如,RESTful 应用程序可能会错误地使用加密 Cookie 来嵌入当前用户的身份。

错误在于加密只能用于保密,而签名用于验证消息的真实性。在这篇文章中,我将解释并举例说明为什么加密不能保证真实性。

如果您只想查看代码,可以随意跳到最后,其中包含一个演示此漏洞的示例 Java 应用程序。

加密 Cookie(糟糕)

假设我们想要避免在会话中查找用户,而是想将用户信息嵌入到 Cookie 中。由于 Cookie 可以被恶意用户修改,我们需要能够验证提供的 Cookie 是否是由我们的应用程序服务器创建的。

为了防止用户篡改 Cookie,我们错误地决定使用AES 加密CBC 模式来加密 Cookie,而不是对 Cookie 进行签名。我们的 Cookie 正确加密(但错误地未签名)如下所示

Cookie = Base64String( IV, aes_cbc(k, IV, plainText) )

这样

  • **Base64String** - 连接每个 byte[],然后返回连接的 byte[] 的 Base64 字符串
  • **k** - 是只有我们的服务器知道的密钥
  • **IV** - 是随机生成的初始化向量
  • **aes_cbc** - 使用提供的 IV 使用 AES/CBC 加密 plainText
  • **plainText** - 格式为“username=winch&firstName=Rob&lastName=Winch”

**注意**:将 IV 与加密文本一起以明文形式包含在内是安全且常见的做法。由于 IV 是固定数量的字节,因此可以轻松地从组合的 IV,encrypted_value byte[] 中提取它。

异或运算回顾

在我们进一步讨论之前,重要的是要理解异或运算 (XOR)。为了复习您的记忆,以下是 XOR 的真值表

A B 输出
0 0 0
0 1 1
1 0 1
1 1 0

CBC 解密如何使用 IV?

为了理解我们如何模拟另一个用户,我们首先需要了解一些 AES/CBC 的工作原理。AES 是一个分组密码,这意味着我们的消息被分成固定大小的块,然后对每个块执行操作。

在解密 AES/CBC 时,第一个块的解密值与 IV 进行异或运算。例如,以下情况成立。

decrypt(k, encrypted_first_block) XOR IV = plaintext_first_block

为了更好地理解,让我们来看一个具体的例子。假设以下情况成立

  • decrypt(k, encrypted_first_block) 为 11011101
  • IV 为 10101010

**注意**:我们的示例通过使用 8 位而不是实际的 128 位块大小来简化。这使得人类更容易理解。

这意味着我们的 plaintext_first_block 将是 01110111(在ASCII 中为“w”)。我们的工作如下所示

     decrypt(k, encrypted_first_block)
 XOR IV
 ------------
     plaintext_first_block
 
    11011101       
XOR 10101010
------------
    01110111 // "w" ASCII

修改解密值

根据以上信息,我们可以修改解密值。具体来说,给定

  • 一个有效的加密值
  • 相应的 IV
  • 相应的明文

我们可以计算一个名为 IV' 的修改后的 IV,它将与原始有效加密值组合以模拟另一个用户。

第一步是通过用 first_block_plaintext 与 IV 进行异或运算来消除 IV 中的所有位,从而计算 decrypt(k, encrypted_first_block) 的未知值。我们的工作如下图所示

     IV
 XOR plaintext_first_block
 ------------
     decrypt(k, encrypted_first_block)
 
     10101010       
 XOR 01110111
 ------------
     11011101

最后一步是通过执行 decrypt(k, encrypted_first_block) XOR desired_plaintext_first_block 来计算 IV'。同样,我们的工作如下图所示

     decrypt(k, encrypted_first_block)
 XOR desired_plaintext_first_block
 ------------
     IV'
 
     11011101       
 XOR 01100001 // "a" ASCII
 ------------
     10111100

我们现在可以验证提供 IV'(而不是 IV)以及原始加密值将导致“a”而不是“w”。

     decrypt(k, encrypted_first_block)
 XOR IV'
 ------------
     desired_plaintext_first_block
 
    11011101       
XOR 10111100
------------
    01100001 // This is "a" ASCII

这表明,如果我们提供 IV'(而不是 IV)以及原始加密值,它将被解密为“a”。

模拟另一个用户

既然我们已经了解了如何创建修改后的 IV 来随意更改我们的加密值,那么让我们探讨一下这如何应用于我们作为用户进行身份验证,然后修改我们的加密 Cookie 来模拟另一个用户。

修改解密值部分,我们提到在执行漏洞利用之前我们需要一些信息。让我们看看如何使用加密 Cookie 获取漏洞利用所需的信息

  • **一个有效的加密值** - 加密值在 Cookie 中传输,任何拥有有效帐户的人都可以查看
  • **相应的 IV** - IV 在 Cookie 中传输,任何拥有有效帐户的人都可以查看
  • **相应的明文** - 为简单起见,假设恶意用户通过观察 Cookie 名称对应于开源框架来发现 Cookie 的格式。然后通过我们身份验证的用户知识和研究开源框架的代码来计算格式。

现在我们有了必要的信息,并且了解了如何修改加密值,很容易看出我们可以模拟任何我们想要的用户。只要我们拥有一个有效的帐户,我们就可以创建一个 IV',将加密 Cookie 中的用户名更改为我们选择的所需用户。

源代码

不相信这个漏洞?通过运行github 上的示例项目来查看演示。要运行该示例,请将其作为 Maven 项目导入到您最喜欢的 IDE 中,然后运行demo.Main类。

您将观察到我们以“winch”身份进行身份验证,但能够修改加密 Cookie 以模拟名为“admin”的用户。

结论

我希望此时您已经相信加密并非提供身份验证的有效方法。相反,我们需要确保cookie已签名。一种解决方案是使用带身份验证的加密,它提供机密性、完整性和身份验证。

请记住,仅仅对我们的cookie进行签名并不能保证其安全。还存在其他攻击媒介,例如重放攻击,必须解决这些问题才能使解决方案安全。

我们必须认识到,安全措施难以实现,不应自行实施,甚至不应由少数个人实施。相反,安全措施最好在能够相互检查错误的社区中实施。

获取Spring新闻通讯

通过Spring新闻通讯保持联系

订阅

领先一步

VMware提供培训和认证,以加速您的进步。

了解更多

获取支持

Tanzu Spring在一个简单的订阅中提供OpenJDK™、Spring和Apache Tomcat®的支持和二进制文件。

了解更多

即将举行的活动

查看Spring社区中所有即将举行的活动。

查看全部