云服务器价格_云数据库_云主机【优惠】最新活动-搜集站云资讯

美国服务器_彩票网站建设_哪家好

小七 141 0

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

上周五,来自Google的projectzero的tavisormandy联系Cloudflare报告了我们的边缘服务器的安全问题。他看到一些通过Cloudflare运行的HTTP请求返回了损坏的web页面。结果发现,在一些不寻常的情况下(我将在下面详细介绍),我们的边缘服务器运行时超过了缓冲区的末端,返回的内存包含了诸如HTTP cookie、身份验证令牌、HTTP POST主体和其他敏感数据等私有信息。有些数据已经被搜索引擎缓存了。为免生疑问,Cloudflare客户SSL私钥没有泄漏。Cloudflare总是通过NGINX的独立实例终止SSL连接,而NGINX不受此bug的影响。我们很快发现了这个问题,并关闭了Cloudflare的三个次要功能(电子邮件模糊处理、服务器端排除和自动HTTPS重写),它们都使用导致泄漏的同一个HTML解析器链。那时,在HTTP响应中再也不可能返回内存了。由于这种错误的严重性,一个来自软件工程、infosec和operations的跨职能团队在旧金山和伦敦成立,以全面了解潜在原因,了解内存泄漏的影响,并与谷歌和其他搜索引擎合作,删除任何缓存的HTTP响应。拥有一个全球性的团队意味着,每隔12小时,各办事处之间就要进行工作交接,使工作人员能够每天24小时处理这个问题。该团队一直在努力确保这个bug及其后果得到充分处理。作为服务的一个优点是,bug可以在几分钟到几小时内从报告变为修复,而不是几个月。对于这样的bug,允许部署修复程序的行业标准时间通常是3个月;我们在全球范围内完成了不到7小时的工作,最初的缓解时间为47分钟。这个错误很严重,因为泄露的内存可能包含私人信息,而且已经被搜索引擎缓存了。我们也没有发现任何恶意利用该漏洞的证据或其他有关其存在的报告。影响最大的时期是2月13日和2月18日,通过Cloudflare每3300000个HTTP请求中就有1个可能导致内存泄漏(约占请求的0.00003%)。我们很感激它被世界顶尖的安全研究小组发现并报告给我们。这篇博文相当长,但是,正如我们的传统一样,我们更愿意对我们的服务中出现的问题进行公开和技术上的详细说明。动态解析和修改HTMLCloudflare的许多服务都依赖于在HTML页面通过边缘服务器时对其进行解析和修改。例如,我们可以插入Google Analytics标记,安全地将改写为https://,从坏机器人程序中排除页面的一部分,模糊电子邮件地址,启用AMP,等等,方法是修改页面的HTML。要修改页面,我们需要读取并解析HTML以找到需要更改的元素。从Cloudflare的早期开始,我们就使用了一个使用Ragel编写的解析器。一个.rl文件包含一个HTML解析器,用于Cloudflare执行的所有动态HTML修改。大约一年前,我们认为基于Ragel的解析器变得过于复杂,无法维护,于是我们开始编写一个新的解析器,名为cfhtml,来代替它。这个流式解析器可以正确地与HTML5一起工作,而且速度快得多,而且更易于维护。我们首先将这个新的解析器用于自动HTTP重写特性,并且一直在缓慢地将使用旧Ragel解析器的功能迁移到cfhtml。cfhtml和旧的Ragel解析器都是作为NGINX模块编译到NGINX构建中实现的。这些NGINX过滤器模块解析包含HTML响应的缓冲区(内存块),根据需要进行修改,并将缓冲区传递给下一个过滤器。为免生疑:这个bug并不在Ragel本身。这是Cloudflare对Ragel的使用。这是我们的错误,不是拉格尔的错。结果发现,导致内存泄漏的底层bug在我们基于Ragel的解析器中已经存在了很多年,但是由于内部NGINX缓冲区的使用方式,没有内存泄漏。尽管cfhtml本身没有问题,但引入cfhtml微妙地改变了启用泄漏的缓冲区。一旦我们知道错误是由激活cfhtml引起的(但在我们知道原因之前),我们禁用了导致使用它的三个特性。Cloudflare发布的每个特性都有相应的特性标志,我们称之为"全局终止"。我们在收到问题的详细信息47分钟后激活了电子邮件混淆全局kill,随后自动HTTPS重写global kill 3h05m。电子邮件混淆功能已于2月13日更改,是内存泄漏的主要原因,因此禁用它很快就阻止了几乎所有内存泄漏。几秒钟之内,这些功能在全球范围内被禁用。我们确认我们没有发现通过测试uri的内存泄漏,并让Google再次检查他们看到了同样的情况。然后我们发现第三个特性,服务器端排除,也很容易受到攻击,并且没有全局终止开关(它太旧了,在实现全局终止之前就已经存在)。我们实施了服务器端排除的全球杀戮,并在全球部署了一个补丁。从实现服务器端排除是个问题到部署补丁大约花了三个小时。然而,服务器端排除很少使用,而且只对恶意IP地址激活。错误的根本原因Ragel代码被转换成生成的C代码,然后进行编译。C代码以经典的C方式使用指向正在解析的HTML文档的指针,而Ragel本身给了用户很大的控制这些指针的移动。由于指针错误而发生底层错误。/*生成的代码*/如果(++p==pe)去测试;该错误的根本原因是使用相等运算符检查到达缓冲区的结尾,并且指针能够跨过缓冲区的末尾。这称为缓冲区溢出。如果使用>=而不是==跳过缓冲区端进行检查,则会被捕获。等式检查是由Ragel自动生成的,不是我们编写的代码的一部分。这表明我们没有正确使用Ragel。我们编写的Ragel代码包含一个错误,该错误导致指针跳过缓冲区的末尾,并超过了通过相等性检查来发现缓冲区溢出的能力。下面是一段Ragel代码,用于使用HTML标记中的属性。第一行表示,它应该尝试查找零个或多个未加引号的字符,后跟(即:>>连接运算符)空白、正斜杠,然后是表示标记结尾的>。script_consume_attr:=((未加引号的_attr_char)*:>>(空格|'/'|'>')>{ddctx("script consume_attr");}@{fhold;fgoto script_tag_parse;}$lerr{dd("脚本消耗属性失败");fgoto script_consume_attr;};如果属性的格式正确,则Ragel解析器将移动到@{}块内的代码。如果属性解析失败(这是我们今天讨论的bug的开始),那么就使用$lerr{}块。例如,在某些情况下(详见下文),如果网页以这样一个损坏的HTML标记结尾:>(空格|'/'|'>')>{ddctx("script consume_attr");}@{fhold;fgoto script_tag_parse;}$lerr{dd("脚本消耗属性失败");fgoto script_consume_attr;};当解析器在使用属性时用完了要解析的字符时会发生什么,这与当前正在解析的缓冲区是否是最后一个缓冲区不同。如果不是最后一个缓冲区,则不需要使用$lerr,因为解析器不知道是否发生了错误,因为属性的其余部分可能在下一个缓冲区中。但如果这是最后一个缓冲区,则执行$lerr。下面是代码如何跳过文件末尾并在内存中运行。解析函数的入口点是ngx_http_email_parse_email(它的名称是历史性的,它比电子邮件解析做的要多得多)。ngx_int_ngx_http_http_email_parse_email(ngx_http_request_t*r,ngx_http_email_ctx_t*ctx){u_char*p=ctx->位置;u峈char*pe=ctx->buf->last;u\u char*eof=ctx->buf->最后一个?pe:空;您可以看到p指向缓冲区中的第一个字符,pe指向bu结束后的字符