继承与原型链
在 JavaScript 中只有一种结构:对象(连函数也是一种对象)。
每个对象都有一个私有属性,指向另一个名为 “原型”(prototype)的对象。
原型对象也有自己的一个原型,一层一层的直到一个对象的原型为 NULL,Null 处于原型链的顶端,或者说原型链的最后一环。
继承属性
JavaScript 对象有一个指向一个原型对象的链。
当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。
__proto__和 prototype
在 JavaScript
中,每个对象都有一个名为__proto__
的内置属性,它指向该对象的原型,类似于指针概念。
同时,每个函数也都有一个名为 prototype
(原型)的属性,它是一个对象,包含构造函数的原型对象应该具有的属性和方法。
下面是一段代码举例
function Person(name) { this.name = name; }//一个带参构造函数,传入name并赋值 Person.prototype.greet = function() { console.log(`Hello, my name is ${this.name}`);//将prototype上的greet设置为一个打招呼的函数 }; const person1 = new Person('Alice'); person1.greet(); // 输出 "Hello, my name is Alice"
当创建了 person1 的时候,person1 里并没有 greet,他会沿着原型链搜索并继承 prototype 中的 greet 函数。
看看这段代码的执行情况
从这里就可以看出,prototype
是类 Person
的一个属性,所有用类 Person
进行实例化的对象,都会拥有 prototype
的全部内容。
总结:
1、prototype是一个类的属性,所有类对象在实例化的时候将会拥有prototype中的属性和方法 2、一个对象的__proto__属性,指向这个对象所在的类的prototype属性
我在网上找了两张图
这一张是关于__proto__
可以看到 f1 的__proto__指向了 Foo.prototype,因为 f1 是从 Foo 这个函数里 new 的,所以他的原型是 foo
Foo () 的__proto__指向了 Function.prototype,因为他是一个函数,所以指向的函数的原型
下面这张图是关于 prototype 的
关于 prototype,它是函数所独有的,它是从一个函数指向一个对象,它的含义是函数的原型对象
__proto__和 prototype 的关系可以用下图表示
原型链污染
先举个例子
var a = {number : 520} var b = {number : 1314} b.__proto__.number=520 var c= {} c.number
这段代码的执行情况如下
这段代码中的 C 我是没有赋值的,但是执行出来是 520
原因是,b 的 proto 其实指向的就是 object 的 prototype
在这里将 object 类里所有对象的 number 属性的值都设置为了 520
效果就如同下面这张图
但是这时输出 b 的值就是 1314,因为在 b 自己的属性中能够找到 number 属性
系统只有在找不到的时候才会沿着原型链搜索,
下面来实战演练一下
CatCTF 2022 wife
源码如下:
app.post('/register', (req, res) => { let user = JSON.parse(req.body) if (!user.username || !user.password) { return res.json({ msg: 'empty username or password', err: true }) } if (users.filter(u => u.username == user.username).length) { return res.json({ msg: 'username already exists', err: true }) } if (user.isAdmin && user.inviteCode != INVITE_CODE) { user.isAdmin = false return res.json({ msg: 'invalid invite code', err: true }) } let newUser = Object.assign({}, baseUser, user) users.push(newUser) res.json({ msg: 'user created successfully', err: false }) })
这里可以利用的点就是 object.assign 这个函数
Object.assign()
方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
Object.assign(target, source1,source2,source3…..)
该函数从 source1 到 sourceN 中将属性添加或覆盖到 target 中
const object1 = {
a: 1, b: 2, c: 3 }; const object2 = Object.assign({c: 4, d: 5}, object1); console.log(object2.c, object2.d); // expected output: 3 5相当于是括号内所有变量的集合体组成的object2
很明显这道题是要获得 admin 权限,但是源码里设置了 isAdmin 为 false,就要进行原型链污染
抓个包如下
可以使用 proto 将所有类的 isAdmin 设置为 true
那么随便注册一个用户的时候都能通过验证
关键就在于
"__proto__":{"isAdmin":true}
参考链接:
浅析 CTF 中的 Node.js 原型链污染 – FreeBuf 网络安全行业门户
帮你彻底搞懂 JS 中的 prototype、__proto__与 constructor(图解)_码飞_CC 的博客 - CSDN 博客
prototype 与__proto__的区别和关系_prototype 和__proto___白小白灬的博客 - CSDN 博客