关于js中的继承关系:
原型对象和实例对象的继承关系:![](https://img-blog.csdnimg.cn/direct/c4bef35243134aff835d57c5af0004fd.png)
使用一段js代码说明继承关系:
//定义一个名为 Animal 的构造函数,它有一个原型对象,其中定义了 name 和 eat 属性
function Animal(name)
{
this.name = name;
}
Animal.prototype.eat = function(food)
{
console.log(${this.name} is eating ${food});
};
// 定义一个名为 Cat 的构造函数,它继承自 Animal
function Cat(name, color)
{
Animal.call(this, name);
this.color = color;
}
// Cat 构造函数的原型对象继承自 Animal 的原型对象
Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;
// 在 Cat 的原型对象上添加一个 catchMouse 方法
Cat.prototype.catchMouse = function()
{ console.log(${this.color} cat is catching mouse);
};
// 创建一个实例对象 tom,它继承了 Animal 和 Cat 原型对象上定义的属性和方法
var tom = new Cat("Tom", "grey");
tom.eat("cheese"); // 输出 "Tom is eating cheese"
tom.catchMouse(); // 输出 "grey cat is catching mouse"
上述代码中创建了一个名为Animal的构造函数Animal.prototype.eat是他的一个原型对象具有“eat”属性。
之后定义了一个名为Cat的构造函数拥有“name”和“color”两种属性,Animal.call(this, name); 此处表示Cat函数的“name”属性
是继承了Animal的“name”属性而不是自己创建的单独属性。
Cat.prototype = Object.create(Animal.prototype); 这句话让Cat的原型指向Animal的原型Object.create是创建一个新的对象,并让该对象的原型指向Animal的原型。为什么不直接写”Cat.prototype = Animal.prototype;“?这样一来Cat的原型指向Animal的原型也可以实现Cat创建的实例对象继承Animal的的原型对象的属性,但是这样操作就无法保证Cat.prototype和Animal.prototype之间隔离,也就是说当修改Cat.prototype的属性时,Animal.prototype的属性也会随之修改,所以要使用Object.create来创建新的对象起到隔离两个构造函数的作用。
Cat.prototype.constructor = Cat; 用于将Cat.prototype的construct属性指向Cat。因为在Cat.prototype = Object.create(Animal.prototype);之后Cat.prototype的construct属性是指向Animal的,为了让其重新指向Cat本身,需要改代码进行修正。
简单一点的例子:
// 定义一个名为 Animal 的构造函数,它有一个原型对象,其中定义了 name 和 eat 属性
function Animal(name)
{
this.name = name;
}
Animal.prototype.eat = function(food)
{
console.log(${this.name} is eating ${food}
);
};
// 创建一个实例对象 cat,它继承了 Animal 原型对象上的属性和方法
var cat = new Animal("kitty");
cat.eat("fish"); // 输出 "kitty is eating fish"
// 在 Animal 的原型对象上添加一个 sayHi 方法
Animal.prototype.sayHi = function()
{
console.log(Hi, my name is ${this.name}
);
};
// cat 现在可以调用 Animal 原型对象上添加的 sayHi 方法
cat.sayHi(); // 输出 "Hi, my name is kitty"
代码先构造了一个构造函数Animal,拥有一个属性“name”。接下来他有一个原型对象,该对象定义了一个“eat”的方法
接着用new创建一个实例对象cat,由于cat是由Animal构造出来的,所以cat继承了Animal的属性“name”和方法“eat”
接着又在Animal的原型上加一个方法“sayHi”,同样的,cat也能使用该方法。
什么是原型链:
一个构造函数可以作为原型对象去构造一个实例对象,而他所构造的实例对象也可以作为一个原型对象去构造另一个实例对象。
每一个实例对象都指向他的原型对象,直到指向第一个构造函数为止。这一连串的指向像一个链表,就是原型链。
即构造函数构造了“实例1”,则“实例1”的prototype就是构造函数,“实例1”作为一个原型对象再构造其他的实例“实例2”,则“实例2”的prototype就是“实例1”以此类推,直到“实例n”的prototype指向“实例n-1”当我们要从“实例n”中找一个属性而该属性却只存在于第一个构造函数,那么程序将一直遍历,从“实例n”遍历到构造函数直到在构造函数里找到该属性。
原型链是如何污染的:
prototype和proto的关系:prototype是每个函数对象独有的属性,他指向当前函数的原型,而proto是每个对象都有的属性,他指向当前实例对象的原型。
举例:
function Person() {}
// Person 的 prototype 对象
Person.prototype
// 实例对象的原型对象
const person = new Person();
person.proto;
显而易见,在该js代码中,person是原型对象Person的实例对象,那么Person.prototype=person.proto;
注意在真实情况中proto应该写成这样_proto_
要在两边加上下划线。
简单的污染演示:
user1 = {"id":123,"name":york}; user1.proto.password = "123456"; console.log(user1.password); user2 = {"id":456,"name":glas}; console.log(user2.password);
第一个输出可以输出id为123的用户的密码(此处的password只是一种对象的方法)
而第二个输出任然可以输出id为123的用户的密码,因为程序为user1创建了一个原型对象的方法“password”
当user2调用password时,找不到该方法就会寻找其原型,而有一个原型正好有password方法,于是user2就成为了user1.proto的实例对象。于是user2也可以调用password。
利用漏洞:
有三种情况可使用原型链污染
一,js的对象递归合并:
// 目标对象
const originalObj = {};
//被修改的对象,具有恶意语句
const maliciousObject = {
toString() {
return "恶意内容"; }
};
// 将恶意对象的原型链指向原始对象 (函数将两个对象合并)
Object.setPrototypeOf(originalObj, maliciousObject);
console.log(originalObj); 完成而已内容调用。
二,按路径定义属性:
// 目标对象
const target = {};
// 由攻击者掌握的对象
const attacker = { evilProperty: 'evil' };
// 通过 proto 属性将目标对象的原型设置为攻击者控制的对象
target.proto = attacker; (或者Object.setPrototypeOf(target, attacker);)
// 此时通过按路径定义属性,为目标对象创建新属性
target.evilProperty = 'Oops! ';
// 访问污染后的属性值 console.log(target.evilProperty);
// 输出"Oops!"
console.log(attacker.evilProperty); // 输出"evil"
三,对象克隆:
const obj1 = { name: 'Alice' };
const obj2 = { age: 20 };
obj1.proto = obj2;
const cloneObj = Object.assign({}, obj1); //使用assign函数进行拷贝
// 克隆后的对象任然留有原对象的继承性
console.log(cloneObj.name); //继承原型对象的name属性
console.log(cloneObj.age); //继承原型对象的age属性
// 添加新属性
cloneObj.proto.danger = 'evil';
console.log(obj1.danger);
实例:
以攻防世界的题库中的wife-wife为例
展示源代码及注释:
看到Object.assign直到这里有复制函数,那么在这里会出现原型链污染。
所以在使用抓包时
添加proto,此处创建的时新用户,但新用户拥有其他实例对象的属性,那么新用户的proto也是管理员的原型对象,可以直接使用isAdmin这个属性,前提是需要用proto追溯到newuser的原型对象。
实例二:
以ctfshow的web338题为例
源代码:
在代码中,只有secert.ctfshow等于字符串“36dboy”才可以,看源代码,不难发现存在copy函数,因此漏洞就在这里,把req.body复制给了user,此时user与secert拥有相同的原型对象,那么user.proto.ctfshow 就可以被使用。
那么只需要在数据包中写入{"proto":{"ctfshow":"36dboy"}}即可,该payload会被copy函数调用赋值给user,让user变成user.proto.ctfshow:36dboy。