Node.js require() RCE复现
前言 前阵子参加了Balsn CTF 2022,有道Node.js的题目叫2linenodejs,个人觉得思路十分巧妙,遂进行了完整的复现,收获颇多。下面是整个复现的过程。 题目代码如下: // server.js process.stdin.setEncoding('utf-8'); process.stdin.on('readable', () => { try{ console.log('HTTP/1.1 200 OK\nContent-Type: text/html\nConnection: Close\n'); const json = process.stdin.read().match(/\?(.*?)\ /)?.[1], obj = JSON.parse(json); console.log(`JSON: ${json}, Object:`, require('./index')(obj, {})); }catch{ require('./usage') }finally{ process.exit(); } }); // index.js module.exports=(O,o) => (Object.entries(O).forEach(([K,V])=>Object.entries(V).forEach(([k,v])=>(o[K]=o[K]||{},o[K][k]=v))), o); // usage.js console.log('Validate your JSON with <a href="/?{}">query</a>'); 在try block里面将输入的JSON字符串转换为JavaScript对象,再使用一个遍历将对象的属性逐一赋给另一个空对象,很明显这里存在原型链污染。 预期思路是在读取JSON是产生异常,然后进入catch执行require('./usage'),通过require()方法实现RCE。 至于如何产生异常,方法是在JSON里面加一个项值为null,这样在遍历时Object.entries(V)为null,再调用forEach就会产生无法读取属性的异常。 require()任意文件包含执行 使用WebStorm对源码进行调试,在catch处下断点,然后传入输入:?{"a":null} ,使用Force Step Into(快捷键Alt+Shift+F7)即可跳转到require()的源码继续调试: 最终定位到Module._load方法: Module._load = function(request, parent, isMain) { let relResolveCacheIdentifier; if (parent) { debug('Module....