原型对象及相关基础

在JavaScript中只有一种结构:对象,也就是常说的"万物皆对象"。

而每个实例对象都有一个原型对象,而原型对象则引申出其对应的原型对象,经过一层层的链式调用,就构成了我们常说的"原型链"。

实例对象可以通过__proto__访问其原型对象:

1
2
3
4
>  let obj = {};
undefined
> obj.__proto__;
<· {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}

而经过不断的调用,最终的原型对象会调用到null,这将作为该原型链的最后一个环节,与之对应的,作为终点的null自然也是没有原型对象的。比如,我们继续在上面的例子中调用其原型对象:

1
2
>  obj.__proto__.__proto__;
null

在JavaScript中,可以用几种方式实现继承,常见的继承方式包括:原型链继承借用构造函数继承原型式继承,寄生式继承

每种方法对应的原理及具体的实现方式不再赘述,可参见《JavaScript高级程序语言设计》中的继承相关章节。

在Javascript中可以通过example.a.bexample.a["b"]来对数组进行访问,example.a访问的是example对象下的a对象的b,而example.a["b"]则是访问example对象下的a数组的下标为b的值。

由于对象是无序的,当使用第二种方式访问对象时,只能使用指明下标的方式去访问。

因此我们可以通过a["__proto__"]的方式去访问其原型对象。

实例

在Hackit 2018中,有一个nodejs的题目,其中便涉及到了使用原型链继承来进行变量覆盖的攻击手法,源码我已同步至我的Github项目中CTF-Challenge

其中漏洞点在第64行:

1
matrix[client.row][client.col] = client.data;

data是从网页传递的参数,形如:

1
{"row":1,"col":"1","data":"X"}

对应的row以及col值会存放至matrix中,而这里则会导致一个原型链污染的隐患。

回到题目本身的逻辑中,获得Flag的要求为:

传入的querytokenuser.admintoken的md5哈希值一样

因此我们可以通过上面发现的原型链的漏洞对admintoken进行赋值。那么继续探讨如何对其进行赋值。

Array实例继承自Array.prototype,因此我们可以通过更改构造函数的原型对象来对所有的Array实例进行修改。

那么我们可以通过这个思路来做到变量覆盖。

通过代码我们可以发现userArray,matrix同样是Array,因此我们根据上面的思路,通过对matrix进行赋值进而篡改user.admintoken的值。

在控制台演示原型链污染如下:

1
2
3
4
5
6
7
8
9
10
11
12
>  a=[];
<· []
> b=[];
<· []
> b["__proto__"];
<· [constructor: ƒ, concat: ƒ, find: ƒ, findIndex: ƒ, pop: ƒ, …]
> b["__proto__"]["admintoken"]="ccda";
<· "ccda"
> a
<· []
> a.admintoken
<· "ccda"

因此我们传入的payload就可以构造出来了。