XSS注入之原型链污染

admin 2024年11月27日23:13:09评论11 views字数 11038阅读36分47秒阅读模式

前置知识

原型对象

在JavaScript中,每个函数都有一个特殊的属性prototype,它指向一个对象,这个对象就是所谓的原型对象。原型对象是一个普通的JavaScript对象,它包含着一些属性和方法,这些属性和方法可以被该函数的所有实例共享。

当使用new操作符来创建一个实例对象时,实例对象会继承它所属的构造函数的原型对象中的属性和方法。实例对象可以通过原型链访问原型对象中的属性和方法。如果实例对象需要访问的属性或方法在自身上找不到,它就会沿着原型链向上查找,直到找到为止。

以下是一个简单的示例,演示了如何使用原型对象:

// 定义一个构造函数
function Person(name, age) {
 this.name = name;
 this.age = age;
}

// 在原型对象上定义一个方法
Person.prototype.sayHello = function() {
 console.log('Hello, my name is ' + this.name + ' and I am ' + this.age + ' years old.');
};

// 创建两个实例对象
var person1 = new Person('Alice', 25);
var person2 = new Person('Bob', 30);

// 调用实例对象上的方法
person1.sayHello(); // Hello, my name is Alice and I am 25 years old.
person2.sayHello(); // Hello, my name is Bob and I am 30 years old.

// 查看实例对象的原型
console.log(person1.__proto__ === Person.prototype); // true
console.log(person2.__proto__ === Person.prototype); // true

// 查看原型对象的构造函数
console.log(Person.prototype.constructor === Person); // true

prototype和__proto__的区别

在 JavaScript 中,每个对象都有一个 __proto__ 属性,它指向该对象的原型。原型是一个对象,也可以有自己的原型,这样就形成了一个原型链。同时,每个函数也有一个 prototype 属性,它是一个对象,当该函数作为构造函数创建实例时,实例对象的 __proto__ 属性会指向该构造函数的 prototype 属性,这样就可以实现属性和方法的继承。

下面是 prototype__proto__ 的区别:

  1. prototype 属性是函数所独有的,而 __proto__ 属性是每个对象都有的。

  2. prototype 属性指向一个对象,它是用来存储属性和方法,这些属性和方法可以被该函数的实例对象所继承。而 __proto__ 属性指向该对象的原型,它是用来实现对象之间的继承。

下面是一些示例代码,可以帮助理解 prototype__proto__

// 定义一个构造函数
function Person(name, age) {
 this.name = name;
 this.age = age;
}

// 在 Person 的原型上定义一个方法
Person.prototype.sayHello = function() {
 console.log('Hello, my name is ' + this.name + '.');
}

// 创建一个 Person 实例对象
var person = new Person('Alice', 20);

// 输出实例对象的属性和方法
console.log(person.name);  // "Alice"
console.log(person.age);   // 20
person.sayHello();         // "Hello, my name is Alice."

// 输出 Person 和实例对象的 __proto__ 属性
console.log(Person.__proto__);     // [Function]
console.log(person.__proto__);    // Person { sayHello: [Function] }

// 输出 Person 和实例对象的 prototype 属性
console.log(Person.prototype);    // Person { sayHello: [Function] }
console.log(person.prototype);   // undefined

从上面的示例代码可以看出,prototype 属性被用于在函数的原型上定义方法和属性,而 __proto__ 属性被用于实现实例对象和构造函数之间的继承关系。

原型链概念

当创建一个 JavaScript 对象时,它都会有一个原型对象(也称为“proto”)。可以通过该对象来访问该对象的属性和方法。如果该对象本身没有定义某个属性或方法,那么 JavaScript 会在原型对象中查找,如果还没有找到,会继续查找原型对象的原型对象,直到找到 Object 的原型对象为止。

以下是一个简单的例子来演示原型链的概念:

// 定义一个构造函数
function Person(name, age) {
 this.name = name;
 this.age = age;
}

// 在构造函数的原型对象上定义一个方法
Person.prototype.sayHello = function() {
 console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
};

