JavaScript Prototype
prototype, prototype chaining, inheritance, JavaScript, prototype pollution
Understanding JavaScript is sometimes nothing less than an Adventure, today I am sharing with you one of JavaScript's concept called the prototypes. I never went into this detail with the prototypes and what made me to dive deep into this was prototype pollution.
Ever heard of it ? well I too had no idea about it, until last week when I was talking to one of my friend where he mentioned how there recent project was flagged for this vulnerability and he gave me a vague explanation about it by linking it to a Injection attack that allows attacker to manipulate the default value of your objects making your application code go south.
I was confused and looking for answers and I found it while working on a recent project where I had to add some new feature in it, but before making my changes I wanted to play around with the code to understand the flow and it came to my mind, is this code also prone to prototype pollution. What I found was something interesting and I can't wait to share the finding with you.
So, Let's get started, but before we start, let's shed some light on JavaScript's inheritance model – the prototype. I have divided this article in two parts, So the first part covers the JavaScript prototype and the next will cover about the prototype pollution.
What is Prototype in JavaScript?
Prototypes in JavaScript are a mechanism by which JS objects inherits properties from one another like inheritance. Every Object in JavaScript has a built-in property which is called prototype. The prototype is itself an object, so the prototype will have its own prototype, making what's called a prototype chain.
When you try to access a property of an object, if the property can't be found in the object itself, the prototype is searched for the property. If the property still can't be found, then the prototype's prototype is searched, and so on until either the property is found, or the end of the chain is reached, in which case undefined is returned.
let's understand this with an example:
/*creating object using javascript object literal
this will make objA to inherit all the property coming
from the prototype object properties like - toString, valueOf etc*/
let objA={}
/* Similar to how objA was created only difference is that objB will
have name and age property of its own along with inherited prototype object
properties */
let objB={
name:"B",
age:20
}
/* The Object.create method gives you option to create your object based on any
other object/prototype so here we are using Object.prototype that's same as the
global prototype object that objA and objB have inherited properties from*/
let objC = Object.create(Object.prototype)
/*Object.assign: Copy the values of all of the enumerable own properties from
one or more source objects to a target object and returns the target object.
So here all the properties of objB will be copied and assigned to objD
means objB and objD will have same properties */
let objD = Object.assign({},objB)
/*we are overiding the name property
that was copied from objB and updating its value*/
objD.name="D"
/*here we are explicitly making a empty object where
we don't want it to inherit anything from the
prototype object so the objE will not have access
to any of the default properties that comes with prototype object*/
let objE = Object.create(null)
console.log(objB.age) // Outputs: 20
console.log(objB.valueOf()) // Outputs: {name: 'B', age: 20}
console.log(objD.name) // Outputs: D
console.log(objD.age) // Outputs: 20
// the prototype object can be accessed using __proto__
console.log(objA.__proto__ === objB.__proto__) // Outputs: true
console.log(objB.__proto__ === objC.__proto__) // Outputs: true
console.log(objC.__proto__ === objD.__proto__) // Outputs: true
console.log(objD.__proto__ === objA.__proto__) // Outputs: true
console.log(objE.__proto__ === objA.__proto__) // Outputs: false
So, the above console logs were pretty straight forward. Now let's see how JavaScript behaves when we do something as below.
/*creating object using javascript object literal
this will make objA to inherit all the property coming
from the prototype object properties like - toString, valueOf etc*/
let objA={}
/* This one is interesting because
here we are not passing the Object.prototype directly
instead we are passing the objA as a prototype for objF
So objF will inherit properties from objA
and since objA already inherits properties from the prototype object
making objF to have access to properties of prototype object
via objA (like multilevel inheritence)*/
let objF = Object.create(objA)
console.log(objF.__proto__ === objA.__proto__); // Outputs: false
console.log(objA.__proto__ === Object.prototype); // Outputs: true
console.log(objF.__proto__.__proto__ === objA.__proto__) // outputs: true
Here's why console.log(objF.__proto__ === objA.__proto__)
returns false
:
objF.__proto__
points to prototype object of objAobjA.__proto__
points toObject.prototype
console.log(objF.__proto__.__proto__ === objA.__proto__)
outputs: true
Playing with the __proto__
Object:
Since objA, objB, objC, objD share the same proto object what would happen if I add any value to the prototype like objA.__proto__.city = "hyd"
//lets see, I think you already guessed
console.log(objA.city) // outputs: hyd
console.log(objB.city) // outputs: hyd
console.log(objC.city) // outputs: hyd
console.log(objD.city) // outputs: hyd
It's because all these objects share the same prototype object and if one object adds or updates any thing then it will reflect for all other object that share the same prototype object.
Now what about objE and objF:
console.log(objE.city) // outputs: undefined
console.log(objF.city) // outputs: hyd
Since objF inherits objA so JavaScript first checks if objF has its own city property and since it does not have city property of its own JavaScript checks if its prototype has any city property and since that is also not present its checks the prototype of objF's prototype which is pointing to prototype of objA and there it finds the city property and prints its value and this chaining is known as prototype chaining.
Where as objE is created using Object.create(null)
so it does not share the same prototype object like other objects hence it will not have access to the city property until we explicity define that property for objE.
Conclusion:
Thankyou for taking out time to learn and explore along with me, I hope you must have got something useful out of it.
prototype inheritance comes with its own set of nuances and pitfalls. While it offers a seamless approach to achieving inheritance without the complexities of traditional class-based systems, However if not handled properly this simple thing can cause a problem as serious as prototype pollution and make your application do weird things.