# 十八、内存

> 作者：Peter Yaworski

> 译者：[飞龙](https://github.com/)

> 协议：[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/)

## 描述

缓冲区溢出是一个场景，其中程序向缓冲区或内容区域写入数据，写入的数据比实际分配的区域要多。使用冰格来考虑的话，你可能拥有 12 个空间，但是只想要创建 10 个。在填充格子的时候，你添加了过多的水，填充了 11 个位置而不是 10 个。你就溢出了冰格的缓存区。

缓冲区溢出在最好情况下，会导致古怪的程序行为，最坏情况下，会产生严重的安全漏洞。这里的原因是，使用缓冲区移除，漏洞程序就开始使用非预期数据覆盖安全数据，之后会调用它们。如果这些发生了，覆盖的代码会是和程序的预期完全不同的东西，这会产生错误。或者，恶意用户能够使用移除来写入并执行恶意代码。

这里是来自 Apple 的一个图片：

![](img/18-0-1.jpg)

这里第一个例子展示了可能的缓冲区溢出。`strcpy`接受字符串`Larger`，并将其写入到内存，无论分配的可用空间（白色格子），以及将其写入非预期的内容中（红色格子）。

### 越界读取

除了越过分配的内容写入数据之外，另一个漏洞时越过内容边界读取数据。这是一类缓冲区溢出，因为内容被越界读取，这是缓存区不允许的。

越界读取数据漏洞的一个著名的近期示例，是 OpenSSL Heartbleed 漏洞，在 2014 年 4 月发现。在发现的时候，大约 17%（500K）的互联网安全服务器，由可信授权机构颁发证书，被认为存在此漏洞。

Heartbleed 可以利用来盗取服务器的私钥，回话数据，密码，以及其他。它通过向服务器发送“Heatbleed 请求”消息来执行，服务器会向请求者发送相同信息。消息包含长度参数。那些漏洞服务器会基于长度参数为消息分配内存，而不验证消息的真实大小。

因此，Heartbleed 消息通过发送小型消息以及较大的长度参数来利用，存在漏洞的接受者会读取额外数据，这超出了为消息分配的内存长度。这里是来自维基百科的图片：

![](img/18-0-2.jpg)

虽然缓冲区溢出需要更详细的分析，读取越界和 Heartbleed 超出了本书的范围。如果你对它们感兴趣，这里是一些不错的资源：

+ [Apple 的文档](https://developer.apple.com/library/mac/documentation/Security/Conceptual/SecureCodingGuide/Articles/BufferOverflows.html)

+ [维基百科：缓冲区溢出词条](https://en.wikipedia.org/wiki/Buffer_overflow)

+ [维基百科：NOP 垫](https://en.wikipedia.org/wiki/NOP_slide)

+ [OWASP：缓冲区溢出](https://www.owasp.org/index.php/Buffer_Overflow)

+ [`heartbleed.com`](http://heartbleed.com)

### 内存截断

内存截断是一种技巧，用于通过使代码执行一些不常见或者非预期的行为，来发现漏洞。它的效果类似于缓冲区溢出，其中内容在不该暴露的时候暴露了。

一个例子是空字节注入。这发生在提供了空字节`%00`或者十六进制的`0x00`，并导致接收程序的非预期行为时。在 C/C++，或低级编程语言中，空字节表示字符串的末尾，或者字符串的终止符。这可以告诉程序来立即停止字符串的处理，空字节之后的字节就被忽略了。

当代码依赖字符串长度时，它的影响力十分巨大。如果读取了空字节，并停止了处理，长度为 10 的字符串就只剩 5 了。例如：

```
thisis%00mystring
```

这个字符串的长度应该为 15，暗示如果字符串以空字节终止，它的长度为 6。这对于管理自己的内存的低级语言是有问题的。

现在，对于 Web 应用，当 Web 应用和库、外部 API 以及其它用 C 写成的东西交互的时候，这就有关系了。向 URL 传入`%00`可能使攻击者操作更广泛服务器环境中的 Web 资源。尤其是当编程语言存在问题的时候，例如 PHP，它是使用 C 语言编写的。

> OWASP 链接

> 查看 [OWASP 缓冲区溢出](https://www.owasp.org/index.php/Buffer_Overflows)，[OWASP 为缓冲区覆盖和溢出复查代码](https://www.owasp.org/index.php/Reviewing_Code_for_Buffer_Overruns_and_Overflows)，[OWASP 检测缓冲区溢出](https://www.owasp.org/index.php/Testing_for_Buffer_Overflow)，[OWASP 检测堆溢出](https://www.owasp.org/index.php/Testing_for_Heap_Overflow)，[OWASP 检测栈溢出](https://www.owasp.org/index.php/Testing_for_Stack_Overflow)，[OWASP 嵌入空字符](https://www.owasp.org/index.php/Embedding_Null_Code)。

## 示例

### 1\. PHP`ftp_genlist()`

难度：高

URL：无

报告链接：`https://bugs.php.net/bug.php?id=69545`

报告日期：2015.5.12

奖金：$500

描述：

PHP 编程语言使用 C 语言写成，C 语言自己管理内存。像上面描述的那样，缓冲区溢出允许恶意用户写入应该为不可访问的内存，并可能执行远程代码。

这里，FTP 扩展 的`ftp_genlist()`函数允许溢出，或者发送多于 ~4293MB 的数据，它们会被写入到临时文件中。

这使得分配的缓冲区太小，而不能存放写入临时文件的数据，在将文件内容加载回内存时，这会造成堆溢出。

> 重要结论

> 缓冲区溢出是非常古老，知名的漏洞，但是在处理自己管理内存的应用时，还是很普遍的，特别是 C 和 C++。如果你发现，你正在处理基于 C 语言（PHP 用它编写）的 Web 应用，缓冲区溢出是一个明显的可能性。但是，如果你刚起步，可能你值得花费更多时间，来寻找和漏洞相关的简单注入，在更有经验时，再返回到缓冲区溢出。

### 2\. Python Hotshot 模块

难度：高

URL：无

报告链接：`http://bugs.python.org/issue24481`

报告日期：2015.7.20

奖金：$500

描述：

像 PHP 一样，Python 编程语言也是用 C 编写的，它在之前提到过，自己管理内存。Python Hotshot 模块是一个现有 profile 模块的替代品，并且几乎都是用 C 编写，比现有的 profile 模块产生一些更微小的性能影响。但是 2015 年 7 月，该模块中发现了缓冲区溢出漏洞，和尝试将字符串从一个内容位置复制到另一个的代码有关。

本质上，这个漏洞的代码叫做`memcpy`方法，它将内容从一个地方复制到另一个地址，接受要复制的字节数。像这样：

```c
memcpy(self->buffer + self->index, s, len);
```

这个方法接受 3 个参数，`str`，`str2`和`n`。`str`是目标，`str2`是要复制的来源，`n`是要复制的字节数。这里，它们对应`self->buffer + self->index`，`s`和`len`。

这里，漏洞实际上是，`self->buffer`总是固定长度的，但是`s`可以为任意长度。

因此，在执行`copy`函数时（就像上面的 Apple 图表那样），`memcpy`函数忽视了目标区域的真实大小，因此造成了溢出。

> 重要结论

> 我们现在查看了两个函数的例子，它们的不正确实现都收到了缓冲区溢出的影响，`memcpy`和`strcpy`。如果我们知道某个站点或者应用依赖 C 或者 C++，我们就可以遍历还语言的源代码库（使用类似`grep`的东西），来寻找不正确的实现。

> 关键是寻找这样的实现，它向二者之一传递固定长度的变量作为第三个函数，对应被分配的数据长度，在数据复制时，它实际上是变量的长度。

> 但是，像之前提到的那样，如果你刚刚起步，可能你需要放弃搜索这些类型的漏洞，等你更熟悉白帽子渗透时再回来。

### 3\. Libcurl 越界读取

难度：高

URL：无

报告链接：`http://curl.haxx.se/docs/adv_20141105.html`

报告日期：2014.11.5

奖金：$1000

描述：

Libcurl 是一个免费的客户端 URL 库，并且由 CURL 命令行工具用于转送数据。libcurl 的`curl_easy_duphandle() `函数中发现了一个漏洞，它可以利用来发送本不应传输的敏感数据。

在使用 libcurl 执行数据传输时，我们可以使用一个选项，`CURLOPT_COPYPOSTFIELDS`，来为要发送给远程服务器的数据指定内存区域。换句话说，为你的数据找一块地方。区域大小使用单独的选项来设置。

现在，我们没必要非常技术化，内存区域和一个“句柄”相关（理解清楚“句柄”超出了本书范围，所以没必要了解），并且应用会复制句柄来创建数据的副本。这就是漏洞所在，复制的实现使用了`strdup`，而数据被假设拥有空字符作为字符串末尾。

这种情况下，数据可能没有，或者在任意位置上拥有空字符。因此，复制的句柄可能过小，过大，或者使程序崩溃。此外，在复制之后，发送数据的函数并没有考虑已经读取和复制的数据，所以它也越过了预期的内存地址来访问和发送数据。

> 重要结论

> 这是一个非常复杂的漏洞的示例。虽然它对于这本书来说，过于技术化了，我将其包含来展示它与我们所学的东西的相似性。当我们将其分解时，这个漏洞也与 C 语言代码实现中的一个错误相关，而 C 语言与内存管理和复制相关。同样，如果你打算开始 C 程序的漏洞挖掘，要寻找数据从一块区域复制到另一块区域的地方。

### 4\. PHP 内存截断

难度：高

URL：无

报告链接：`https://bugs.php.net/bug.php?id=69453`

报告日期：2015.4.14

奖金：$500

描述：

`phar_parse_tarfile`函数并没有考虑以空字符开始的文件名称，空字符是值为 0 的字节，即十六进制的`0x00`。

在该方法的执行期间，当使用文件名称时，数组会发生下溢（即尝试访问不存在的数据，并超出了数组分配的内存）。

这是个重要漏洞，因为它向黑客提供了本该限制的内存的访问权。

> 重要结论

在处理自己管理内存的应用时，特别是 C 和 C++，就像缓冲区溢出那样，内存截断是个古老但是仍旧常见的漏洞。如果你发现，你正在处理基于 C 语言的 Web 应用（PHP 使用它编写），要留意内存操作的方式。但是同样，如果你刚刚起步，你可能值得花费更多时间来寻找简单的注入漏洞，当你更熟练时，再回到内存截断。

## 总结

虽然内存相关的漏洞能搞个大新闻，但他们也非常难以处理，并需要相当大量的技巧。这些类型的漏洞最好还是留着，除非你拥有底层编程语言的编程背景。

虽然现代的程序语言不太可能受其影响，由于它们的内存处理和垃圾收集策略，用 C 语言编写的应用仍然易受影响。此外，当你处理用 C 语言编写的现代语言时，事情可能需要一些技巧，就像我们在 PHP`ftp_genlist()`和 Python Hotspot 模块的示例中看到的那样。