// 创建一个 Person 实例
const person1 = new Person('Alice', 25);

// 调用该实例的 sayHello 方法
person1.sayHello(); // 输出:Hello, my name is Alice and I am 25 years old.

// 查看该实例的原型对象
console.log(person1.__proto__); // 输出:{ sayHello: [Function] }

// 查看该实例的原型对象的原型对象
console.log(person1.__proto__.__proto__); // 输出:{ constructor: [Function: Object], ... }

在上面的例子中,定义了一个 Person 构造函数,该构造函数有两个属性 nameage,并在构造函数的原型对象上定义了一个方法 sayHello。然后,创建了一个 person1 实例,并调用了该实例的 sayHello 方法。当调用 person1.sayHello() 时,JavaScript 在该实例对象中查找该方法,但是它发现该对象本身没有定义该方法,因此它会查找该对象的原型对象,即 Person.prototype,在该原型对象中找到了该方法并执行。这就是原型链的概念。

原型链的继承

基于原型链的继承:

// 定义一个父类
function Animal(name) {
 this.name = name;
}

Animal.prototype.sayName = function() {
 console.log(this.name);
};

// 定义一个子类
function Dog(name, breed) {
 this.breed = breed;
 Animal.call(this, name); // 调用父类的构造函数来初始化属性
}

// 设置子类的原型继承自父类
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

// 子类可以使用父类的方法和属性
var dog = new Dog('Tommy', 'Golden Retriever');
dog.sayName(); // "Tommy"
console.log(dog.breed); // "Golden Retriever"

基于对象的继承:

// 定义一个父对象
var animal = {
 name: '',
 sayName: function() {
   console.log(this.name);
}
};

// 定义一个子对象
var dog = Object.create(animal);
dog.breed = '';

// 子对象可以使用父对象的方法和属性
dog.name = 'Tommy';
dog.breed = 'Golden Retriever';
dog.sayName(); // "Tommy"
console.log(dog.breed); // "Golden Retriever"

原型链污染(start)

当在 JavaScript 中使用不可信的数据时,可能会导致原型链污染问题。以下是一个示例代码,演示了如何利用原型链污染来执行恶意代码:

// 从不受信任的数据创建一个 JavaScript 对象
const user = JSON.parse('{"__proto__": {"isAdmin": true}}');

// 恶意代码:修改所有对象的 toString 方法,以便执行任意的 JavaScript 代码
Object.prototype.toString = function() {
 if (this.isAdmin) {
   // 恶意代码:执行任意的 JavaScript 代码
   console.log('恶意代码:您的计算机已被攻陷!');
   // 恶意代码:发送您的敏感数据到黑客服务器
   console.log('恶意代码:正在发送您的银行卡信息和密码到黑客服务器!');
}
 // 恶意代码:调用 toString 原始实现
 return '[object Object]';
};

// 在不知情的情况下,调用 toString 方法
console.log(user.toString()); // 输出:恶意代码:您的计算机已被攻陷!恶意代码:正在发送您的银行卡信息和密码到黑客服务器![object Object]

在上面的代码中,从一个不可信的数据源(例如一个恶意网站)解析出一个 JavaScript 对象 user,其中该对象的 __proto__ 属性被设置为一个新的原型对象,该原型对象有一个 isAdmin 属性,值为 true。接下来,在 Object.prototype 上修改了 toString 方法,以便执行任意的 JavaScript 代码。最后,调用 user.toString() 方法,JavaScript 沿着原型链查找该方法,并在 user 对象的原型对象中找到了它,导致恶意代码被执行。

因此,原型链污染是一种严重的安全问题,可能会导致代码执行任意的 JavaScript 代码。

原型链污染原理

在 JavaScript 中,每个对象都有一个原型对象,当访问一个对象的属性时,JavaScript 会沿着该对象的原型链向上查找,直到找到该属性或者到达原型链的顶端为止。原型链污染是指攻击者通过修改对象的原型链来实现对代码的攻击。

