Skip to content
/ content Public

Conversation

@Elchi3
Copy link
Member

@Elchi3 Elchi3 commented Sep 23, 2025

Description

This PR adds a new page to our list of "attacks", this one is on JavaScript Prototype Pollution.

Generally, I'm not sure if I covered everything that needs to appear here. I think I'm somewhat happy about the defenses part, but explaining how to actually get into to polluting objects might need some more words?

Also, I think this needs integration into JS docs (at a minimum some linkage).

Motivation

Developing a series of guides about common attacks on websites, explaining for each attack: how it works, the impact, and how to defend against it.

Additional details

Would love some feedback from @wbamberg, @aaronshim and maybe @Josh-Cena.

Related issues and pull requests

None, I think.

Verified

This commit was signed with the committer’s verified signature.
Elchi3 Florian Scholz
@github-actions github-actions bot added Content:Security Security docs size/m [PR only] 51-500 LoC changed labels Sep 23, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Sep 23, 2025

Preview URLs (6 pages)
Flaws (1)

Note! 5 documents with no flaws that don't need to be listed. 🎉

URL: /en-US/docs/Web/Security/Attacks/Prototype_pollution
Title: JavaScript prototype pollution
Flaw count: 1

  • macros:
    • Macro produces link /en-US/docs/Web/API/fetch which is a redirect
External URLs (8)

URL: /en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf
Title: Object.setPrototypeOf()


URL: /en-US/docs/Web/Security/Attacks/Prototype_pollution
Title: JavaScript prototype pollution

(comment last updated: 2025-10-14 16:12:45)

@Josh-Cena
Copy link
Member

Thanks! I actually thought about writing this not long ago. However, this seems to be covered by XSS or supply chain attack already because you need to have a vector in order to inject code for prototype pollution, and if you get someone to execute your code anyway, you can do many other bad things as well.

Copy link
Member

@Josh-Cena Josh-Cena left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't think through this completely, but there are at least two things that are missing:

  1. Not all prototype pollution involves adding properties to Object.prototype. Sometimes, they overwrite primordial methods and "hook into" language mechanisms to execute arbitrary code. An infamous case is overriding Promise.prototype.then, which would be called for every await operation. See for example: nodejs/node#59699 and https://github.com/tc39/proposal-thenable-curtailment. This is also why Symbol.species is considered a mistake: https://github.com/tc39/proposal-rm-builtin-subclassing.
  2. "Freezing the prototype" is good, but instead of recommending Immer, we could recommend something actually built for this purpose (freezing every built-in object). That is SES.

@Josh-Cena
Copy link
Member

So your description is not far from how prototype pollution works. Generally, you either get to execute code directly (for example, in an iframe or via eval), or you can create a payload that contains the __proto__ property key which later gets merged into another object via spreading, for...in loops, etc, turning it into an assignment operation and therefore triggers the setter.

Elchi3 and others added 2 commits September 26, 2025 15:00

Partially verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
We cannot verify signatures from co-authors, and some of the co-authors attributed to this commit require their commits to be signed.
Co-authored-by: Joshua Chen <sidachen2003@gmail.com>

Verified

This commit was signed with the committer’s verified signature.
Elchi3 Florian Scholz
@Elchi3
Copy link
Member Author

Elchi3 commented Sep 26, 2025

Thanks @Josh-Cena! I've tried to work in your feedback in 2ce35f2 and I've accepted your suggestions. Let me know what you think about that.

Also, should we add links to this from some JS docs? I guess at a minimum from Object.prototype.__proto__ and Object.prototype?

@Elchi3 Elchi3 marked this pull request as ready for review September 26, 2025 13:52
@Elchi3 Elchi3 requested a review from a team as a code owner September 26, 2025 13:52
@Elchi3 Elchi3 requested review from hamishwillee and removed request for a team September 26, 2025 13:52
Copy link
Member

@Josh-Cena Josh-Cena left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a bug to fix, and then I'm looking at https://github.com/tc39/proposal-symbol-proto, which has a lot of useful points:

  • No code injection (data-only): I already added a bit about it, but perhaps you want to make it even clearer that CSP won't help.
  • You can also reach Object.prototype via result.constructor.prototype.test, which is longer, but doesn't use __proto__.

And yes we should link to this page from __proto__, Object, and the prototype chain guides.

