一、nodejs 和 js 的区别

​ js(JavaScript)是一种高级的、解释型的编程语言;它是一门基于原型、函数先行的语言,是一门多范式的语言,它支持面向对象编程,命令式编程,以及函数式编程。

​ Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境,是一个让 JavaScript 运行在服务端的开发平台,它让 JavaScript 成为与PHP、Python、Perl、Ruby 等服务端语言平起平坐的脚本语言。

二、prototype 和 __proto__

function Foo() {
    this.bar = 1
}
new Foo()

在js中,定义一个类就是定义它的构造函数

这里Foo类的构造函数就是Foo函数

image-20220216190155055

这张图中,先定义了一个foo类,并使用了原型prototype定义了一个show属性,下面新实例的对象就具有了这个show属性

我们可以认为原型prototype是类Foo的一个属性,而所有用Foo类实例化的对象,都将拥有这个属性中的所有内容,包括变量和方法。比

如上图中的foo对象,其天生就具有foo.show()方法。

我们可以通过Foo.prototype来访问Foo类的原型,但Foo实例化出来的对象,是不能通过prototype访问原型的。

这时候,就要用到 proto

例如Foo类的对象foo:

Foo.prototype == foo._proto_

这里总结一下:

所有类都有一个prototype属性,实例化的对象就会拥有这个prototype中的属性和方法

而实例化的对象可以通过 __proto__属性,指向类的prototype属性

三、什么是原型链

上面说了,所有对象都可以通过__proto__属性来访问自身类的prototype

而js中,万物皆是对象,所以各个对象之间就有一条用__proto__连起来的的链条

下图就清晰易懂

image-20220216190549269

下面这个例子讲解了prototype继承链的机制:

image-20220216190613017

最终输出: Name:Melania Trump

首先将Son的prototype指向了Father类的对象,然后在输出son.last_name 时,js引擎先在son对象中查找,如果找不到就在son.__proto__上查找,直到null

这就是prototype继承链

四、原型链污染是什么?

上面解释了原型链,这里使用例子来解释原型链污染

image-20220216190732722

image-20220216190746042

image-20220216190759383

可以看到,我们首先定义了一个foo对象,foo的prototype(原型)是object,当我们修改了object的属性后,再定义一个对象qoo时,可以发现它的原型多了一个 bar:2,即qoo的原型链被污染了

所以,如果攻击者控制和修改了一个对象的原型,那么所有继承这个原型的对象都会拥有修改后的属性,这个攻击称为原型链污染。

五、实例

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]
        }
    }
}

可以看到merge方法中存在赋值操作:target[key] = source[key]

若这个key == _proto_ ,是不是就能造成原型链污染了呢?

image-20220216192327363

这个例子中,首先定义了两个对象o1、o2,使用merge方法后,可以看到o1的值被成功覆盖,但是原型链并没有被污染

因为在定义的o2中,”_proto_”不被认为是一个键,而是直接代表了o2的原型

image-20220216193346016

也就是说,这里只是更改了o2的原型,并不是将其污染,因此其他对象继承的object并没有被污染

下面将代码修改成这样:

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)

o3.b 也输出 2

因为在JSON.parse的作用下,”_proto_”也被认为是一个键,从而污染了原型链

实例之CTF题:

来自VNCTF2022的一道:newcalc0

给了源码

image-20220216193913345

image-20220216194002354

这里获取flag需要满足条件 Object.keys(Object.prototype).length > 0,也就是要污染Object

而下面的foreach遍历将Object的原型中的数据删完了,因为在污染了原型链之后,除非重启,这个被污染的原型链将一直存在,所以需要遍历删除数据

官方playload:console.table([{a:1}],['__proto__'])

https://nodejs.org/zh-cn/blog/vulnerability/jan-2022-security-releases/#incorrect-handling-of-certificate-subject-and-issuer-fields-medium-cve-2021-44533

学习一下:

“Node.js 修复的第四个漏洞是低危漏洞 (CVE-2022-21824),可导致攻击者通过 console.table 属性污染原型。鉴于Console.table() 函数的格式化逻辑,以下做法并不安全:允许受用户控制的输入被传递给属性参数,同时将具有至少一个属性的对象当作第一个参数 (_proto_) 进行传递。 该原型污染漏洞产生的影响有限,因为它仅允许将空白字符串分配给对象原型的数字键。”

说实话,研究了半天,不是很理解这个是什么意思,但最终的结果就是能污染原型链。