All your JavaScript code is polluted

jankapunkt

Jan Küster

Posted on November 8, 2022

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.

💖 💪 🙅 🚩
jankapunkt
Jan Küster

Posted on November 8, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related