原理
这篇是在复现newstar week3的otenki girl时写的,其实就是整理了一下js原型链的定义和题目注入点此类的东西。
原型链
首先解释一下js原型链,给个例子:
1 | function test(num){ |
这是声明一个test类的实例te的过程,我们首先了解一下实例的过程,也就是new的过程。
1.创建一个空的json结构
2.以这个json结构运行构造器constructor
3.将这个json结构的原型指向构造器的prototype,具体做法为将json的__proto__属性赋值为构造器的prototype
4.返回json
也就是说,实例化一个类,实际上就是创建一个json结构,然后将实例化的’原型’属性,也就是__proto__指向构造器的prototype,在上述代码里这样解释:
te.__proto__===test.prototype
也可以这样表示:
te['__proto__']===test.prototype
小特性
在js中,一个类中找不到需要访问的属性时,会转向他的原型寻找,例如:
console.log(te.constructor)
他会打印出”test”,原因是实例化,也就是new操作只继承prototype内的属性,constructor不在prototype里,也就是说te是没constructor属性的,那js就会转向他的原型里寻找,也就是te.proto === test.prototype,所以会返回
test.prototype.constructor === test.
那么知道了这一些,就可以初步开始原型链污染了,和字面意思一样,我们的目的是利用已由的实例化类来改变原型的属性,例如最开始我写的例子,我们可以
te['__proto__']['add']=123
然后我们
console.log(test.prototype.add)
输出123
题目复现
之后看otenki girl,具体题目在https://buuoj.cn/match/matches/190/challenges
结合提示我们看info.js和submit.js,先看info.js,里面比较重要的代码如下
1 | async function getInfo(timestamp) { |
第一行查看timestamp是否为数字,不是则赋值为当前时间
第二行创建miniTimestamp变量,赋值为CONFIG.min_public_time或者DEFAULT_CONFIG.min_public_time
第三行把timestamp赋值为timestamp和miniTimestamp最大的那个,也就是说这个值最小为miniTimestamp
这里去找一下CONFIG.min_public_time,查看config.js
1 | module.exports={ |
发现根本没有CONFIG.min_public_time,也就是说miniTimestamp一直会赋值为DEFAULT_CONFIG.min_public_time。
info.js最后一行是sql查找,找timestamp>=?的所有信息,那假如我们把timestamp变小,就可以获取最大数据了。
至此只是看了info.js文件,还没找到利用点,在看看submit.js,发现了可利用点
1 | const merge = (dst, src) => { |
这里是递归获取前后json都有的属性,然后把dst中属性值赋值为src的属性值,在往后看
1 | const result = await insert2db(merge(DEFAULT, data)); |
这里的result就是利用点了,结合上下两串代码,我们可以构造恶意的data属性修改原型DEFAULT_CONFIG.min_public_time的值。
在此之前寻找一下DEFAULT_CONFIG.min_public_time,发现在config.default.js中,从文件名也能看出来DEFAULT是继承于config.default的
1 | module.exports={ |
也即是说
DEFAULT['__proto__']['min_public_time']=DEFAULT_CONFIG['min_public_time']
那么我们的payload就可以设置为
1 | { |
稍微解释一下这个payload,联系meger函数
进入循环取出这个payload的键,也就是”contact”,”reason”,”__proto__“,前面两个不成立if,所以直接进入else运行
DEFAULT['contact']="test"
又因为因为DEFAULT[‘contact’]不存在,所以没有影响
同理,reason也没有影响,所以就直接看最后一个”__proto__“:
首先进入第一次if,
1 | DEFAULT["__proto__"]=merge("DEFAULT["__proto__"]", |
接着第二次if
merge("DEFAULT["__proto__"]["min_public_time"]","1001-1-30")
然后进入else,将
DEFAULT["__proto__"]["min_public_time"] = "1001-1-30"
至此,原型链污染就已经成功了,然后我们访问info路由,将数据包时间戳改成0,timestamp就赋值为1001-1-30,就可成功查询。
submit路由下的污染图片:

info路由下的查看图片:

成功拿下flag:flag{c700abcc-df5d-4222-acc9-0967cea6cd4a}