跳转至

SSTI注入漏洞

该技术最早由 PortSwigger 研究在会议演讲[Server-Side Template Injection: RCE for the Modern Web App](https://portswigger.net/research/server-side-template-injection)中记录。

服务器端模板注入是指攻击者能够利用本地模板语法向模板注入恶意负载,然后在服务器端执行。

模板引擎设计为通过将固定模板与动态数据结合来生成网页。当用户输入直接被拼接到模板中,而不是以数据形式传递时,可能发生服务器端模板注入攻击。这使得攻击者能够注入任意模板指令以操控模板引擎,通常能够完全控制服务器。顾名思义,服务器端模板注入Payload是在服务器端处理的,这使其可能比典型的客户端模板注入更危险。

服务器端模板注入漏洞可能会使网站暴露于各种攻击之下,具体取决于所涉及的模板引擎以及应用的具体使用方式。在某些罕见情况下,这些漏洞并不构成真正的安全风险。然而,大多数情况下,服务器端模板注入的影响可能是灾难性的。

在最严重的层面,攻击者可能实现远程代码执行,完全控制后端服务器,并利用其对内部基础设施进行其他攻击。

即使在无法实现完整远程代码执行的情况下,攻击者通常仍可利用服务器端模板注入作为多种攻击的基础,从而获得对服务器上敏感数据和任意文件的读取权限。

SSTI注入漏洞如何产生的

服务器端模板注入漏洞出现在用户输入被拼接到模板中,而非以数据形式传递时。

静态模板仅提供动态内容渲染的占位符,通常不易受到服务器端模板注入的影响。经典例子是一封邮件,会以用户的名字向他们致意,比如以下摘自 Twig 模板的片段:

PHP
$output = $twig->render("Dear {first_name},", array("first_name" => $user.first_name) );

这不会受到服务器端模板注入的漏洞,因为用户的名字只是作为数据传递到模板中。

然而,由于模板只是字符串,网页开发者有时会在渲染前直接将用户输入串接到模板中。我们举一个类似上述的例子,但这次用户可以在邮件发送前自定义邮件的部分内容。例如,他们可能能够选择所用的名字:

PHP
$output = $twig->render("Dear " . $_GET['name']);

在这个例子中,模板中不是传递静态值,而是通过 GET 参数 name 动态生成模板的一部分。由于模板语法是在服务器端评估的,这有可能让攻击者在 name 参数中放置服务器端模板注入有效载荷,具体如下:

Text Only
http://vulnerable-website.com/?name={{bad-stuff-here}}

这类漏洞有时是由于不熟悉安全影响的人设计不当的模板设计意外造成的。如上例所示,你可能会看到不同的组件,其中一些包含用户输入,被串接并嵌入到模板中。在某种程度上,这类似于写得不好的SQL语句中出现的 SQL 注入漏洞。

然而,有时这种行为实际上是有意为之。例如,有些网站故意允许某些特权用户,如内容编辑者,设计性地编辑或提交自定义模板。如果攻击者能够攻破拥有此类权限的账户,这显然构成了巨大的安全风险。

如何发现SSTI注入漏洞

识别服务器端模板注入漏洞并策划成功攻击通常涉及以下高层次流程。

Detect

服务器端模板注入漏洞往往被忽视,不是因为它们复杂,而是因为它们只有明确寻找的审计人员才会察觉。如果你能够检测到存在漏洞,利用它其实出乎意料地容易。这在非沙盒环境中尤为明显。

和任何漏洞一样,被利用的第一步是能够找到它。也许最简单的初步方法是通过注入模板表达式中常用的特殊字符序列(如 ${{<%[%'"}}%\ )来对模板进行模糊化。如果异常被触发,说明注入的模板语法可能被服务器以某种方式解释。这表明服务器端模板注入可能存在漏洞。

服务器端模板注入漏洞发生在两种不同的情境中,每种情境都需要不同的检测方法。无论你的模糊测试效果如何,也要尝试以下针对具体情境的方法。如果模糊测试效果不确定,仍可能通过这些方法暴露漏洞。即使模糊测试确实暗示模板注入漏洞,你仍然需要识别其上下文才能利用它。

明文上下文

大多数模板语言允许你自由输入内容,要么直接使用 HTML 标签,要么使用模板的原生语法,后端会先渲染成 HTML 格式,然后再发送 HTTP 响应。例如,在 Freemarker 中, render('Hello ' + username) 行会被渲染成类似 Hello Carlos 。

这有时会被利用用于 XSS,实际上常被误认为是简单的 XSS 漏洞。然而,通过将数学运算设为参数值,我们可以测试这是否也是服务器端模板注入攻击的潜在入口点。

例如,考虑一个包含以下易受攻击代码的模板:

Text Only
render('Hello ' + username)

审计时,我们可能会通过请求以下 URL 来测试服务器端模板注入:

Text Only
http://vulnerable-website.com/?username=${7*7}

如果结果输出包含 Hello 49 ,这表明数学运算是在服务器端被评估的。这是一个很好的服务器端模板注入漏洞的概念验证。

注意,成功评估数学运算所需的具体语法会因所用模板引擎而异。

代码上下文

在其他情况下,漏洞通过用户输入被置入模板表达式中暴露,正如我们之前的电子邮件示例所示。这可能表现为参数中放置一个用户可控制的变量名,例如:

Text Only
greeting = getQueryParameter('greeting')
engine.render("Hello {{"+greeting+"}}", data)

在网站上,最终的 URL 大致如下:

Text Only
http://vulnerable-website.com/?greeting=data.username

例如,这会在输出中渲染为 Hello Carlos 。

这个上下文在评估时很容易被忽略,因为它没有明显的 XSS,几乎与简单的哈希图查找无异。在此语境下测试服务器端模板注入的一种方法是,首先通过向值注入任意 HTML 来确定该参数不包含直接的 XSS 漏洞:

Text Only
http://vulnerable-website.com/?greeting=data.username<tag>

如果没有 XSS,通常会导致输出中出现空白条目(仅 Hello 且无用户名)、编码标签或错误信息。下一步是尝试用常见的模板语法跳出语句,并在后面注入任意 HTML:

Text Only
http://vulnerable-website.com/?greeting=data.username}}<tag>

如果这再次导致错误或空白输出,说明你要么用错了模板语言的语法,要么如果没有模板式语法有效,服务器端模板注入就无法实现。或者,如果输出和任意 HTML 都被正确渲染,这表明存在服务器端模板注入漏洞:

Text Only
Hello Carlos<tag>

Identify

一旦检测到模板注入可能性,下一步就是确定模板引擎。

虽然模板语言数量众多,但许多使用非常相似的语法,且被特别选择以避免与 HTML 字符冲突。因此,创建探测有效载荷以测试所使用的模板引擎相对简单。

通常只需提交无效语法就足够了,因为错误信息会准确告诉你模板引擎是什么,有时甚至是哪个版本。例如,无效表达式 <%=foobar%> 会触发基于 Ruby 的 ERB 引擎的以下响应:

Text Only
(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>'

否则,你需要手动测试不同的语言专用负载,并研究模板引擎如何解读它们。通过排除法,根据哪些语法看起来有效或无效,你可以比想象中更快地缩小选项范围。一种常见的做法是从不同模板引擎中注入任意的数学运算,使用语法。然后你可以观察这些项目是否被成功评估。为了帮助这个过程,你可以使用类似以下的决策树:

你应该注意,同一个负载有时会在多种模板语言中成功返回响应。例如,在 Twig 中返回 49 和 Jinja2 中返回 7777777 的有效载荷。因此,重要的是不要仅凭一次成功的反应就妄下结论。

Exploit

利用服务器端模板注入漏洞 |网络安全学院 --- Exploiting server-side template injection vulnerabilities | Web Security Academy

如何防御SSTI注入漏洞

防止服务器端模板注入的最佳方法是不允许任何用户修改或提交新模板。然而,由于业务需求,有时这是不可避免的。

避免引入服务器端模板注入漏洞的最简单方法之一,是除非绝对必要,否则始终使用“无逻辑”模板引擎,如 Mustache。尽可能将逻辑与展示分离,可以大大减少你遭受最危险的基于模板攻击的风险。

另一个措施是只在沙盒环境中执行用户代码,那里可能有危险的模块和功能已被完全移除。不幸的是,对不可信代码进行沙箱处理本身就很困难,也容易被绕过。

最后,另一种互补方法是接受任意代码执行几乎不可避免,并通过在封闭的 Docker 容器中部署模板环境来应用自己的沙箱。