最近做ctf时遇到一个模板注入的题,才想到以前都是直接在网上找payload,并没有仔细研究过ssti。本文从几个方面来细说ssti并应用实例来加深理解。
一.什么是服务器端模板注入?
服务器端模板注入是指攻击者能够使用本机模板语法将恶意有效负载注入模板中,然后在服务器端执行该模板。
模板引擎旨在通过将固定模板与易失性数据结合来生成网页。当用户输入直接连接到模板而不是作为数据传递时,可能会发生服务器端模板注入攻击。这使攻击者可以注入任意模板指令以操纵模板引擎,从而经常使攻击者能够完全控制服务器。顾名思义,服务器端模板注入有效负载是在服务器端交付和评估的,这可能使它们比典型的客户端模板注入更加危险。
二.服务器端模板注入有什么影响?
服务器端模板注入漏洞可能使网站遭受各种攻击,具体取决于所讨论的模板引擎以及应用程序使用它的方式。在某些罕见情况下,这些漏洞不会带来真正的安全风险。但是,在大多数情况下, 服务器端模板注入的影响可能是灾难性的。
在规模最大的一端,攻击者可以潜在地实现远程代码执行,从而完全控制后端服务器,并使用它对内部基础结构进行其他攻击。
即使在无法完全执行远程代码的情况下,攻击者通常仍可以使用服务器端模板注入作为其他众多攻击的基础,从而有可能获得对服务器上敏感数据和任意文件的读取权限。
三.服务器端模板注入漏洞如何产生?
当用户输入连接到模板中而不是作为数据传递时,服务器端模板注入漏洞就会出现。
仅提供占位符并在其中呈现动态内容的静态模板通常不容易受到服务器端模板注入的攻击。经典示例是一封电子邮件,其中用每个用户的名字向他们致意,例如以下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注入漏洞。
但是,有时这种行为实际上是有意实施的。例如,某些网站故意允许某些特权用户(例如内容编辑器)通过设计来编辑或提交自定义模板。如果攻击者能够利用这种特权来破坏帐户,则显然会带来巨大的安全风险。
四.构建服务器端模板注入攻击
确定服务器端模板注入漏洞并成功进行攻击通常涉及以下高级过程。
4.1 检测
通常不会注意到服务器端模板注入漏洞,这不是因为它们很复杂,而是因为它们只对明确寻找它们的审计人员真正明显。如果您能够检测到存在漏洞,则利用它非常容易。在非沙盒环境中尤其如此。
与任何漏洞一样,利用漏洞的第一步就是能够找到它。也许最简单的初始方法是通过注入模板表达式中常用的特殊字符序列来使模板模糊,例如${{<%[%'"}}%\
。如果引发异常,则表明服务器可能以某种方式解释了注入的模板语法。这表明服务器端模板注入可能存在漏洞。
服务器端模板注入漏洞发生在两个不同的上下文中,每个上下文都需要使用自己的检测方法。无论您进行模糊测试的结果如何,都必须尝试以下特定于上下文的方法,这一点很重要。如果模糊测试尚无定论,则漏洞可能仍会使用这些方法之一显示出来。即使模糊测试确实暗示了模板注入漏洞,您仍然需要确定其上下文才能加以利用。
4.2 纯文本上下文
大多数模板语言都允许您直接使用HTML标记或使用模板的本机语法自由输入内容,这些模板将在发送HTTP响应之前在后端呈现为HTML。例如,在Freemarker中,该行将render('Hello ' + username)
呈现为Hello Carlos
。
有时可以将其用于XSS,实际上经常被误认为是简单的XSS漏洞。但是,通过将数学运算设置为参数的值,我们可以测试这是否也是服务器端模板注入攻击的潜在入口点。
例如,考虑一个包含以下易受攻击的代码的模板:
render('Hello ' + username)
在审核期间,我们可能会通过请求URL来测试服务器端模板的注入,例如:
http://vulnerable-website.com/?username=${7*7}
如果结果输出包含Hello 49
,则表明正在服务器端评估数学运算。这是服务器端模板注入漏洞的良好概念证明。请注意,成功评估数学运算所需的特定语法将根据所使用的模板引擎而有所不同。
4.3 代码上下文
在其他情况下,该漏洞是通过将用户输入放置在模板表达式中来暴露的,正如我们之前在电子邮件示例中所看到的。这可以采用将用户可控制的变量名放置在参数中的形式,例如:
greeting = getQueryParameter('greeting')engine.render("Hello {{"+greeting+"}}", data)
在网站上,生成的URL类似于:
http://vulnerable-website.com/?greeting=data.username
例如,这将在输出到中呈现Hello Carlos
。
在评估期间很容易错过此上下文,因为它不会导致明显的XSS,并且与简单的哈希映射查找几乎没有区别。在这种情况下,测试服务器端模板注入的一种方法是,通过将任意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>
4.4 识别
一旦检测到模板注入潜力,下一步就是确定模板引擎。
尽管有大量的模板语言,但是其中许多模板使用非常相似的语法,而这些语法是专门为不与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>'
否则,您将需要手动测试特定于语言的不同有效负载,并研究模板引擎如何解释它们。使用基于哪个语法似乎有效或无效的消除方法,可以比您想象的更快地缩小选项的范围。常用的方法是使用来自不同模板引擎的语法注入任意数学运算。然后,您可以观察它们是否被成功评估。为了帮助完成此过程,可以使用类似于以下内容的决策树:
相同的有效负载有时可能会以一种以上的模板语言返回成功的响应。例如有效负载{{7*'7'}}
在Twig中返回49
,在Jinja2中返回7777777
。
五.利用
5.1 基本服务器端模板注入
点击第一个产品,发现是GET请求传递参数message,同时由题目知道这是ERB模板,百度知道通过此语法<%= someExpression %>
将提交的结果显示在页面上。
测试<%=7*7%>,显示在页面中为49,表明我可能具有服务器端模板注入漏洞:
从Ruby文档中发现system()
方法,该方法可用于执行任意操作系统命令:
成功删除指定文件。
5.2 基本服务器端模板注入(代码上下文)
登录账户,有个Preferred name
的功能,抓包可看到post数据请求设置参数的值blog-post-author-display
,当您加载包含评论的页面时,评论上方的名称将根据该参数的当前值进行更新。
百度Tornado模板表达式语法{{someExpression}}
,通过构造post数据:
重新加载评论页面查看自己的用户名,表明代码上下文中可能存在服务器端模板注入漏洞:
Tornado文档中,确定用于执行任意Python的语法:
{% somePython %}
可以通过导入os
模块,您可以使用该system()
方法执行任意系统命令:
{% import os %}
{{os.system('rm /home/carlos/morale.txt')
同样通过构造:
重新加载评论界面即可执行指定命令,如果失败尝试payload用url编码后重新发包:
5.3 使用文档的服务器端模板注入
登录账户,编辑产品,可查看到此模板使用的语法为${someExpression}
。为证明,更改现有表达式之一以引用不存在的对象(例如)${foobar}
,然后保存模板。输出中的错误消息表明正在使用Freemarker模板引擎。
研究Freemarker文档,发现附录包含一个常见问题部分,其问题为“我可以允许用户上传模板,并且对安全有何影响?”。答案描述了new()
内置的危险。转到文档的“内置参考”部分,并找到的条目new()
。本条目进一步描述new()
了安全问题,因为它可用于创建实现该TemplateModel
接口的任意Java对象。加载TemplateModel
该类的JavaDoc ,并查看“所有已知的实现类”列表。观察到有一个名为的类Execute
,该类可用于执行任意的shell命令。尝试构建漏洞利用程序,并进行如下调整:
<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("rm /home/carlos/morale.txt") }
删除先前输入的无效语法,然后将新的有效负载插入模板。保存并重新查看加载成功执行payload。
六.如何防止服务器端模板注入漏洞
防止服务器端模板注入的最佳方法是不允许任何用户修改或提交新模板。但是,由于业务需求,有时这是不可避免的。
避免引入服务器端模板注入漏洞的最简单方法之一是,除非绝对必要,否则始终使用“无逻辑”模板引擎,例如Mustache。尽可能将逻辑与表示分离开来,可以大大减少您遭受最危险的基于模板的攻击的风险。
另一措施是仅在已完全删除潜在危险模块和功能的沙盒环境中执行用户的代码。不幸的是,对不受信任的代码进行沙箱处理固有地困难,并且容易被绕过。
最后,另一种补充方法是接受几乎不可避免的任意代码执行,并通过在例如锁定的Docker容器中部署模板环境来应用自己的沙箱。