Comment on lines 68 to 72
result.test; // "polluted"
{}.test; // "polluted"
"".test; // "polluted"
[].test; // "polluted"
// ...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ummm... This is wrong. In this case you constructed a single object, result, whose prototype is { test: "polluted" }. You did not successfully taint every object (although this may still be an issue). In order to do that, you need to have code that assigns properties, where the object being assigned could turn out to be Object.prototype:

result[x][y] = z;

In this case, if result is a plain object, then you can set x = "__proto__" and y = "test" to successfully inject it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I removed this part for now. I struggle to illustrate it properly.


### Node.js flag `--disable-proto`

If you are in a Node.js environment, you can disable `Object.prototype.__proto__` with the `--disable-proto=MODE` option where `MODE` is either `delete` (the property is removed entirely), or `throw` (accesses to the property throws an exception with the code `ERR_PROTO_ACCESS`).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A simple trick for non-Node is delete Object.prototype.__proto__

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a trick or advice we should give?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know any pitfalls of this, so this is an advice. Perhaps the only pitfall is, like freezing internals, you may have code that runs before the realm lockdown (polyfills etc) and those codes are still insecure.

Comment on lines 79 to 88
### Create a JavaScript object without a prototype

The {{jsxref("Object.create()", "Object.create(null)")}} function allows you to create a new empty object without a prototype. Because of this, there won't be any prototype pollution issues:

```js
let obj = Object.create(null);
obj.__proto__; // undefined
obj.constructor; // undefined
obj["__proto__"]["a"] = 1; // TypeError: Cannot set properties of undefined
```
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create "what" object without a prototype? All objects?

I think it is worth spelling out more like "If you are populating objects from user supplied data then they should be ....". This is true for the other sections too.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I tried to make this more clear by saying that it is not the default and that would be needed for every object creation.

