ejs 模板引擎实现 RCE
需要ejs的版本为3.1.5,高版本的ejs直接把outputFunctionName属性值给删除了
首先探测一下该漏洞是否存在
app.js
1 | var express = require('express'); |
index.ejs
1 | <!DOCTYPE html> |
访问127.0.0.1:8000
至于为啥要污染outputFunctionName属性,我们来看看源码
我们从 index.js::res.render 处开始,跟进 render 方法:
- node_modules/express/lib/response.js
跟进到 app.render 方法:
- node_modules/express/lib/application.js
发现最终会进入到 app.render 方法里的 tryRender 函数,跟进到 tryRender:
- node_modules/express/lib/application.js
调用了 view.render
方法,继续跟进 view.render
:
- node_modules/express/lib/view.js
至此调用了 engine
,也就是说从这里进入到了模板渲染引擎 ejs.js
中。跟进 ejs.js
中的 renderFile 方法:
- node_modules/ejs/ejs.js
发现 renderFile 中又调用了 tryHandleCache 方法,跟进 tryHandleCache:
- node_modules/ejs/ejs.js
进入到 handleCache 方法,跟进 handleCache:
- node_modules/ejs/ejs.js
在 handleCache 中找到了渲染模板的 compile 方法,跟进 compile:
发现在 compile 中存在大量的渲染拼接。这里将 opts.outputFunctionName
拼接到 prepended 中,prepended 在最后会被传递给 this.source 并被带入函数执行。所以如果我们能够污染 opts.outputFunctionName
,就能将我们构造的 payload 拼接进 js 语句中,并在 ejs 渲染时进行 RCE。在 ejs 中还有一个 render
方法,其最终也是进入了 compile
。最后给出几个 ejs 模板引擎 RCE 常用的 POC:
1 | {"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require(\'child_process\').execSync(\'calc\');var __tmp2"}} |