Heartbleed 漏洞利用怎么可能?

信息安全 tls openssl 脆弱性 心血来潮
2021-08-26 00:56:35

我已经阅读了 Heartbleed OpenSSL 漏洞并理解了这个概念。但是我不明白的是我们传递 64k 作为长度并且服务器返回 64kb 的随机数据的部分,因为它不检查我们是否真的传递了 64kb 的回显消息或 1 个字节。

但是服务器上的进程怎么可能从 RAM 中返回 64kb 的随机数据呢?

难道操作系统不应该阻止对真实 RAM 的访问,并且只允许访问一个进程无法访问其他进程的内存内容的虚拟内存吗?

OpenSSL 是否在内核模式下运行,因此可以访问所有 RAM?

如果一个进程试图访问它没有明确分配的任何内存,我预计会出现分段错误。我可以理解从运行 OpenSSL 程序本身的进程中获取 64kb 的随机数据,但我看不到它如何甚至可以看到服务器的完整 RAM 以便能够将其发送回客户端。

更新: @paj28 的评论,是的,正是虚假信息让我对此产生了怀疑。正如你所说,即使是官方的 heartbleed.com 咨询也以一种误导的方式来表达它(尽管我会说他们这样做是因为它的目的是为更广泛的受众而不仅仅是我们技术人员,他们希望保持简单)

作为参考,以下是 heartbleed.com 的陈述方式(强调我的):

Heartbleed 漏洞允许 Internet 上的任何人读取受 OpenSSL 软件易受攻击版本保护的系统内存。

对于任何暗示虚拟/物理机完整 RAM 的技术人员。

2个回答

@paj28 的评论涵盖了要点。OpenSSL 是一个共享库,因此它在与使用它的进程相同的用户模式地址空间中执行。它根本看不到其他进程的内存;任何其他建议都是错误的。

然而,OpenSSL 使用的内存——可能靠近 Heartbleed 过度读取的缓冲区的东西——充满了敏感数据。具体来说,它可能同时包含任何最近或即将进行的传输的密文和明文。如果您攻击服务器,这意味着您将看到其他人发送到服务器的消息,以及服务器对这些消息的响应。这是窃取会话令牌和私人信息的好方法,而且您可能还会捕获某人的登录凭据。OpenSSL 存储的其他数据包括对称加密密钥(用于通过 TLS 进行批量数据加密和完整性)和私钥(用于证明服务器的身份)。窃取这些信息的攻击者可以实时窃听(甚至修改)受损的 TLS 通信,或成功冒充服务器,

现在,Heartbleed 有一件奇怪的事情让它比你想象的更糟。通常,如果您尝试从进程中的任意堆地址开始读取 64k 数据,那么很有可能会遇到未分配的内存地址(虚拟内存没有任何支持,因此无法使用)迅速地。进程地址空间中的这些漏洞非常常见,因为当进程释放它不再需要的内存时,操作系统会回收该内存以便其他进程可以使用它。除非您的程序像筛子一样泄漏内存,否则内存中除了当前正在使用的数据之外通常没有那么多数据。尝试读取未分配的内存(例如,尝试访问已释放的内存)会导致读取访问冲突(在 Windows 上)/分段错误(在 *nix 上),这将使程序崩溃(并且在它可以执行任何操作(例如发回数据)之前就崩溃了)。这仍然是可利用的(作为拒绝服务攻击),但它并没有让攻击者获取所有数据那么糟糕。

使用 Heartbleed,该过程几乎从未崩溃。事实证明,OpenSSL 显然认为平台内存管理库太慢(或其他原因;我不会试图证明这个决定的合理性),预先分配了大量内存,然后使用自己的内存管理功能在那之内。这意味着几件事:

  • 当 OpenSSL “释放”内存时,就操作系统而言,它实际上并没有被释放,因此内存仍然可供进程使用。OpenSSL 的内部内存管理器可能认为内存没有分配,但就操作系统而言,使用 OpenSSL 的进程仍然拥有该内存。
  • 当 OpenSSL “释放”内存时,除非它在调用其函数之前明确擦除数据,否则free该内存将保留它在“释放”之前的任何值。这意味着可以读取大量实际上尚未使用的数据。
  • OpenSSL 使用的内存堆是连续的;就操作系统而言,其中没有任何差距。因此,缓冲区过度读取不太可能进入未分配的页面,因此不太可能崩溃。
  • OpenSSL 的内存使用具有非常高的局部性——也就是说,它集中在一个相对较小的地址范围内(预分配的块)——而不是在操作系统内存分配器的一时兴起的情况下分布在地址空间中。因此,读取 64KB 的内存(这不是很多,即使在 32 位进程的典型 2GB 范围旁边,更不用说 64 位进程的巨大范围)可能会获得大量数据,即当前(或最近)正在使用,即使该数据驻留在一堆所谓的单独分配的结果中。

如果一个进程试图访问它没有明确分配的任何内存,我预计会出现分段错误

这就是误解所在。

任何中断的内存访问都可能导致分段错误,但实际上如果请求的内存地址位于当前进程的地址空间内(例如,您刚刚释放的变量),这极不可能。

这就是为什么您不应该依赖分段错误来查找内存访问错误的原因!