Comment on lines 134 to 139
- Create empty objects with `Object.create(null)`.
- Use `Map` and `Set` objects instead.
- Consider freezing your own objects, for example with a library like [Immer](https://immerjs.github.io/immer/).
- Consider freezing built-in objects, for example by using the [SES](https://github.com/endojs/endo/tree/master/packages/ses#ses) shim.
- Validate your object structure with a schema.
- Use `--disable-proto` in Node.js.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above "what object". I assume disabling proto etc only disables proto on your code? How might it affect imported libraries etc?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addded "to disable Object.prototype.__proto__." because as I understand it, it just disables the special __proto__ property.


Not all prototype pollution involves adding properties to `Object.prototype`, though. Other prototypes can also be polluted, like when overriding {{jsxref("Promise.prototype.then")}} which would be called for every `await` operation, and more.

To see the effect of prototype pollution, we can look at the how the following {{domxref("fetch()")}} call can be changed completely. By default, it is a {{HTTPMethod("GET")}} request, but because we polluted the `Object` object's prototype with two new default properties, the `fetch()` call is now transformed into a {{HTTPMethod("POST")}} request.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is obviously not the intent, but I kind of feel that this is a bit abstract - why might this be a problem? Would be great to be a little more concrete on what you might now do having made this change.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added another example that Aaron suggested on srcdoc to make things more spooky.

fetch("https://example.com", {
mode: "cors",
});
// Promise {status: "pending", body: "a=1", method: "POST"}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I think that this is as simple and very clear example, I wonder whether we can do more here (or somewhere else in the doc) to really drive home the point of "Prototype Pollution is scary and a big security issue".

In addition to overriding GET / POST, some other attacks that could be a lot more scary-seeming:

  • srcdoc on Object.prototype -> XSS
  • default configurations on sanitizers
  • some existing CVEs and/or known faults in particularly famous NPM packages that allow for exploitability in the wild

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I added srcdoc and sanitizer config as other examples (to at least mention the fact that some objects might be more sensitive than others and deserve extra attention). Thank you for these pointers, they made it even scarier for me :)


There are a few options to mitigating prototype pollution vulnerabilities. This section presents them.

### Create a JavaScript object without a prototype

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this is a very very simple way to stop PP on the most sensitive objects, the onus is still on the instantiators of every object to run this and therefore is not secure-by-default. Is this nuance worth mentioning here?

Also, if that is the case... should the Object.freeze solution be promoted higher on the list of solutions?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I tried to make it more clear that the default behavior is not a null-prototype unfortunately.

I moved the Map and Set mitigation below Object.freeze now, so it is second after Object.create(null). I hope that makes more sense.


Instead of using JavaScript objects, use {{jsxref("Map")}} or {{jsxref("Set")}} objects. These are more modern data structures not vulnerable to prototype pollution issues.

### Freezing the prototype

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth mentioning the slightly related mitigations Object.seal and Object.preventExtensions ?

https://portswigger.net/web-security/prototype-pollution/preventing

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Object.seal and Object.preventExtensions are both mentioned in this section now. Object.freeze is presented as the tightest option

Elchi3 and others added 4 commits October 6, 2025 15:46

Partially verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
We cannot verify signatures from co-authors, and some of the co-authors attributed to this commit require their commits to be signed.
Co-authored-by: Hamish Willee <hamishwillee@gmail.com>
Co-authored-by: Joshua Chen <sidachen2003@gmail.com>

Verified

This commit was signed with the committer’s verified signature.
Elchi3 Florian Scholz

Verified

This commit was signed with the committer’s verified signature.
Elchi3 Florian Scholz

Verified

This commit was signed with the committer’s verified signature.
Elchi3 Florian Scholz
@Elchi3 Elchi3 requested a review from a team as a code owner October 6, 2025 14:20
@github-actions github-actions bot added the Content:JS JavaScript docs label Oct 6, 2025
@sideshowbarker sideshowbarker removed the request for review from a team October 6, 2025 22:12

Verified

This commit was signed with the committer’s verified signature.
Elchi3 Florian Scholz
Copy link
Collaborator

@wbamberg wbamberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't really a complete review, but overall, it's not very clear to me what specific things a developer might be doing that makes them vulnerable to this threat, and which defenses they need to implement against which threats, based on what they are doing.

Comment on lines 10 to 13
To understand how JavaScript prototypes work, read the following MDN articles:

- [Inheritance and the prototype chain](/en-US/docs/Web/JavaScript/Guide/Inheritance_and_the_prototype_chain)
- [Working with objects](/en-US/docs/Web/JavaScript/Guide/Working_with_objects)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's very helpful to start out by sending someone to two very long and fiercely technical articles, as a preparation for understanding this article. For one thing, there's a lot in those articles, and they're not all about prototypes. For another, our job here is to give someone who might be quite inexperienced practical guidance about how to protect themselves against concrete threats. I think if we say effectively "to understand this threat, you have to read all that", they will switch off.

It's fine to include these links and invite people to read them, but we should do our best to explain the setup in as accessible a way as possible here, and then present these links as "if you want to learn more about this...".

BTW I tried to explain this accessibly in https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Advanced_JavaScript_objects/Object_prototypes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Will, your suggested text is a lot more accessible and I will merge that in. We should probably also link to the Learn article


In a nutshell, every object in JavaScript has a `prototype` property which is an object itself and that prototype has its own prototype, too. This is called the JavaScript prototype chain. The chain ends when we reach an object that has `null` for its own prototype. The power of the prototype chain is that it allows an object to inherit properties and methods from its parent. However, a bad practice that JavaScript allows, is that the prototypes of objects can be changed during runtime which makes it possible to install properties at unintended places with malicious values. This even includes the built-in default objects, like the {{jsxref("Object")}} object, which is likely in the prototype chain of the majority of objects in your codebase and therefore makes this attack especially dangerous.

A key attack vector is the special [`Object.prototype.__proto__`](/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto) property, and how adding a property to `Object.prototype` will make that property accessible on ("polluted to") every object in your application. The `__proto__` property is often used, but you can also reach `Object.prototype` via `yourObject.constructor.prototype`.
Copy link
Collaborator

@wbamberg wbamberg Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We say __proto__ is special, but don't say how it is special, or even what it is, and the second part of that sentence doesn't seem to follow from the first.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed "special" and I changed this to have two paragraphs with the goal to explain two things:

  1. The __proto__ properties are used very often for this attack but, as Josh says, the constructor.prototype property can also be used.
  2. Object.prototype is most commonly the target of this. But other prototypes (like Promise.prototype) could also be targets.

```

Any configuration objects, like `fetch()`'s {{domxref("RequestInit")}} object in the code example above, or the instantiation of `<iframes>`, or configuration of sanitizers ({{domxref("SanitizerConfig")}} objects) are some of the most sensitive objects and are often targets of prototype pollution attacks.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I struggle with these because they're not attacks. Yes, if you can run JS in my context (to do things like Object.prototype.method = "POST";) you can do this, but if you can run JS in my context it's game over already, isn't it?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these example are more useful to show how prototypes are malleable in JavaScript (as an illustration of the previous discussion of prototypes being malleable) than they are as examples of attacks.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree and I struggle to explain the first step here. I understand how it is game over if you are already able to pollute prototypes, but I don't understand how it usually comes to that. I mean anyone could do Object.prototype.method = "POST"; in devtools even. Would that make things horrible already? Do pentesters test this way?

I actually try to mention this in the "Vulnerable sources" section. So there I mention URL query strings which https://github.com/BlackFan/client-side-prototype-pollution seems to be all about. And I mention JSON.parse which apparently also can go wrong if you deal with JSON input coming from untrusted sources.


### Vulnerable sources

In order to pollute objects, the attacker needs a way to add arbitrary properties to prototype objects. This may happen as a consequence of [XSS](/en-US/docs/Web/Security/Attacks/XSS), in which the attacker gains direct access to the page's JavaScript execution environment. However, this can also happen without code injection, such as by constructing a payload that contains the `__proto__` property key will later get merged into another object via [spreading](/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax), [`for...in` loops](/en-US/docs/Web/JavaScript/Reference/Statements/for...in), etc., turning it into an assignment operation and therefore triggering the setter.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit vague. As a developer, I want to know: exactly what things might I do that would make me vulnerable? For example, with XSS it's pretty clear: including potentially-user-supplied content in pages without sanitizing it. What's the equivalent?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #41260 (comment). I don't know the answer for sure but my guess is that this comes via URL query strings, untrusted JSON input, and maybe even just devtools.


## Defenses against prototype pollution

There are a few options to mitigating prototype pollution vulnerabilities. This section presents them.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a developer, I want to know:

  • when do I need to implement defenses
  • in these cases, which defenses do I NEED, and which are alternates for each other
  • which are nice-to-have, defenses in depth

What's the best practice recommendation? I don't think it is so helpful to say "here's a long list of things you can do".

Copy link
Member Author

@Elchi3 Elchi3 Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know the answer. I can try to guess. Would be good to hear from an SME.

For objects you create and fully control:

  • Evaluate if an object is needed or if a Map or Set would be the better choice
  • If you're forced to create an object, use Object.create(null), especially for sensitive objects like FetchInit or SanitizerConfig
  • If you can't create an object with Object.create(null), consider freezing your object, or make it immutable using a library like Immer
  • If you need to create complex and unfrozen objects with a prototype, consider validating your object structure with a schema and reject unneeded properties by setting additionalProperties to false.

For built-in and third party objects:

  • Consider freezing built-in and third party objects, for example by using the SES shim.

Runtime defenses:

  • Use --disable-proto in Node.js to disable Object.prototype.__proto__.
  • Use delete Object.prototype.__proto__ in non-Node environments.


### Use `Map` and `Set` instead

Instead of using JavaScript objects, use {{jsxref("Map")}} or {{jsxref("Set")}} objects. These are more modern data structures not vulnerable to prototype pollution issues. See the `Map` documentation for a [comparison between Maps and Objects](/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map#objects_vs._maps). The {{jsxref("Map.prototype.get()")}} method will always only return the property that have been set directly on the `Map`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Instead of using JavaScript objects, use {{jsxref("Map")}} or {{jsxref("Set")}} objects." but of course we use JS objects all the time so we can't really mean that can we? I'm not clear how this would protect against (say) the fetch() pollution attack above, does it? So what's the specific vulnerability we're mitigating here?

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Co-authored-by: wbamberg <will@bootbonnet.ca>

Verified

This commit was signed with the committer’s verified signature.
Elchi3 Florian Scholz
…raph
Copy link
Member

@Josh-Cena Josh-Cena left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if you want to have an overarching principle for defense against PP, it would be this: (1) don't make prototype modifications happen, (2) even if modifications happen, don't access properties on Object.prototype and you should be safe.

(1) can be achieved via freezing prototypes, --disable-proto, delete Object.prototype.__proto__ etc. Another thing you can mention is to avoid dynamic property modification (of the form obj[k] = v) unless you know deterministically what k can be (a la JSON validation). If you rule out __proto__, constructor, prototype as keys then you are generally safe.

(2) looks like this (fetch falls into this category):

function doSomething(options = {}) {
  if (options.isAdmin) // ...
}

In this case, if options.isAdmin is undefined, it may walk up the prototype chain. The correct defense here, besides of course requiring your user to pass null-prototype objects or a Map (the latter being an API change), is to add an Object.hasOwn(options, "isAdmin") check to avoid lookup on the prototype, or explicitly set a default like options = { isAdmin: false }.

It could also look like this:

for (const key in payload) {
  doSomething(payload[key]);
}

for...in traverses the protoype. The correct way is to only visit own keys, i.e., for (const key of Object.keys(payload)) {.

In other words, audit all your object property accesses and make sure you know deterministically that the property exists on the object itself. When you are getting keys on objects that may not possess the key, add an Object.hasOwn check; when you are traversing keys on objects, use for...of + Object.keys instead of for...in.

Also perhaps we are making it too much like PP is a subset of XSS, but the danger of PP is exactly that it's unrelated to XSS—it's a data-only attack that only requires JSON communication.

Comment on lines 29 to 30
// toString() is defined on the prototype of `Array.prototype`,
// which is `Object.prototype`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also false: this method is defined on Array.prototype which shadows the one from Object.prototype (otherwise you should see [object Array]). If you want an example that calls Object.prototype methods, you need to do myArray.propertyIsEnumerable("length").

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK maybe my example is not good, but having an example here is helpful I think!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--> 801ab9d

Elchi3 added 2 commits October 9, 2025 14:47

Verified

This commit was signed with the committer’s verified signature.
Elchi3 Florian Scholz

Verified

This commit was signed with the committer’s verified signature.
Elchi3 Florian Scholz
@Elchi3
Copy link
Member Author

Elchi3 commented Oct 9, 2025

So in 63c8954 I committed my (opinionated) thoughts from #41260 (comment) to the Defense summary checklist and also worked it into each of the sections.

I also added a new section "Avoid lookups on the prototype" which is inspired by Josh's last comment.

@Josh-Cena
Copy link
Member

Are you fine if I push directly to your branch, or would you prefer a PR to your fork? I have some reordering/rewriting suggestions which are too complicated to propose as comments.

@Elchi3
Copy link
Member Author

Elchi3 commented Oct 9, 2025

I'm fine with direct pushes to this PR (granular commits appreciated so I can review what was changed). Thank you Josh!

Verified

This commit was signed with the committer’s verified signature.
Josh-Cena Joshua Chen

Verified

This commit was signed with the committer’s verified signature.
Josh-Cena Joshua Chen
@Josh-Cena
Copy link
Member

I pushed two commits. My first one is a rewrite; I hope it makes sense what I added and removed, but LMK if anything's fishy. My second one is a reorder; I reordered both the anatomy and the defenses into two lines, "pollution (and avoiding)" and "exploitation (and avoiding)".

Comment on lines 157 to 168
```js
const config = deepFreeze({
url: "https://api.example.org",
headers: {
Authorization: "some_token",
},
});

config.headers.Authorization = "hacked_token"; // fails thanks to deepFreeze
Object.freeze(Object.prototype);
const obj = {};
const key1 = "__proto__";
const key2 = "a";
obj[key1][key2] = 1; // fails silently in non-strict mode
obj.a; // undefined
```

The `deepFreeze` approach is not handling circular references by default, so to create true immutable objects, you may want to look into using a library like [Immer](https://immerjs.github.io/immer/). You could also consider the [SES](https://github.com/endojs/endo/tree/master/packages/ses#ses) shim for [Hardened JavaScript](https://hardenedjs.org) to freeze every built-in object.
Copy link
Member

@Josh-Cena Josh-Cena Oct 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note, I removed this paragraph about deep-freezing your own objects. Freezing objects you created don't make sense outside the context of XSS, because (1) if your object is expected to change dynamically, then you can't freeze it anyway (2) if your object is not expected to change and you don't have any code of the kind obj[key] = value, then you aren't susceptible to any data-only modification attempts. It's only built-in objects (i.e. prototypes) that are vulnerable and need to be frozen, so SES is good enough.

Another dangerous pollution attack target is the {{domxref("HTMLIframeElement.srcdoc")}} property which specifies the content of an {{HTMLElement("iframe")}} element. By overriding its value, it could potentially be possible to execute arbitrary code.

```js
Object.prototype.srcdoc = "<script>alert(1)<\/script>";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this is real. In HTML, the source of truth is the DON srcdoc attribute, not its JavaScript reference's srcdoc IDL property. It just happens that the IDL setter is a shorthand for setting the HTML attribute, but I don't think this attack will do anything...? Or I am missing context about how the iframe is created?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aaronshim can you elaborate? In #41260 (comment) you said:

In addition to overriding GET / POST, some other attacks that could be a lot more scary-seeming:

  • srcdoc on Object.prototype -> XSS

How does attacking srcdoc usually work in the context of prototype pollution?

Verified

This commit was signed with the committer’s verified signature.
Elchi3 Florian Scholz
@Elchi3
Copy link
Member Author

Elchi3 commented Oct 10, 2025

Thank you @Josh-Cena, I really appreciate your changes. I think the article flows quite nicely now.

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
@moz-mdauer
Copy link

Iterating here on the comments about how prototype pollution usually occurs in the first place and why it is scary. I think it makes sense to differentiate between client-side and server-side prototype pollution, as their sources and impacts vary.

The most common source of client-side prototype pollution is URL parsing, as it is something that can be controlled by an attacker. Either via the query string or fragment. Another potential, though less common source can be a postMessage listener -- if it lacks origin validation and implements logic vulnerable to prototype pollution.
As for the impact, it depends on the circumstances and your creativity :) In most cases, the goal is XSS. The client-side prototype pollution repository, you already mentioned, has an extensive list of "Script Gadgets" that achieve this using popular third-party libraries. In other cases, you may be able to bypass a HTML sanitizer (important to note: DOMPurify, for example, has made an extensive effort to be immune to prototype pollution.) The impact of other gadgets may not be immediately clear:

