欢迎来到 黑吧安全网 聚焦网络安全前沿资讯,精华内容,交流技术心得!

Cloudflare解析器bug导致内存泄漏事件报告

来源:本站整理 作者:佚名 时间:2017-02-24 TAG: 我要投稿

上星期五,来自谷歌Project Zero组织的Tavis Ormandy联系Cloudflare,报告了我们的边界服务器的一个安全问题。他看到经过Cloudflare的一些HTTP请求返回了崩溃的网页。

它出现在一些不寻常的情况下,在下面我将详细介绍,我们的边界服务器运行时缓冲区越界了,并返回了隐私信息,如 HTTP cookies,认证令牌,HTTP POST体和其他敏感数据的内存。并且有些数据会被搜索引擎缓存。

为了避免怀疑,Cloudflare客户的SSL私钥没有泄漏。Cloudflare总是通过一个隔离的Nginx实例来结束SLL连接,因此不受这个bug影响。

我们快速的确认了这个问题,并关闭了3个Cloudflare功能(邮件混淆,服务端排除和自动HTTPs重写),这些都用来了相同的HTML解析器链,会导致泄漏。这样在一个HTTP响应中就不会有内存返回了。

因为这个bug的严重性,来自San Francisco和 London的软件工程师、信息安全和操作的交叉功能团队充分了解了潜在的原因,为了降低内存泄漏的影响,和谷歌和其他搜索引擎团队一起将缓存的HTTP响应移除了。

拥有一个全球化的团队,每12小时为间隔,每天24小时交替解决这个问题。团队持续的努力确保了问题的圆满解决。作为服务的一个优势是这个bug从报告到解决,花了几分钟到几小时,而不是几个月。针对这样的bug部署修复方案的工业标准通常是3个月;我们在小于7个小时就圆满解决,47分钟内就缓解了bug。

这个bug是严重的,因为泄漏的内存包含了隐私信息,并且还会被搜索引擎缓存。我们还没有发现这个bug的漏洞利用或者它们存在的报告。

影响最大的时期是2月13日到2月18号,通过Cloudflare的每3,300,000个HTTP请求中约有1个可能导致内存泄漏(约为请求的0.00003%)。

我们感谢它由世界顶级安全研究团队发现并报告给我们。

本文很长,但是作为我们的传统,我们倾向于对我们的服务出现的问题保持开放和技术上的详细描述。

0x01 运行时解析并修改HTML

很多Cloudflare服务依赖通过我们的边界服务器时解析和修改HTML页面。例如,我们能通过修改HTML页面来插入谷歌分析标签,安全的重写http://链接为https://,排除来自机器人的部分页面,模糊电子邮件地址,启用AMP等。

为了修改页面,我们需要读取并解析HTML以发现需要修改的元素。因为在Cloudflare的早期,我们已经使用了用Ragel编写的解析器。一个独立的.rl文件包含一个HTML解析器,被用来在Cloudflare平台修改HTML。

大约一年前,我们认为Ragel解析器维护起来太复杂,并且我们开始写一个新的解析器(cf-html)来替代它。这个解析器能正确处理HTML5,而且非常非常快且易维护。

我们首先将这个解析器用于自动HTTP重写功能,并一直慢慢地迁移cf-html替换Ragel。

Cf-html和老的Ragel解析器都作为Nginx模块实现,并编译到我们的Nginx构建中。这个Nginx过滤模块解析包含HTML响应的缓冲区(内存块),做出必要的修改,并将缓冲区传递给下一个过滤模块。

这样,引起内存泄漏的bug在我们的Ragel解析器中已存在多年,但是因为我们内部Nginx使用缓冲区的方式,并没有内存泄漏。Cf-html巧妙的改变了缓冲去,导致在cf-html中不会有问题。

因为我们知道了这个bug是由激活cf-html引起的(但是之前我们知道为什么),我们禁用了使用它的3个功能。Cloudflare每个功能都有一个相应的功能标志,我们称之为全局杀手。我们在收到问题细节报告后的47分钟时启用了邮件混淆的全局杀手,并在3小时5分钟后关闭了自动HTTP重写。邮件混淆功能在2月13号已经修改了,并且是内存泄漏的原因,因此禁用它快速地阻止了几乎所有的内存泄漏。

在几秒内,这些功能在全球范围内被禁用。我们确定没有通过测试URI来泄漏内存,并且谷歌的二次校验也一样。

然后,我们发现了第三个功能(服务端排除)也有这个漏洞,但是没有全局杀手开关(它非常老,在全局杀手之前实现)。我们为服务端排除实现了一个全局杀手,并全球部署补丁。从发现服务端排除是个问题到部署补丁只花了3个小时。然而,服务端排除很少使用,且只针对对恶意的IP地址才激活。

0x02 bug的根因

Ragel代码转化为C代码,然后编译。这个C代码使用经典的C方法,指向HTML文档的指针被解析,并且Ragel自身给用户提供了针对这些指针大量的控制权。因为一个指针错误导致的bug的产生。

/* generated code */
if ( ++p == pe )
    goto _test_eof;
bug的根因是,使用等于运算符来校验是否到达缓冲区的末端,并且指针能够步过缓冲去末端。这是熟知的缓冲去溢出。使用>=代替==来做检验,将跳过缓冲区末端。这个等于校验由Ragel自动生成,不是我们编写的代码。意味着我们没有正确的使用Ragel。

我们编写的Ragel代码包含了一个bug,其能引起指针越界且给了等号校验造成缓冲区溢出的能力。

下面是Ragel代码的一段代码,用来获取HTML <script>标签中的一个属性。第一行说的是它试图找到更多以空格,正斜杠或>结尾的unquoted_attr_char。(:>>是连接符)

script_consume_attr := ((unquoted_attr_char)* :>> (space|'/'|'>'))
>{ ddctx("script consume_attr"); }
@{ fhold; fgoto script_tag_parse; }
$lerr{ dd("script consume_attr failed");
       fgoto script_consume_attr; };
如果一个属性格式良好,则Ragel解析器跳转到@{}代码块。如果解析属性失败(就是我们今天讨论的bug的开始),那么到$lerr{}。

举个例子,在实际情况下(细节如下),如果web页面以错误的HTML标签结尾,如:


<script type=
$lerr{ }块将执行,并且缓冲去将溢出。这个例子中$lerr执行dd(“script consume_attr failed”);(这是个调试语句),然后执行fgoto script_consume_attr;(转移到script_consume_attr去解析下一个属性)。

从我们的分析中看,这样错误的标签出现在大约0.06%的网站中。

如果你观察仔细,你可能已经注意到@{ }也是一个fgoto,但是在它之前执行了fhold,并且$lerr{ }没有。它没有fhold导致了内存泄漏。

在内部,生成的C代码有一个指针p,指向HTML文档中正在检测的字符。Fhold等价于p--,并且是必要的。因为当错误条件发生时,p将指向导致script_consume_attr失败的字符。

[1] [2] [3]  下一页

【声明】:黑吧安全网(http://www.myhack58.com)登载此文出于传递更多信息之目的,并不代表本站赞同其观点和对其真实性负责,仅适于网络安全技术爱好者学习研究使用,学习中请遵循国家相关法律法规。如有问题请联系我们,联系邮箱admin@myhack58.com,我们会在最短的时间内进行处理。
  • 最新更新
    • 相关阅读
      • 本类热门
        • 最近下载