是道WEB0解题,在赛中时感觉快做出来了(其实没有),于是赛后重新尝试了一下,终于给搞出来了
进去是个普通页面 没有能ssti的点,但是通过源码发现了merge 就基本确定是考python的原型链污染
先来说说原型链污染的原理
在调用一个类或实例的属性时,若没有找到,则会去对应的父类查找,举个例子
这里print(son_a.secret)就是因为在son_a这个类里没有secret,去其的父类father查找,并输出father.secret
而如果我们能更改father.secret的话 相当于变相更改了son_a.secret
要更改就得利用这里自定义的merge函数
这个函数能迭代json里的对象属性
比如执行像上图的merge(payload,instance)
instance=son_b()
payload={
"__class__":{
"__base__":{
"secret":"world"
}
}
}
他实际就是通过merge函数迭代执行了instance.__class__.__base__.secret="world"
但是这道题污染的目的不是我们去污染父类来改变其子类的值,而只是单单的去更改某个值而已
直接看题吧
需要本地搭建调试
一开始根据源码
我想的是直接污染静态目录 然后将静态目录改到根目录下 这样就能直接访问到/flag
但是这里的这个static_path是被用于初始化的,也就是只在程序启动时使用过一次,然后就会在某一个实例的属性里记载static_path的值,后面每次访问静态目录时读取的都是某个实例的属性而不是static_path 所以这里污染static_path是没用的
那我们的目的就很明确了 只要找到static_path赋给了哪些属性,并且这个属性确实是每次访问静态目录时程序查找的属性就行了
通过查找文档知道处理静态路由主要是由StaticFileHandler这个处理程序来完成的
后面就是查看源码跟踪,最后会发现是在p.__init__.__globals__['app'].wildcard_router.rules[0].target_kwargs['path']里
那我们只要修改p.__init__.__globals__['app'].wildcard_router.rules[0].target_kwargs['path']='/',然后再访问http://ip:5000/static/flag就能获得根目录下的flag,不过我也不确定flag是不是在/flag
但是实际在payload的时候会有问题,原因就是在这个rules[0],因为这个rules是个对象数组,如图
里面有四个对象,每个对象里都有一个matcher属性,matcher属性也是一个对象,并且我们不能更改这个对象 否则会报错
而我们如果payload用这样
他就会覆盖掉第0索引的对象,从而导致识别不到matcher这个对象而报错,但是我们也不能在json里添加matcher对象,因为对象没法用字符串表示
所以这个方法就不行了,只能另求他路
在刚刚调试的时候,我发现了tornado似乎会对第一次render过的文件进行缓存
比如这道题他render了这个文件
但是当我去更改这个文件的时候是没有任何变化的
所以我产生了一个想法 会不会有一个实例的属性记载了这个文件的内容来当作缓存 例如在某一个实例里,有一个属性是{{handler.application.settings}},于是我每次去访问这个文件的时候他都会从属性里去读取而不是读取文件,那我就可以更改这个属性以从{{handler.application.settings}}更改为{{sstirce语句}}来触发ssti漏洞
查阅文档发现关于缓存是有一个叫_template_loaders来处理的
那我们就去这个_template_loaders里看看
有个对应的路径 感觉很符合我们的猜想 继续跟
进到templates的static/index.html里看看
发现了不得了的东西,直接有一个code变量 里面还储存着python代码
先扔给gpt问一下,说就是回显handler.application.settings的意思 这不就是在处理我们index.html内容{{handler.application.settings}}的代码吗
于是我尝试更改了这个代码,但是没有任何变化,应该也只是个用来初始化的,看到下面还有个变量compiled,意思就是编译完的
那流程猜测一下就是先识别index.html里的内容,并且给出对应的控制html内容的代码赋值给code,然后再对code进行编译,将对象放在compiled里
实际我们的源码也是这样写的
首次访问文件时,先创造对应的code,然后再用compile函数编译,之后每次访问这个文件时都会执行这个编译后的函数以达到缓存的目的
好的那问题又来了
这个compiled属性是个对象啊,又没办法用json来表示对象,只能表示字符串,那咋办呢
搞到这步已经半夜了,想了半天 还是先去睡个觉 隔天还有早八
上课的时候想着还是再看看源码 有啥利用点
一回宿舍就开始继续看代码 最后在执行的部分发现了利用点
在这一步我原以为只是执行编译后的对象,结果点进去看了一下才发现
exec_in这个函数 还会判断你的code是不是字符串,如果是编译好的对象 那当然直接执行了,但如果是字符串 他还会帮你再编译一次,然后还会送到exec里执行
所以我们的compiled是可以直接传字符串给他的
那我们就可以构造payload 注意绕过黑名单
之后访问一下htpp://ip:5000/ 让他加载一下缓存代码
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论