// Attacker indirectly causes the following pollution
Object.prototype.body = "a=1";
Object.prototype.method = "POST";

fetch("https://example.com", {
  mode: "cors",
});
// Promise {status: "pending", body: "a=1", method: "POST"}

But let's assume your banking website is vulnerable and you can do something like this:

Object.prototype.body = "action=transfer&amount=1337&to=1337-1337-1337-1337";
Object.prototype.method = "POST";

fetch("https://example.bank", {
  mode: "cors",
});
// Promise {status: "pending", body: "action=transfer&amount=1337&to=1337-1337-1337-1337", method: "POST"}

That's pretty bad, right?

Server-side prototype pollution often occurs when parsing JSON data received from a client and then "deep merging" objects. But it may also occur later when retrieving data from a database and constructing a new object, for example. There are many more possibilities here.
The impact can be way more devastating, ranging from bypassing access-control checks to RCE (scary!). I will refer to https://github.com/KTH-LangSec/server-side-prototype-pollution as it has a good overview.

Verified

This commit was signed with the committer’s verified signature.
Elchi3 Florian Scholz
@Elchi3
Copy link
Member Author

Elchi3 commented Oct 14, 2025

Thank you, @moz-mdauer! I updated the abstract fetch code example with your specific bank example, as it is more bad and maybe brings across the spookiness of this even more.

Also thanks for the pointer to server-side prototype pollution! I think it is a very valuable resource, so I added it along with the client-side prototype pollution repo to the "See also" of this article. Overall, I think the article explains this more in the context of the client-side but that's quite typical for the MDN audience and I think that's alright.

I've heard good feedback on this article from various people now, so I'm planning to merge it later today or tomorrow. Thanks again, everyone, for your contributions and your feedback!

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Copy link
Collaborator

@wbamberg wbamberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you Florian, Josh, and everyone else who's contributed to this!

@wbamberg wbamberg merged commit 00c3b9f into mdn:main Oct 17, 2025
8 checks passed
@Elchi3 Elchi3 deleted the attacks-pp branch October 17, 2025 15:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Content:JS JavaScript docs Content:Security Security docs size/m [PR only] 51-500 LoC changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

None yet

6 participants