├── .gitattributes ├── .github └── workflows │ └── build.yml ├── .gitignore ├── .npmrc ├── LICENSE ├── README.md ├── index.html ├── package.json └── spec.emu /.gitattributes: -------------------------------------------------------------------------------- 1 | index.html -diff merge=ours 2 | spec.js -diff merge=ours 3 | spec.css -diff merge=ours 4 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Deploy spec 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions/setup-node@v1 12 | with: 13 | node-version: '12.x' 14 | - run: npm install 15 | - run: npm run build 16 | - name: commit changes 17 | uses: elstudio/actions-js-build/commit@v3 18 | with: 19 | commitMessage: "fixup: [spec] `npm run build`" 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # Only apps should have lockfiles 40 | yarn.lock 41 | package-lock.json 42 | npm-shrinkwrap.json 43 | pnpm-lock.yaml 44 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 ECMA TC39 and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Prototype Pollution Mitigation / Symbol.proto 3 | 4 | **Authors**: [Santiago Díaz](https://github.com/salcho) (Google) 5 | 6 | **Champion**: Shu-yu Guo (Google) 7 | 8 | **Stage**: 1 9 | 10 | # TOC 11 | 12 | * [Problem Description](#problem-description) 13 | * [Spooky action at a distance](#spooky-action-at-a-distance) 14 | * [Data-only attacks](#data-only-attacks) 15 | * [Issues with `freeze`, `seal` and `preventExtensions`](#issues-with-freeze-seal-and-preventExtensions-) 16 | * [The override mistake](#the-override-mistake) 17 | * [Coarse granularity](#coarse-granularity) 18 | * [Freezing points](#freezing-points) 19 | * [Application types](#application-types) 20 | * [Proposed solution](#proposed-solution) 21 | * [Provide reflection APIs](#provide-reflection-apis) 22 | * [Opt-in feature](#opt-in-feature) 23 | * [Automatic refactoring](#automatic-refactoring) 24 | * [What does delete mean?](#what-does-delete-mean) 25 | * [Incompatible code bases](#incompatible-code-bases) 26 | * [Appendix](#appendix) 27 | * [What about constructor pollution?](#what-about-constructor-pollution) 28 | * [Computed access in minimized JS](#computed-access-in-minimized-JS) 29 | * [Example vulnerabilities](#example-vulnerabilities) 30 | 31 | # tl;dr 32 | 33 | This proposal seeks to mitigate a language-level vulnerability known as prototype pollution with a mechanism that complements freeze primitives and a mechanism to make most code bases compatible with it. It describes an opt-in feature that makes prototypes available only through reflection APIs. By doing so, the statement `obj[key]` can't access prototypes anymore. Code bases compatible with this feature are more _intentional_ about the way they use prototypes. 34 | 35 | # Problem Description 36 | 37 | ## Spooky action at a distance 38 | 39 | PP vulnerabilities allow attackers to manipulate objects they don't control or don't have access to at runtime. This 'spooky action at a distance' primitive can be used to change the shape of other objects and override their properties, thereby tainting objects in the runtime. 40 | 41 | Tainted objects invalidate the underlying assumptions of code that would otherwise be safe/correct and can lead to arbitrary code execution and a wide range of other security issues in JS code bases. Prototype pollution bugs express themselves often in web applications, but also affect non-web JS runtime. 42 | 43 | Object properties in JS are writeable by any code that can reference them. In particular, if many objects rely on a shared property, any one of them can enact changes on all others. 44 | 45 | ## Data-only attacks 46 | 47 | A special property of PP is that it is a data-only attack, allowing code execution to be attained purely through data. For instance, see the following vulnerable code and a corresponding exploit: 48 | 49 | ```javascript 50 | // source is attacker-controlled 51 | function merge(target, source) { 52 | for (let key in source) { 53 | if (typeof source[key] === 'object') { 54 | if(target[key] === undefined) { 55 | target[key] = {}; 56 | } 57 | target[key] = merge(target[key], source[key]); 58 | } else { 59 | target[key] = source[key]; 60 | } 61 | } 62 | return target; 63 | } 64 | 65 | // User input comes as a string 66 | const userSuppliedObj = JSON.parse('{"__proto__": {"polluted": true}}'); 67 | // Trigger prototype pollution 68 | merge({}, userSuppliedObj); 69 | // Create a brand new object 70 | const newObj = {}; 71 | // Has polluted property 72 | console.log(newObj.polluted); // true 73 | ``` 74 | 75 | Note that the exploit is able to taint the creation of new objects _without injecting any foreign code_. 76 | 77 | Because of this special property, modern mitigations against code execution issues -like the Content Security Policy or Trusted Types- fall short of protecting against PP, as they focus on enforcing _code provenance_. 78 | 79 | **Note** that data-only attacks are relevant to situations where the code running on the VM is trusted and arbitrary code execution has security impact. 80 | 81 | # Issues with `freeze`, `seal` and `preventExtensions` 82 | 83 | Existing freezing primitives suffer from significant design issues that make them unlikely to be widely adopted. They can be useful to expert users, but are not suitable to be deployed by the majority of developers, who **reasonably expect prototypes to be mutable**: 84 | 85 | ### The override mistake 86 | 87 | Freeze APIs suffer from the override mistake and [other inconsistencies](https://docs.google.com/presentation/d/17dji3YM5_LeMvdAD3Y3PQoXU1Mgm5e2yN_BraBSTkjo/edit) which introduce bugs in existing code bases, making them throw or worse, _silently fail_ in sloppy mode. A [previous investigation](https://github.com/tc39/ecma262/pull/1320) of the override mistake concluded that the override mistake triggers on [~10% of code bases in strict mode](https://chromestatus.com/metrics/feature/timeline/popularity/2610) and [20% in sloppy mode](https://chromestatus.com/metrics/feature/timeline/popularity/2609). The investigation was dropped shortly after. 88 | 89 | ### Coarse granularity 90 | 91 | Freeze APIs give developers the heavy responsibility of knowing which prototypes should be frozen to maintain a secure code base, assuming that developers are security experts. These APIs describe the _what_ but not the _how_ of security. Freezing `Object` is certainly not good enough, as many exploits abuse `Array`. What about `Error`, `Date`, `Reflect` or `Proxy`? Or future built-in types? Freeze APIs provide no answers to these questions. 92 | 93 | ### Freezing points 94 | 95 | Freeze APIs assume a stable freezing point: a fixed moment at runtime where prototypes have settled and can be frozen. In practice, this point is volatile and changes over time in code bases that are actively developed. While one can find such a point in many applications today, the addition of new dependencies, polyfills, code structure changes and power features like hotswapping and developer tools make freezing points a moving target. 96 | 97 | ### Application types 98 | 99 | Freeze APIs can't protect the full prototype chain. In JS, objects can be added or removed from the prototype chain at any point in time. To protect the full chain, one should always remember to freeze objects that are added to the chain, an error-prone process. When they are removed from the chain, they cannot be made unfrozen anymore. 100 | 101 | # Proposed solution 102 | 103 | In a nutshell: **a feature that exposes prototypes only to reflection APIs**. If prototypes weren't made available through properties like `__proto__` or `prototype`, they would not be exposed to data-only issues. 104 | 105 | This is better understood through an example: the statement `obj[one][two] = value` is vulnerable to PP through `obj.__proto__.polluted`. If one deletes the `Object.prototype.__proto__` property, the same statement is no longer vulnerable because it can't fit the only other way to reach prototypes, which is `obj.constructor.prototype.polluted`. Note that prototype can't be deleted. 106 | 107 | This proposal can be implemented by providing **reflection APIs** and creating a new **opt-in encapsulation feature** that **deletes prototype properties**. A description of each step follows. 108 | 109 | ### Provide reflection APIs 110 | 111 | `__proto__` is a legacy property name that can be deleted, but the internal slot behind it can still be read through `Object/Reflect.getPrototypeOf` and written through `Object/Reflect.setPrototypeOf`, which will simply continue to make this property accessible to code that is already running. 112 | 113 | We propose the creation of new APIs for `prototype`, for example `getClassPrototypeOf` and `setClassPrototypeOf`, which would allow this property name to be deleted without changing in any way how this special property works and supports the VM. 114 | 115 | Reflection APIs can be polyfilled, which allows hardened code bases to work in all browsers, including older versions. 116 | 117 | ### Opt-in feature 118 | 119 | A new opt-in 'encapsulation feature' where no property names are created for the getter and setter function of prototype slots, which is now possible because references to those properties can use reflection APIs instead. 120 | 121 | The feature is enabled through an **out-of-band flag**: 122 | 123 | - In **browser contexts**, through an HTTP header like `X-Encapsulate-Prototype: true` 124 | - In **other contexts**, through a feature flag like `--encapsulate-prototype` 125 | 126 | When encapsulation is _disabled_, prototypes are available via both properties and reflection APIs. 127 | 128 | When encapsulation is _enabled_, prototypes are only available via reflection APIs, having deleted both `__proto__` and `prototype`. 129 | 130 | Encapsulation also includes the following **automatic refactoring** feature: 131 | 132 | ### Automatic refactoring 133 | 134 | When encapsulation is enabled, JS engines loading new source code enable an extra step in their parse phases that registers all dot-notation to prototype properties as if they were calls to their reflection APIs. This step can be implemented efficiently and allows code bases with **third-party, transitive or dynamically-loaded dependencies to be compatible with encapsulation**. 135 | 136 | In the future, this change will pave the way for marking `prototype` as deprecated. 137 | 138 | ### What does delete mean? 139 | 140 | Prototype properties could simply be `undefined` when encapsulation is enabled, but they could throw an error when there are attempts to read/write them. This would mean failing faster and loud and would allow migrations to the reflection APIs/encapsulation to be tested. 141 | 142 | This implies making the getters and setters of `__proto__` and `prototype` conditional on encapsulation, by using a host hook, in the same way the `eval` function throws under Content Security Policy. 143 | 144 | # Incompatible code bases 145 | 146 | Code that _relies on computed property access_ to reference prototypes is not compatible with encapsulation or with automatic refactoring. It must be refactored to explicitly refer to prototypes when they are used. This refactoring actually makes the code express intent, which makes dangerous patterns visible to static analysis. In practice, code bases with this characteristic are usually reflection frameworks, debugging tools and other reflection-heavy use cases that are most likely aware of how they use prototypes. 147 | 148 | Code bases that use the word `prototype` to define custom properties are not compatible. Such code bases can be made compatible with encapsulation if this property is always set/get through bracket notation. Historically and based on HTTP Archive queries, there is a small percentage of code bases that are incompatible for this reason. 149 | 150 | # Appendix 151 | 152 | ### What about constructor pollution? 153 | 154 | Some changes to the `constructor` property can also have spooky action at a distance. During our research, we have found no practical vulnerabilities affected by this. 155 | 156 | The bar is significantly high for this attack to work: Like in PP, one must find an application with gadgets to both write **and** read arbitrary properties. But in constructor pollution the reading gadget must read from `constructor.polluted` instead of `polluted`. This dramatically reduces the number of useful gadgets. 157 | 158 | ### Computed access in minimized JS 159 | 160 | Some minimized JS may be incompatible with encapsulation mode, because static property access could be minified into computed access. We have queried the [HTTP Archive](http://httparchive.org) to get an estimate of this in practice. The following table shows that pages with this behavior are consistently below 1% throughout the last 12 months for all pages crawled with a desktop browser: 161 | 162 | | Table | Documents accessing `__proto__` or `constructor` dynamically | Total number of crawled documents | Ratio | 163 | |--------------------|--------------------------------------------------------------|-----------------------------------|-------| 164 | | 2023_03_01_desktop | 5,407,936 | 609,469,458 | 0.89% | 165 | | 2023_02_01_desktop | 4,842,383 | 549,089,708 | 0.88% | 166 | | 2023_01_01_desktop | 5,283,826 | 589,519,160 | 0.90% | 167 | | 2022_12_01_desktop | 5,161,471 | 577,073,883 | 0.89% | 168 | | 2022_11_01_desktop | 5,023,169 | 561,726,239 | 0.89% | 169 | | 2022_10_01_desktop | 4,393,377 | 476,880,624 | 0.92% | 170 | | 2022_09_01_desktop | 4,239,257 | 466,278,762 | 0.91% | 171 | | 2022_08_01_desktop | 4,259,814 | 463,784,047 | 0.92% | 172 | | 2022_07_01_desktop | 3,011,137 | 339,468,615 | 0.89% | 173 | | 2022_06_01_desktop | 2,301,317 | 257,501,222 | 0.89% | 174 | | 2022_04_01_desktop | 2,368,577 | 263,144,657 | 0.90% | 175 | | 2022_03_01_desktop | 2,319,518 | 259,249,013 | 0.89% | 176 | 177 | ### Example vulnerabilities 178 | 179 | Google has seen an upward trend in bugs submitted to our Vulnerability Rewards Program: 1 in 2020, 3 in 2021 and 5 so far in 2022. We have identified several more in our internal research. 180 | 181 | Example vulnerabilities include: 182 | 183 | 1. **On the Web**: Several XSS issues in services that should have been protected because they use [Strict CSP](https://w3c.github.io/webappsec-csp/#strict-csp). And a [wide range of known vulnerable libraries](https://github.com/BlackFan/client-side-prototype-pollution). 184 | 1. **On the desktop**: An bug in a Google-owned desktop application where users could be given a malicious JSON object that could allow local files to be leaked due to a pollution vulnerability. (Currently non-public, disclosure TBD.) 185 | 1. **In security features**: Multiple bypasses in sanitizers, including [Chrome's Sanitizer API](https://crbug.com/1306450), [DOMPurify and the Closure sanitizer](https://research.securitum.com/prototype-pollution-and-bypassing-client-side-html-sanitizers/). 186 | 1. **In the browser**: A [Firefox sandbox escape](https://www.zerodayinitiative.com/blog/2022/8/23/but-you-told-me-you-were-safe-attacking-the-mozilla-firefox-renderer-part-2) leading to remote code execution. 187 | 1. **In NodeJS**: Several [RCE](https://research.securitum.com/prototype-pollution-rce-kibana-cve-2019-7609/)s have been discovered. 188 | 189 | We expect the number of vulnerable applications will grow as JavaScript applications are deployed to more environments (e.g. Electron, Cloudflare Workers, etc). Therefore, a language-level solution is required to mitigate attacks in all environments. 190 | 191 | 192 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Proposal Title Goes Here
2519 |

