原型对象及相关基础
在JavaScript中只有一种结构:对象,也就是常说的"万物皆对象"。
而每个实例对象都有一个原型对象,而原型对象则引申出其对应的原型对象,经过一层层的链式调用,就构成了我们常说的"原型链"。
实例对象可以通过__proto__访问其原型对象:
1 | > let obj = {}; |
而经过不断的调用,最终的原型对象会调用到null,这将作为该原型链的最后一个环节,与之对应的,作为终点的null自然也是没有原型对象的。比如,我们继续在上面的例子中调用其原型对象:
1 | > obj.__proto__.__proto__; |
在JavaScript中,可以用几种方式实现继承,常见的继承方式包括:原型链继承、借用构造函数继承、原型式继承,寄生式继承。
每种方法对应的原理及具体的实现方式不再赘述,可参见《JavaScript高级程序语言设计》中的继承相关章节。
在Javascript中可以通过example.a.b或example.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的要求为:
传入的
querytoken与user.admintoken的md5哈希值一样
因此我们可以通过上面发现的原型链的漏洞对admintoken进行赋值。那么继续探讨如何对其进行赋值。
Array实例继承自Array.prototype,因此我们可以通过更改构造函数的原型对象来对所有的Array实例进行修改。
那么我们可以通过这个思路来做到变量覆盖。
通过代码我们可以发现user为Array,matrix同样是Array,因此我们根据上面的思路,通过对matrix进行赋值进而篡改user.admintoken的值。
在控制台演示原型链污染如下:
1 | > a=[]; |
因此我们传入的payload就可以构造出来了。