下面是一个简单的示例,演示了原型链污染的基本原理:

// 定义一个基础对象,包含一个 getName 方法
const baseObj = {
 getName: function() {
   return 'I am a base object';
}
};

// 定义一个恶意对象,通过原型链污染修改了 getName 方法
const maliciousObj = {
 getName: function() {
   return 'I am a malicious object';
}
};
maliciousObj.__proto__ = baseObj; // 修改原型对象为 baseObj

// 创建一个普通的对象,并访问 getName 方法
const normalObj = {};
console.log(normalObj.getName()); // 输出:Uncaught TypeError: normalObj.getName is not a function

// 修改 normalObj 对象的原型为 maliciousObj
Object.setPrototypeOf(normalObj, maliciousObj);

// 此时再次访问 getName 方法,输出被污染的内容
console.log(normalObj.getName()); // 输出:I am a malicious object

在上面的示例中,定义了一个基础对象 baseObj,它包含一个 getName 方法,该方法返回一个固定的字符串。接下来,定义了一个恶意对象 maliciousObj,该对象同样包含一个 getName 方法,但它返回的字符串不同,并且将 __proto__ 属性设置为 base

Obj 对象,从而使 maliciousObj 对象的原型指向了 baseObj。接下来,创建了一个普通的对象 normalObj,该对象在原型链上并没有定义 getName 方法。然后,使用 Object.setPrototypeOf 方法将 normalObj 的原型设置为 maliciousObj,从而在 normalObj 对象上成功实现了原型链污染。最后,调用 normalObj.getName() 方法时,JavaScript 沿着 normalObj 对象的原型链查找到了 maliciousObj 对象,并返回了被污染的 getName 方法的内容。

javascript中可能会存在原型链污染的危险函数

merge 函数:

当使用 merge 函数合并两个对象时,如果这两个对象都有相同的属性,那么 merge 函数会将目标对象中的属性值替换为源对象中的属性值。然而,如果这些属性的值是对象或者数组,那么 merge 函数并不会复制它们的值,而是将它们的引用复制给了目标对象。这样,如果修改目标对象中的这些属性,那么源对象中的这些属性也会被修改,进而污染了原型链。

下面是一个示例代码,它展示了如何使用 merge 函数修改全局作用域中的对象,从而污染了原型链:

// 定义一个全局变量
var globalObj = {};

// 在 globalObj 的原型上定义一个属性
Object.prototype.globalProp = 'global property';

// 合并一个带有属性的对象到全局变量中
_.merge(globalObj, {
 myProp: 'my property'
});

// 修改全局变量中的属性
globalObj.globalProp = 'modified global property';

// 输出全局变量中的属性和原型链上的属性
console.log(globalObj.myProp);      // "my property"
console.log(globalObj.globalProp);  // "modified global property"

// 创建一个对象并继承自全局变量
var obj = Object.create(globalObj);

// 输出继承自全局变量的对象的属性和原型链上的属性
console.log(obj.myProp);            // "my property"
console.log(obj.globalProp);        // "modified global property"

从上面的示例代码可以看出,由于 merge 函数修改了全局变量中的属性,因此继承自全局变量的对象也受到了污染。

clone函数:

在JavaScript中,对象可以被复制或克隆,有时候需要将一个对象的属性和方法复制到另一个对象中。通常会使用一些函数库提供的clone函数来实现对象的复制,但是有些clone函数实现方式可能会造成原型链污染的风险。

原型链污染的风险在于,攻击者可以通过污染原型对象,从而控制对象的行为并进行恶意操作。

下面是一个clone函数的示例,它使用了Object.assign函数来实现对象的复制:

function cloneObject(source) {
return Object.assign({}, source);
}

这个cloneObject函数实现了对象的复制,它接受一个源对象source作为参数,然后返回一个新的对象,新对象的属性和方法与源对象相同。但是这个cloneObject函数存在原型链污染的风险,因为Object.assign函数会复制源对象的原型对象中的属性和方法。