Stage -1 Draft / May 23, 2022

Proposal Title Goes Here

2528 | 2529 | 2530 |

1 This is an emu-clause

2531 |

This is an algorithm:

2532 |
  1. Let proposal be undefined.
  2. If IsAccepted(proposal), then
    1. Let stage be 0.
  3. Else,
    1. Let stage be -1.
  4. Return ? ToString(proposal).
2533 |
2534 |

A Copyright & Software License

2535 | 2536 |

Copyright Notice

2537 |

© 2022 Your Name(s) Here

2538 | 2539 |

Software License

2540 |

All Software contained in this document ("Software") is protected by copyright and is being made available under the "BSD License", included below. This Software may be subject to third party rights (rights from parties other than Ecma International), including patent rights, and no licenses under such third party rights are granted under this license even if the third party concerned is a member of Ecma International. SEE THE ECMA CODE OF CONDUCT IN PATENT MATTERS AVAILABLE AT https://ecma-international.org/memento/codeofconduct.htm FOR INFORMATION REGARDING THE LICENSING OF PATENT CLAIMS THAT ARE REQUIRED TO IMPLEMENT ECMA INTERNATIONAL STANDARDS.

2541 | 2542 |

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

2543 | 2544 |
    2545 |
  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  2. 2546 |
  3. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  4. 2547 |
  5. Neither the name of the authors nor Ecma International may be used to endorse or promote products derived from this software without specific prior written permission.
  6. 2548 |
