All your JavaScript code is polluted
Tags: javascript, security
Published: 2022-11-08 15:04:16

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.

<small>Photo by <a href="https://unsplash.com/@shaikhulud?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Maxim Tolchinskiy</a> on <a href="https://unsplash.com/s/photos/pollution?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a></small>

## What is this about?

If you use JavaScript [Object-bracket notation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_Accessors#property_names) that accepts input from users then you may already have introduced Prototype pollution to your code. See this very simplified example:

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

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

Applied:

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

// malicious input
acceptUserInput('__proto__', 'polluted', 'Bon jour 🐻‍❄️')
```

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:

```js
const obj = {}
console.debug(obj.polluted) // 'Bon jour 🐻‍❄️'
```

## 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:

```js
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()
```

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`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) or a [Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/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.