服务器端模板注入 ------ Server-side template injection
- [1. 什么是服务器端模板注入?](#1. 什么是服务器端模板注入?)
- [2. 服务器端模板注入有什么影响?](#2. 服务器端模板注入有什么影响?)
- [3. 服务器端模板注入漏洞是如何产生的?](#3. 服务器端模板注入漏洞是如何产生的?)
- [4. 构建服务器端模板注入](#4. 构建服务器端模板注入)
-
- [4.1 检测](#4.1 检测)
-
- [4.1.1 纯文本上下文](#4.1.1 纯文本上下文)
- [4.1.2 代码上下文](#4.1.2 代码上下文)
- [4.2 识别](#4.2 识别)
- [4.3 利用](#4.3 利用)
-
- [4.3.1 读](#4.3.1 读)
-
- [4.3.1.1 了解基本模板语法](#4.3.1.1 了解基本模板语法)
- [4.3.1.2 了解安全隐患](#4.3.1.2 了解安全隐患)
- [4.3.1.3 查找已知的漏洞](#4.3.1.3 查找已知的漏洞)
- [4.3.2 探索环境](#4.3.2 探索环境)
-
- [4.3.2.1 开发人员提供的对象](#4.3.2.1 开发人员提供的对象)
- [4.3.3 创建自定义攻击](#4.3.3 创建自定义攻击)
-
- [4.3.3.1 使用对象链构建自定义漏洞](#4.3.3.1 使用对象链构建自定义漏洞)
- [4.3.3.2 使用开发人员提供的对象构建自定义漏洞](#4.3.3.2 使用开发人员提供的对象构建自定义漏洞)
- [5. 如何防止服务器端模板注入漏洞](#5. 如何防止服务器端模板注入漏洞)
概述
本文将讨论什么是服务器端模板注入,并概述检测服务器端模板注入漏洞的基本方法。并且还将提出一些方法,确保你自己使用模板时不会遭受服务器端模板注入攻击。
1. 什么是服务器端模板注入?
服务器端模板注入是指攻击者能够使用原生模板语法将恶意负载注入模板,然后在服务器端执行。
模板引擎旨在通过将固定模板与易变数据相结合来生成网页。当用户输入直接拼接到模板中而不是作为数据传入时,可能会发生服务器端模板注入攻击。这使得攻击者能够注入任意模板指令以操纵模板引擎,通常使他们能够完全控制服务器。顾名思义,服务器端模板注入有效载荷在服务器端交付并进行评估,这可能使它们比典型的客户端模板注入危险得多。
2. 服务器端模板注入有什么影响?
服务器端模板注入漏洞可能会使网站遭受各种攻击,具体取决于所讨论的模板引擎以及应用程序使用它的确切方式。在某些极少数情况下,这些漏洞不会造成真正的安全风险。但是,大多数时候,服务器端模板注入的影响可能是灾难性的。
在严重的情况下,攻击者可能会实现远程代码执行,完全控制后端服务器并使用它对内部基础设施执行其他攻击。
即使在无法完全远程执行代码的情况下,攻击者通常仍然可以使用服务器端模板注入作为许多其他攻击的基础,从而可能获得对服务器上敏感数据和任意文件的读取访问权限。
3. 服务器端模板注入漏洞是如何产生的?
当用户输入连接到模板中而不是作为数据传递时,就会出现服务器端模板注入漏洞。
仅提供动态内容渲染占位符的静态模板通常不易受到服务器端模板注入的影响。一个典型的例子是用每个用户的名字来问候他们的电子邮件,例如以下来自 Twig 模板的摘录:
bash
$output = $twig->render("Dear {first_name},", array("first_name" => $user.first_name) );
这不易受到服务器端模板注入的攻击,因为用户的名字仅作为数据传递到模板中。
但是,由于模板只是字符串,因此 Web 开发人员有时会在呈现之前直接将用户输入连接到模板中。让我们举一个与上面类似的示例,但这次,用户可以在电子邮件发送之前自定义电子邮件的某些部分。例如,他们可能能够选择使用的名称:
bash
$output = $twig->render("Dear " . $_GET['name']);
在此示例中,模板本身的一部分不是传递到模板中,而是使用 GET
获取参数name
动态生成。由于模板语法是在服务器端评估的,因此这可能允许攻击者将服务器端模板注入有效负载放在 name
参数中,如下所示:
bash
http://vulnerable-website.com/?name={{bad-stuff-here}}
像这样的漏洞有时是由于不熟悉安全隐患的人设计不佳的模板而意外造成的。就像上面的例子一样,你可能会看到不同的组件,其中一些包含用户输入,连接并嵌入到模板中。在某些方面,这与在编写不当的预编译语句中出现的 SQL 注入漏洞类似。
但是,有时此行为实际上是有意实现的。例如,某些 Web 站点故意允许某些特权用户(如内容编辑者)根据设计编辑或提交自定义模板。如果攻击者能够破坏具有此类权限的帐户,这显然会带来巨大的安全风险。
4. 构建服务器端模板注入
识别服务器端模板注入漏洞并精心策划成功的利用通常涉及以下高级过程。

4.1 检测
服务器端模板注入漏洞常常被忽视,不是因为它们复杂,而是因为只有明确寻找它们的审计人员才能真正发现它们。如果你能够检测到漏洞的存在,那么利用它可能会出奇地容易。在未沙箱化的环境中尤其如此。
与任何漏洞一样,利用漏洞的第一步是能够找到它。也许最简单的初始方法是通过注入模板表达式 中常用的一系列特殊字符来对模板进行模糊测试,例如${``{<%[%'"}}%\
。如果引发异常,则表明注入的模板语法可能以某种方式被服务器解释。这是存在服务器端模板注入漏洞的一个迹象。
服务器端模板注入漏洞出现在两种不同的情况下,每种情况都需要自己的检测方法。无论模糊测试的结果如何,尝试以下特定于上下文的方法也很重要。如果模糊测试没有定论,漏洞仍可能通过这些方法之一显现出来。即使模糊测试确实表明存在模板注入漏洞,您仍然需要确定其上下文才能加以利用。
4.1.1 纯文本上下文
大多数模板语言都允许您通过直接使用 HTML 标签或使用模板的本机语法自由输入内容,这些语法将在发送 HTTP 响应之前在后端呈现为 HTML。例如,在 Freemarker 中,行 render('Hello ' + username)
将呈现为类似于 Hello Carlos
的内容。
这有时可能被用于 XSS 攻击,实际上它经常被误认为是一个简单的 XSS 漏洞。然而,通过将数学运算设置为参数的值,我们可以测试这是否也是服务器端模板注入攻击的潜在入口点。
例如,假设一个模板包含以下易受攻击的代码:
bash
render('Hello ' + username)
在审计期间,我们可能会通过请求如下 URL 来测试服务器端模板注入:
bash
http://vulnerable-website.com/?username=${7*7}
如果生成的输出包含 Hello 49
,这表明数学运算正在服务器端进行评估。这是服务器端模板注入漏洞的一个很好的概念验证。
请注意,成功评估数学运算所需的特定语法将因使用的模板引擎而异。我们将在"识别"步骤中更详细地讨论这一点。
4.1.2 代码上下文
在其他情况下,将用户输入置于模板表达式中会暴露漏洞,正如我们之前在电子邮件示例中看到的那样。这可能采用将用户可控制的变量名称放置在参数内的形式,例如:
bash
greeting = getQueryParameter('greeting')
engine.render("Hello {{"+greeting+"}}", data)
在网站上,生成的 URL 将如下所示:
bash
http://vulnerable-website.com/?greeting=data.username
这将在输出中呈现为Hello Carlos
。
在评估过程中很容易错过这个上下文,因为它不会导致明显的 XSS,并且与简单的 hashmap 查找几乎没有区别。在这种情况下,测试服务器端模板注入的一种方法是首先通过将任意 HTML 注入到值中来确定参数不包含直接的 XSS 漏洞:
bash
http://vulnerable-website.com/?greeting=data.username<tag>
在没有 XSS 的情况下,这通常会导致输出中出现空白条目(只有 Hello
没有用户名)、编码的标签或错误消息。下一步是尝试使用常见的模板语法跳出语句,并尝试在它后面注入任意 HTML:
bash
http://vulnerable-website.com/?greeting=data.username}}<tag>
如果再次得到错误结果或空白输出,那么要么是使用了错误的模板语言语法,要么是如果没有模板风格的语法是有效的,那么就不可能存在服务器端模板注入漏洞。另外,如果输出正确呈现,并且带有任意的 HTML,这是一个表明存在服务器端模板注入漏洞的关键迹象。
bash
Hello Carlos<tag>
4.2 识别
检测到模板注入可能性后,下一步是确定模板引擎。
尽管有大量的模板语言,但其中许多都使用非常相似的语法,这些语法是专门为不与 HTML 字符冲突而选择的。因此,创建探测有效负载以测试正在使用的模板引擎可能相对简单。
简单地提交无效语法通常就足够了,因为生成的错误消息会告诉你模板引擎是什么,有时甚至是哪个版本。例如,无效表达式 <%=foobar%>
会触发来自基于 Ruby 的 ERB 引擎的以下响应:
bash
(erb):1:in `<main>': 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 `<main>'
否则,你需要手动测试不同特定语言的有效载荷,并研究模板引擎如何解释它们。通过基于哪些语法看起来有效或无效的排除过程,你可以比想象中更快地缩小选项范围。一种常见的方法是使用来自不同模板引擎的语法注入任意数学运算。然后,你可以观察它们是否被成功评估。为了帮助这个过程,你可以使用类似于以下的决策树:

您应该知道,相同的有效负载有时可能会以多种模板语言返回成功的响应。例如,负载 {``{7*'7'}}
在 Twig 中返回 49
,在 Jinja2 中返回 7777777
。因此,重要的是不要根据单个成功的响应匆忙下结论。
4.3 利用
在检测到存在潜在漏洞并成功识别模板引擎后,您可以开始尝试寻找利用它的方法。
在本节中,我们将更仔细地研究一些典型的服务器端模板注入漏洞,并演示如何使用我们的高级方法利用它们。通过将此过程付诸实践,您可以潜在地发现和利用各种不同的服务器端模板注入漏洞。
发现服务器端模板注入漏洞并确定正在使用的模板引擎后,成功利用通常涉及以下过程。
- 读
- 模板语法
- 安全文档
- 记录在案的漏洞
- 探索环境
- 创建自定义攻击
4.3.1 读
除非你已经对模板引擎了如指掌,否则阅读其文档通常是首先要开始的地方。虽然这可能不是最令人兴奋的消磨时间的方式,但重要的是不要低估文档的有用信息来源。
4.3.1.1 了解基本模板语法
学习基本语法显然很重要,关键函数和变量的处理也很重要。即使是像学习如何在模板中嵌入原生代码块这样简单的事情,有时也会很快导致漏洞利用。例如,一旦您知道正在使用基于 Python
的 Mako
模板引擎,实现远程代码执行就可以像以下简单:
bash
<%
import os
x=os.popen('id').read()
%>
${x}
在未沙盒的环境中,实现远程代码执行并使用它来读取、编辑或删除任意文件在许多常见模板引擎中同样简单。
4.3.1.2 了解安全隐患
除了提供如何创建和使用模板的基础知识外,该文档还可能提供某种 "Security" 部分。本节的名称会有所不同,但它通常会概述人们应该避免使用模板做的所有潜在危险的事情。这可能是一种宝贵的资源,甚至可以作为一种备忘单,用于说明您在审计过程中应该寻找哪些行为,以及如何利用它们。
即使没有专门的 "Security" 部分,如果特定的内置对象 或函数可能会带来安全风险,则文档中几乎总是会有某种警告。该警告可能没有提供太多细节,但至少它应该将这个特定的内置功能标记为需要调查的内容。
例如,在 ERB 中,文档显示您可以列出所有目录,然后读取任意文件,如下所示:
bash
<%= Dir.entries('/') %>
<%= File.open('/example/arbitrary-file').read %>
4.3.1.3 查找已知的漏洞
利用服务器端模板注入漏洞的另一个关键方面是善于在线查找其他资源。一旦您能够识别正在使用的模板引擎,您应该浏览 Web 以查找其他人可能已经发现的任何漏洞。由于一些主要模板引擎的广泛使用,有时可以找到有据可查的漏洞,您可以对其进行调整以利用您自己的目标网站。
4.3.2 探索环境
此时,您可能已经使用文档偶然发现了一个可行的漏洞利用。如果没有,下一步是探索环境并尝试发现您有权访问的所有对象。
许多模板引擎公开某种类型的 "self" 或 "environment" 对象,它的作用类似于包含模板引擎支持的所有对象、方法和属性的命名空间。如果存在此类对象,则可以使用它来生成范围内的对象列表。例如,在基于 Java 的模板语言中,有时可以使用以下注入列出环境中的所有变量:
bash
${T(java.lang.System).getenv()}
这可以作为创建一个可能有趣的对象和方法的候选列表的基础,以便进一步调查。此外,对于 Burp Suite Professional ,Intruder 提供了一个内置的字典用于暴力破解变量名。
4.3.2.1 开发人员提供的对象
需要注意的是,网站将包含由模板提供的内置对象以及由 Web 开发人员提供的自定义、特定于站点的对象。你应该特别注意这些非标准对象,因为它们特别可能包含敏感信息或可利用的方法。由于这些对象在同一网站的不同模板之间可能会有所不同,因此请注意,在找到利用它的方法之前,你可能需要在每个不同的模板的上下文中研究对象的行为。
虽然服务器端模板注入有可能导致远程代码执行并完全接管服务器,但实际上并不总是能够实现这一点。然而,仅仅因为你排除了远程代码执行,并不一定意味着没有其他类型漏洞利用的可能性。你仍然可以利用服务器端模板注入漏洞进行其他高严重性的漏洞利用,例如文件路径遍历,以获取敏感数据。
4.3.3 创建自定义攻击
到目前为止,我们主要研究了通过重用已记录的漏洞利用或利用模板引擎中的已知漏洞来构建攻击。然而,有时你需要构建一个自定义漏洞利用。例如,你可能会发现模板引擎在沙箱中执行模板,这可能会使漏洞利用变得困难,甚至不可能。
确定攻击面后,如果没有明显的方法来利用漏洞,则应通过审查每个函数的可利用行为来继续使用传统的审计技术。通过有条不紊地执行此过程,有时您可能能够构建一个复杂的攻击,甚至能够攻击更安全的目标。
4.3.3.1 使用对象链构建自定义漏洞
如上文所述,第一步是确定你可以访问的对象和方法。有些对象可能会立即引起你的兴趣。通过结合你自己的知识和文档中提供的信息,你应该能够列出一个你想要更深入调查的对象的候选名单。
在研究对象的文档时,要特别注意这些对象授予访问权限的方法以及它们返回的对象。通过深入研究文档,你可以发现能够链接在一起的对象和方法的组合。将正确的对象和方法链接在一起有时可以让你访问到最初看似无法触及的危险功能和敏感数据。
例如,在基于 Java
的模板引擎 Velocity
中,您可以访问名为 $class
的 ClassTool
对象。研究文档表明,您可以链接 $class.inspect()
方法和 $class.type
属性来获取对任意对象的引用。过去,这已被用于在目标系统上执行 shell 命令,如下所示:
bash
$class.inspect("java.lang.Runtime").type.getRuntime().exec("bad-stuff-here")
4.3.3.2 使用开发人员提供的对象构建自定义漏洞
一些模板引擎默认在安全、锁定的环境中运行,以便尽可能降低相关风险。虽然这使得利用此类模板进行远程代码执行变得困难,但暴露给模板的开发者创建的对象可以提供一个进一步的、不太坚固的攻击面。
然而,虽然通常会为模板内置函数提供大量文档,但特定于站点的对象几乎肯定完全没有文档记录。因此,要弄清楚如何利用它们,你需要手动调查网站的行为,以确定攻击面,并相应地构建自己的自定义漏洞利用程序。
5. 如何防止服务器端模板注入漏洞
防止服务器端模板注入的最佳方法是不允许任何用户修改或提交新模板。但是,由于业务要求,这有时是不可避免的。
避免引入服务器端模板注入漏洞的最简单方法之一是始终使用 "无逻辑" 模板引擎,例如 Mustache,除非绝对必要。尽可能将逻辑与表示分离,可以极大地减少你遭受最危险的基于模板的攻击的风险。
另一种措施是仅在完全删除潜在危险模块和函数的沙盒环境中执行用户的代码。遗憾的是,对不受信任的代码进行沙盒处理本身就很困难,并且容易被绕过。
最后,另一种补充方法是接受任意代码执行几乎是不可避免的这一事实,并通过在锁定的 Docker 容器中部署模板环境等方式应用自己的沙箱。