下面是一个利用这个cloneObject函数进行原型链污染的示例代码:

// 定义一个原型对象
var myPrototype = {
sayHello: function() {
console.log('Hello!');
}
};

// 创建一个空对象
var myObject = {};

// 使用cloneObject函数复制myPrototype对象到myObject中
cloneObject(myObject, myPrototype);

// 调用myObject上的sayHello方法
myObject.sayHello(); // Hello!

在这个示例代码中,定义了一个原型对象myPrototype,它包含一个方法sayHello。然后创建一个空对象myObject,并使用cloneObject函数将myPrototype对象复制到myObject中。由于cloneObject函数使用了Object.assign函数,因此myObject的原型对象被设置为了myPrototypemyObject现在可以访问原型对象中的方法sayHello。因此,调用myObject.sayHello()时,就会执行myPrototype中的sayHello方法。

Object.assign 函数:

Object.assign是一个常用的函数,用于将多个源对象的属性和方法合并到目标对象中。然而,Object.assign也可能会导致原型链污染的问题。

Object.assign的原理是遍历源对象的所有可枚举属性,然后将它们赋值给目标对象。如果源对象的属性是一个引用类型(如对象、数组等),则会将引用复制到目标对象中,这可能会造成原型链污染的风险。

// 定义一个全局变量
var globalObj = {};

// 在 globalObj 的原型上定义一个属性
Object.prototype.globalProp = 'global property';

// 使用 Object.assign 合并一个带有属性的对象到全局变量中
Object.assign(globalObj, {
myProp: 'my property'
});

// 修改全局变量中的属性
globalObj.globalProp = 'modified global property';

// 输出全局变量中的属性和原型链上的属性
console.log(globalObj.myProp); // "my property"
console.log(globalObj.globalProp); // "modified global property"

// 创建一个对象并继承自全局变量
var obj = Object.create(globalObj);

// 输出继承自全局变量的对象的属性和原型链上的属性
console.log(obj.myProp); // "my property"
console.log(obj.globalProp); // "modified global property"·

下面是一个使用Object.assign函数导致原型链污染的示例代码:

// 定义一个原型对象
var myPrototype = {
sayHello: function() {
console.log('Hello!');
}
};

// 创建一个空对象
var myObject = {};

// 使用Object.assign函数将myPrototype对象合并到myObject中
Object.assign(myObject, myPrototype);

// 调用myObject上的sayHello方法
myObject.sayHello(); // Hello!

在这个示例代码中,定义了一个原型对象myPrototype,它包含一个方法sayHello。然后创建一个空对象myObject,并使用Object.assign函数将myPrototype对象合并到myObject中。由于Object.assign函数会将myPrototype对象中的属性和方法复制到myObject中,因此myObject的原型对象被设置为了myPrototypemyObject现在可以访问原型对象中的方法sayHello。因此,调用myObject.sayHello()时,就会执行myPrototype中的sayHello方法。

Object.create函数:

Object.create是一个用于创建新对象的函数,它可以使用现有对象作为新对象的原型。

// 定义一个全局变量
var globalObj = {};

// 在 globalObj 的原型上定义一个属性
Object.prototype.globalProp = 'global property';

// 使用 Object.create 创建一个带有属性的对象并继承自全局变量
var obj = Object.create(globalObj, {
myProp: {
value: 'my property'
}
});

// 修改全局变量中的属性
globalObj.globalProp = 'modified global property';

// 输出继承自全局变量的对象的属性和原型链上的属性
console.log(obj.myProp); // "my property"
console.log(obj.globalProp); // "modified global property"

如果原型对象存在恶意代码,那么新对象也会受到影响,从而导致原型链污染的问题。

下面是一个使用Object.create函数导致原型链污染的示例代码:

// 定义一个原型对象
var myPrototype = {
sayHello: function() {
console.log('Hello!');
}
};

// 使用Object.create函数创建一个新对象,并将myPrototype作为它的原型
var myObject = Object.create(myPrototype);

