Server-side template injection
在本节中,我们将介绍什么是服务端模板注入,并概述利用此漏洞的基本方法,同时也将提供一些避免此漏洞的建议。
什么是服务端模板注入
服务端模板注入是指攻击者能够利用模板自身语法将恶意负载注入模板,然后在服务端执行。
模板引擎被设计成通过结合固定模板和可变数据来生成网页。当用户输入直接拼接到模板中,而不是作为数据传入时,可能会发生服务端模板注入攻击。这使得攻击者能够注入任意模板指令来操纵模板引擎,从而能够完全控制服务器。顾名思义,服务端模板注入有效负载是在服务端交付和执行的,这可能使它们比典型的客户端模板注入更危险。
服务端模板注入会造成什么影响
服务端模板注入漏洞会使网站面临各种攻击,具体取决于所讨论的模板引擎以及应用程序如何使用它。在极少数情况下,这些漏洞不会带来真正的安全风险。然而,大多数情况下,服务端模板注入的影响可能是灾难性的。
最严重的情况是,攻击者有可能完成远程代码执行,从而完全控制后端服务器,并利用它对内部基础设施进行其他攻击。
即使在不可能完全执行远程代码的情况下,攻击者通常仍可以使用服务端模板注入作为许多其他攻击的基础,从而可能获得服务器上敏感数据和任意文件的访问权限。
服务端模板注入漏洞是如何产生的
当用户输入直接拼接到模板中而不是作为数据传入时,就会出现服务端模板注入漏洞。
简单地提供占位符并在其中呈现动态内容的静态模板通常不会受到服务端模板注入的攻击。典型的例子如提取用户名作为电子邮件的开头,例如以下从 Twig 模板中提取的内容:
$output = $twig->render("Dear {first_name},", array("first_name" => $user.first_name) );
这不容易受到服务端模板注入的攻击,因为用户的名字只是作为数据传递到模板中的。
但是,Web 开发人员有时可能将用户输入直接连接到模板中,如:
$output = $twig->render("Dear " . $_GET['name']);
此时,不是将静态值传递到模板中,而是使用 GET name 动态生成模板本身的一部分。由于模板语法是在服务端执行的,这可能允许攻击者使用 name 参数如下:
http://vulnerable-website.com/?name={{bad-stuff-here}}
像这样的漏洞有时是由于不熟悉安全概念的人设计了有缺陷的模板造成的。与上面的例子一样,你可能会看到不同的组件,其中一些组件包含用户输入,连接并嵌入到模板中。在某些方面,这类似于 SQL 注入漏洞,都是编写了不当的语句。
然而,有时这种行为实际上是有意为之。例如,有些网站故意允许某些特权用户(如内容编辑器)通过设计来编辑或提交自定义模板。如果攻击者能够利用特权帐户,这显然会带来巨大的安全风险。
构造服务端模板注入攻击
识别服务端模板注入漏洞并策划成功的攻击通常涉及以下抽象过程。
探测
服务端模板注入漏洞常常不被注意到,这不是因为它们很复杂,而是因为它们只有在明确寻找它们的审计人员面前才真正明显。如果你能够检测到存在漏洞,则利用它将非常容易。在非沙盒环境中尤其如此。
与任何漏洞一样,利用漏洞的第一步就是先找到它。也许最简单的初始方法就是注入模板表达式中常用的一系列特殊字符,例如 ${{<%[%'"}}%\
,去尝试模糊化模板。如果引发异常,则表明服务器可能以某种方式解释了注入的模板语法,从而表明服务端模板注入可能存在漏洞。
服务端模板注入漏洞发生在两个不同的上下文中,每个上下文都需要自己的检测方法。不管模糊化尝试的结果如何,也要尝试以下特定于上下文的方法。如果模糊化是不确定的,那么使用这些方法之一,漏洞可能会暴露出来。即使模糊化确实表明存在模板注入漏洞,你仍然需要确定其上下文才能利用它。
Plaintext context
纯文本上下文。
大多数模板语言允许你通过直接使用 HTML tags 或模板语法自由地输入内容,后端在发送 HTTP 响应之前,会把这些内容渲染为 HTML 。例如,在 Freemarker 模板中,render('Hello ' + username)
可能会渲染为 Hello Carlos
。
这有时经常被误认为是一个简单的 XSS 漏洞并用于 XSS 攻击。但是,通过将数学运算设置为参数的值,我们可以测试其是否也是服务端模板注入攻击的潜在攻击点。
例如,考虑包含以下模板代码:
render('Hello ' + username)
在审查过程中,我们可以通过请求以下 URL 来测试服务端模板注入:
http://vulnerable-website.com/?username=${7*7}
如果结果输出包含 Hello 49
,这表明数学运算被服务端执行了。这是服务端模板注入漏洞的一个很好的证明。
请注意,成功计算数学运算所需的特定语法将因使用的模板引擎而异。我们将在 Identify 步骤详细说明。
Code context
代码上下文。
在其他情况下,漏洞暴露是因为将用户输入放在了模板表达式中,就像上文中的电子邮件示例中看到的那样。这可以采用将用户可控制的变量名放置在参数中的形式,例如:
greeting = getQueryParameter('greeting')
engine.render("Hello {{"+greeting+"}}", data)
在网站上生成的 URL 类似于:
http://vulnerable-website.com/?greeting=data.username
渲染的输出可能为 Hello Carlos
。
在评估过程中很容易忽略这个上下文,因为它不会产生明显的 XSS,并且与简单的 hashmap 查找几乎没有区别。在这种情况下,测试服务端模板注入的一种方法是首先通过向值中注入任意 HTML 来确定参数不包含直接的 XSS 漏洞:
http://vulnerable-website.com/?greeting=data.username
在没有 XSS 的情况下,这通常会导致输出中出现空白(只有 Hello,没有 username ),编码标签或错误信息。下一步是尝试使用通用模板语法来跳出该语句,并尝试在其后注入任意 HTML :
http://vulnerable-website.com/?greeting=data.username}}
如果这再次导致错误或空白输出,则说明你使用了错误的模板语法。或者,模板样式的语法均无效,此时则无法进行服务端模板注入。如果输出与任意 HTML 一起正确呈现,则这是服务端模板注入漏洞存在的关键证明:
Hello Carlos
识别
一旦检测到潜在的模板注入,下一步就是确定模板引擎。
尽管有大量的模板语言,但许多都使用非常相似的语法,这些语法是专门为避免与 HTML 字符冲突而选择的。因此,构造试探性载荷来测试正在使用哪个模板引擎可能相对简单。
简单地提交无效的语法就足够了,因为生成的错误消息会告诉你用了哪个模板引擎,有时甚至能具体到哪个版本。例如,非法的表达式 <%=foobar%>
触发了基于 Ruby 的 ERB 引擎的如下响应:
(erb):1:in `': undefined local variable or method `foobar' for main:Object (NameError)
from /usr/lib/ruby/2.5.0/erb.rb:876:in `eval'
from /usr/lib/ruby/2.5.0/erb.rb:876:in `result'
from -e:4:in `'
否则,你将需要手动测试不同语言特定的有效负载,并研究模板引擎如何解释它们。使用基于语法有效或无效的排除过程,你可以比你想象的更快地缩小选项范围。一种常见的方法是使用来自不同模板引擎的语法注入任意的数学运算。然后,观察它们是否被成功执行。要完成此过程,可以使用类似于以下内容的决策树:
你应该注意,同样的有效负载有时可以获得多个模板语言的成功响应。例如,有效载荷 {{7*'7'}}
在 Twig
中返回 49
,在 Jinja2
中返回 7777777
。因此,不要只因为成功响应了就草率下结论。
利用
在检测到存在潜在漏洞并成功识别模板引擎之后,就可以开始尝试寻找利用它的方法。详细请翻阅下文。
如何防止服务端模板注入漏洞
防止服务端模板注入的最佳方法是不允许任何用户修改或提交新模板。然而,由于业务需求,这有时是不可避免的。
避免引入服务端模板注入漏洞的最简单方法之一是,除非绝对必要,始终使用“无逻辑”模板引擎,如 Mustache
。尽可能的将逻辑与表示分离,这可以大大减少高危险性的基于模板的攻击的风险。
另一措施是仅在完全删除了潜在危险模块和功能的沙盒环境中执行用户的代码。不幸的是,对不可信的代码进行沙盒处理本身就很困难,而且容易被绕过。
最后,对于接受任意代码执行无法避免的情况,另一种补充方法是,通过在锁定的例如 Docker 容器中部署模板环境,来应用你自己的沙盒。
利用服务端模板注入漏洞
在本节中,我们将更仔细地了解一些典型的服务端模板注入漏洞,并演示如何利用之前归纳的方法。通过付诸实践,你可以潜在地发现和利用各种不同的服务端模板注入漏洞。
一旦发现服务端模板注入漏洞,并确定正在使用的模板引擎,成功利用该漏洞通常涉及以下过程。
阅读
- 模板语法
- 安全文档
- 已知的漏洞利用
- 探索环境
- 构造自定义攻击
阅读
除非你已经对模板引擎了如指掌,否则应该先阅读其文档。虽然这可能有点无聊,但是不要低估文档可能是有用的信息来源。
学习基本模板语法
学习基本语法、关键函数和变量处理显然很重要。即使只是简单地学习如何在模板中嵌入本机代码块,有时也会很快导致漏洞利用。例如,一旦你知道正在使用基于 Python 的 Mako 模板引擎,实现远程代码执行可以简单到:
<%
import os
x=os.popen('id').read()
%>
${x}
在非沙盒环境中,实现远程代码执行并将其用于读取、编辑或删除任意文件在许多常见模板引擎中都非常简单。
阅读安全部分
除了提供如何创建和使用模板的基础知识外,文档还可能提供某种“安全”部分。这个部分的名称会有所不同,但它通常会概括出人们应该避免使用模板进行的所有潜在危险的事情。这可能是一个非常宝贵的资源,甚至可以作为一种备忘单,为你应该寻找哪些行为,以及如何利用它们提供指南。
即使没有专门的“安全”部分,如果某个特定的内置对象或函数会带来安全风险,文档中几乎总是会出现某种警告。这个警告可能不会提供太多细节,但至少应将其标记为可以深入挖掘研究的内容。
例如,在 ERB 模板中,文档显示可以列出所有目录,然后按如下方式读取任意文件:
<%= Dir.entries('/') %>
<%= File.open('/example/arbitrary-file').read %>
查找已知的漏洞利用
利用服务端模板注入漏洞的另一个关键方面是善于查找其他在线资源。一旦你能够识别正在使用的模板引擎,你应该浏览 web 以查找其他人可能已经发现的任何漏洞。由于一些主要模板引擎的广泛使用,有时可能会发现有充分记录的漏洞利用,你可以对其进行调整以利用到自己的目标网站。
探索
此时,你可能已经在使用文档时偶然发现了一个可行的漏洞利用。如果没有,下一步就是探索环境并尝试发现你可以访问的所有对象。
许多模板引擎公开某种类型的 self
或 environment
对象,其作用类似于包含模板引擎支持的所有对象、方法和属性的命名空间。如果存在这样的对象,则可以潜在地使用它来生成范围内的对象列表。例如,在基于 Java 的模板语言中,有时可以使用以下注入列出环境中的所有变量:
${T(java.lang.System).getenv()}
这可以作为创建一个潜在有趣对象和方法的短名单的基础,以便进一步研究。
开发人员提供的对象
需要注意的是,网站将包含由模板提供的内置对象和由 web 开发人员提供的自定义、特定于站点的对象。你应该特别注意这些非标准对象,因为它们特别可能包含敏感信息或可利用的方法。由于这些对象可能在同一网站中的不同模板之间有所不同,请注意,你可能需要在每个不同模板的上下文中研究对象的行为,然后才能找到利用它的方法。
虽然服务端模板注入可能导致远程代码执行和服务器的完全接管,但在实践中,这并非总是可以实现。然而,仅仅排除了远程代码执行,并不一定意味着不存在其他类型的攻击。你仍然可以利用服务端模板注入漏洞进行其他高危害性攻击,例如目录遍历,以访问敏感数据。
构造自定义攻击
到目前为止,我们主要研究了通过重用已记录的漏洞攻击或使用模板引擎中已知的漏洞来构建攻击。但是,有时你需要构建一个自定义的漏洞利用。例如,你可能会发现模板引擎在沙盒中执行模板,这会使攻击变得困难,甚至不可能。
在识别攻击点之后,如果没有明显的方法来利用漏洞,你应该继续使用传统的审计技术,检查每个函数的可利用行为。通过有条不紊地完成这一过程,你有时可以构建一个复杂的攻击,甚至能够利用于更安全的目标。
使用对象链构造自定义攻击
如上文所述,第一步是标识你有权访问的对象和方法。有些对象可能会立即跳出来。通过结合你自己的知识和文档中提供的信息,你应该能够将你想要更彻底地挖掘的对象的短名单放在一起。
在研究对象的文档时,要特别注意这些对象允许访问哪些方法,以及它们返回哪些对象。通过深入到文档中,你可以发现可以链接在一起的对象和方法的组合。将正确的对象和方法链接在一起有时允许你访问最初看起来遥不可及的危险功能和敏感数据。
例如,在基于 Java 的模板引擎 Velocity 中,你可以调用 $class
访问 ClassTool
对象。研究文档表明,你可以链式使用 $class.inspect()
方法和 $class.type
属性引用任意对象。在过去,这被用来在目标系统上执行 shell 命令,如下所示:
$class.inspect("java.lang.Runtime").type.getRuntime().exec("bad-stuff-here")
使用开发人员提供的对象构造自定义攻击
一些模板引擎默认运行在安全、锁定的环境中,以便尽可能地降低相关风险。尽管这使得利用这些模板进行远程代码执行变得很困难,但是开发人员创建的暴露于模板的对象可以提供更进一步的攻击点。
然而,虽然通常为模板内置对象提供了大量的文档,但是网站特定的对象几乎根本就没有文档记录。因此,要想知道如何利用这些漏洞,就需要你手动调查网站的行为,以确定攻击点,并据此构建你自己的自定义攻击。