DEV Community

Cover image for All your JavaScript code is polluted
Jan Küster
Jan Küster

Posted on

All your JavaScript code is polluted

Okay, maybe not all code and not always. This is just a short note on the prototype pollution as I have seen this issue again and again.

Photo by Maxim Tolchinskiy on Unsplash

What is this about?

If you use JavaScript Object-bracket notation that accepts input from users then you may already have introduced Prototype pollution to your code. See this very simplified example:

const internal = {
  foo: {
    bar: null
  }
}

const acceptUserInput = (type, subtype, value) => {
  internal[type][subtype] = value
}
Enter fullscreen mode Exit fullscreen mode

Applied:

// no problem so far, this is the expected input
acceptUserInput('foo', 'bar', 'I am so clever')

// malicious input
acceptUserInput('__proto__', 'polluted', 'Bon jour 🐻‍❄️')
Enter fullscreen mode Exit fullscreen mode

The result of the malicious input is, that all your newly created objects do now contain the polluted property and the polar bear greets you:

const obj = {}
console.debug(obj.polluted) // 'Bon jour 🐻‍❄️'
Enter fullscreen mode Exit fullscreen mode

Why is this a problem?

On the client this is somewhat to less problematic but on the server this can open doors to follow-up attacks.

Let's say an attacker knows, that you use a runtime-created Object wihtout Object.create(null) during your checks for certain access privileges (which you should not do but I need an example here).

With the prototype pollution they could unlock privileges and gain more access on your system than they should:

const internal = {
  foo: {
    bar: null
  }
}

const acceptUserInput = (type, subtype, value) => {
  internal[type][subtype] = value
}

// assume, this object
// is constructed when reading
// values from db
const getRoles = () => ({ canAccessThat: true })

const userCanAccessThis = () => {
  const me = getCurrentUser() // get from session etc.
  const roles = getRoles(me.id)
  return roles.canAccessThis === true
}

// malicious input
acceptUserInput('__proto__', 'canAccessThis', true)

// will now always return true for every user
userCanAccessThis()
Enter fullscreen mode Exit fullscreen mode

This is, again, a simplified example but I hope you can see the severity it could introduce into your system.

What can I do to prevent it?

  • Reduce Object-bracket notation and use dot notation where possible
  • Alternatively use a Map or a Set, depending on your use-case
  • Beware of deep merging objects without checking that no properties of the prototype chain are affected
  • Validate inputs, especially if on the server
  • Use Object.create(null) to create Objects with no prototype

Thank you and please let me know, if you have any issues with the article or simply if you liked it and it helped you.

Top comments (15)

Collapse
 
adam_cyclones profile image
Adam Crockett 🌀

Okay I can see a small potential risk here, thank you for showing me why automatic prototypical inheritance could be exploited.

My 2 cents:
I think that the prevention ideas may be idealism fodder for the developer who doesn't yet understand the significance and there is potential to just blindly add this sort of complexity to every single thing (trust me, I was that guy)

Could it be that the best way to prevent this sort of attack is a logical problem rather than a way of using js, for example some sort of 2 step permission system with a key that can only be issued from the server side to prove permissions

Better yet, don't role your own permission system and use Oauth and OpenID

Collapse
 
jankapunkt profile image
Jan Küster

I know the permission example is a bit drastic, but I also think it's a good way to especially show beginners how far this can get hypothetically.

I am also convinced, that this is indeed also related of how people use JS. For example: articles, I read about "why you should not use switch", sometimes propose to use a dictionary-like Object as in the example above. It's also a very fast approach, compared to use am Map.

Maybe I also add, that many popular libraries (lodash, underscore) are known to habe introduced PP im prior versions?

Collapse
 
adam_cyclones profile image
Adam Crockett 🌀

The thing is I looked at your example and initially I was sceptical, the more I looked the more I saw this could happen. I like this post I ranked it as high quality so I hope you can have more discussions from that.

Oh yes, I know the pain that is lodash or similar, the security argument against a community authored std library is a juicy topic, worth writing about?

Poor misundrstood switch, I think there is some anti statement sentiment stemming from the FP community the myth that FP never uses statements, that and the similar looking branches I guess, again I had never considered this might be another reason why switch is the healthy choice.

Much food for thought here!

Collapse
 
gottz profile image
Jan-Stefan Janetzky

hasOwnProperty or "in" should be used here or indeed a set or map..
alternatively a lookup table.
why would I blindly trust user input.. that's trivial basic knowledge.. I always have to know all possible options a user might use and discard the excess.

Collapse
 
jankapunkt profile image
Jan Küster • Edited

You should, of course, never trust user input. But you should also not solely rely on validation, thus taking this scenario as seriously as other scenarios. Validation can fail and you never can cover all edge cases during validation.

Edit: note, that in is not safe, either:

'__proto__' in {} // true
Enter fullscreen mode Exit fullscreen mode

I summarized a few of these issues in one of my other articles: How to mess up your JavaScript code like a boss

Collapse
 
gottz profile image
Jan-Stefan Janetzky • Edited

for some reason that's precisely why I mentioned hasOwnProperty first, because in CAN be used in cases you are checking against known keys.
also.. when validation fails, it should fail.
that's why it is called validation..

Collapse
 
xiziliang profile image
xzl

Bingo~

Collapse
 
wadecodez profile image
Wade Zimmerman

Would like to see a breakdown of this topic on more common operations like saving user settings as a JSON object to the database. Like what would happen if prototype pollution was saved to the database by mistake then hydrated in the browser?

Devs tend to pay close attention to security features but forget about security on the random features like user settings, or repeatable fields.

Collapse
 
jankapunkt profile image
Jan Küster

Thanks that would be a great follow-up article. Let me check the next days what I can find.

Collapse
 
htho profile image
Hauke T.

I never understood why one would use Object.create(null) now I do. Thank you.

Collapse
 
maryannah profile image
Maryannah

"all your js code is polluted"

Proceeds to explain a very specific use-case, which literally no-one in their right mind would implement.

Clickbait much ?

Collapse
 
jankapunkt profile image
Jan Küster

You literally got me on this one. Now please take a few minutes and review my other articles that contain hours of research and writing effort and compare their likes with this one. Something is really broken here. Still, I tried to deliver on the topic as good as possible!

Collapse
 
maryannah profile image
Maryannah

No thanks, given the poor quality of this article, your attention seeking behavior, and passive agressive answer, I'll just pass your offer.

Thread Thread
 
jankapunkt profile image
Jan Küster

I think you misunderstood completely. Sorry.

Collapse
 
mohanramphp profile image
Mohan Ram

Brilliant article on prototype pollution.

Thank you for coming up with simple usecases to understand it.