// 调用myObject上的sayHello方法
myObject.sayHello(); // Hello!

在这个示例代码中,定义了一个原型对象myPrototype,它包含一个方法sayHello。然后使用Object.create函数创建了一个新对象myObject,并将myPrototype作为它的原型。由于myObject的原型对象被设置为了myPrototypemyObject现在可以访问原型对象中的方法sayHello。因此,调用myObject.sayHello()时,就会执行myPrototype中的sayHello方法。

eval函数:

eval函数是一个全局函数,它将传入的字符串作为 JavaScript 代码进行解析和执行。

// 定义一个全局变量
var globalVar = 'global variable';

// 使用 eval 函数创建一个函数并在其中访问全局变量
eval('function myFunc() { console.log(globalVar); }');

// 修改全局变量的值
globalVar = 'modified global variable';

// 调用刚才创建的函数
myFunc(); // "modified global variable"

由于eval函数具有执行任意代码的能力,因此它也可能导致原型链污染的问题。

eval函数中,如果执行的代码中包含了对__proto__属性的操作,那么就会导致原型链污染。例如,下面的示例代码演示了如何使用eval函数向一个对象的原型对象中添加一个属性:

var myObject = {};
var code = "this.__proto__.myProperty = 'Hello World';"; // 向原型对象添加属性

eval(code); // 执行代码

console.log(myObject.myProperty); // Hello World

在这个示例代码中,定义了一个空对象myObject,然后使用字符串形式的代码向它的原型对象中添加了一个属性myProperty。将这个字符串代码传递给eval函数,并执行它。由于eval函数会将字符串代码作为 JavaScript 代码进行解析和执行,因此会向myObject的原型对象中添加一个属性myProperty。最后,输出myObject.myProperty的值,可以看到它已经被设置为了'Hello World'

原型链污染的实际应用

示例代码:

// 定义一个常量对象
const MY_CONST = {
name: "My Const"
};

// 定义一个 merge 函数,用于合并对象
function merge(target, source) {
for (let key in source) {
if (source.hasOwnProperty(key)) {
target[key] = source[key];
}
}
}

// 使用 merge 函数合并对象
merge({}, JSON.parse('{"__proto__": {"isAdmin": true}}'));

// 检查 MY_CONST 是否被污染
console.log(MY_CONST.isAdmin); // true

在这个示例中,首先定义了一个常量对象MY_CONST,然后定义了一个merge函数,用于合并两个对象。接着,使用JSON.parse函数解析了一个包含__proto__属性的 JSON 字符串,并将它作为参数传递给merge函数。由于merge函数中没有对参数进行类型检查或者属性白名单过滤,因此这个函数可能会合并任意对象的属性,包括它们的原型对象中的属性。

在这个示例中,由于恶意 JSON 字符串中包含了__proto__属性,因此它会将一个具有isAdmin属性的对象添加到MY_CONST对象的原型对象中。这样,就可以通过MY_CONST.isAdmin属性访问到这个恶意对象中的属性值了,这就是原型链污染的问题。

JavaScript中可以触发弹窗的函数

  1. alert() 函数可以在浏览器中弹出一个警告框,用于向用户显示一条消息。示例代码如下:

    alert('Hello, world!');
  2. confirm() 函数可以在浏览器中弹出一个确认框,用于向用户显示一条消息并询问用户是否继续操作。示例代码如下:

    if (confirm('Are you sure you want to delete this item?')) {
    // 用户点击了确认按钮,执行删除操作
    } else {
    // 用户点击了取消按钮,不执行删除操作
    }
  3. prompt() 函数可以在浏览器中弹出一个对话框,用于向用户显示一条消息并询问用户输入内容。示例代码如下:

    let name = prompt('What is your name?');
    if (name) {
    alert('Hello, ' + name + '!');
    } else {
    alert('Please enter your name.');
    }

原文始发于微信公众号(鱼板安全):XSS注入之原型链污染

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年11月27日23:13:09
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   XSS注入之原型链污染https://cn-sec.com/archives/2492592.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息