利用加密 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、加密值字节数组中提取。

回顾 XOR

在我们继续之前,了解 XOR(异或)非常重要。为了帮助您回忆,这里是 XOR 的真值表:

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

CBC 解密如何使用 IV?

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

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

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',它将与原始的有效加密值结合,以冒充另一个用户。

第一步是通过将 IV 中的所有位与 first_block_plaintext 进行 XOR 运算来抵消它们,从而计算出未知值 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' 和原始加密值将导致解密结果为 "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 社区所有即将举行的活动。

查看全部