2549 | 2550 |

THIS SOFTWARE IS PROVIDED BY THE ECMA INTERNATIONAL "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ECMA INTERNATIONAL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

2551 | 2552 |
2553 |
-------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "template-for-proposals", 4 | "description": "A repository template for ECMAScript proposals.", 5 | "scripts": { 6 | "start": "npm run build-loose -- --watch", 7 | "build": "npm run build-loose -- --strict", 8 | "build-loose": "ecmarkup --load-biblio @tc39/ecma262-biblio --verbose spec.emu index.html --lint-spec" 9 | }, 10 | "homepage": "https://github.com/tc39/template-for-proposals#readme", 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/tc39/template-for-proposals.git" 14 | }, 15 | "license": "MIT", 16 | "devDependencies": { 17 | "@tc39/ecma262-biblio": "^2.1.2390", 18 | "ecmarkup": "^14.1.1" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /spec.emu: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
 7 | title: Proposal Title Goes Here
 8 | stage: -1
 9 | contributors: Your Name(s) Here
10 | 
11 | 12 | 13 |

This is an emu-clause

14 |

This is an algorithm:

15 | 16 | 1. Let _proposal_ be *undefined*. 17 | 1. If IsAccepted(_proposal_), then 18 | 1. Let _stage_ be *0*. 19 | 1. Else, 20 | 1. Let _stage_ be *-1*. 21 | 1. Return ? ToString(_proposal_). 22 | 23 |
24 | --------------------------------------------------------------------------------