Skip to content

ericcornelissen/pp-runtime-gadgets

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

23 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Prototype Pollution affects the JavaScript Runtime

This repository aims to provide a list of JavaScript language functionality that can be affected by prototype pollution.

TODO

  • Manually check all 160 Get( in the fixed spec reference.
  • Double check all 160 Get( in the fixed spec reference.
  • Manually check other Get(-like functions in the fixed spec reference.
  • Replicate Reflect.ownKeys' gadget with other uses of [[OwnPropertyKeys]].
  • Similar gadgets to those for RegExp.prototype[@@match] and RegExp.prototype[@@matchAll] in other RegExp.prototype functions.

Reproduce

To reproduce the results:

  • for Node.js: make test-node (or make test-node-docker)
  • for Deno: make test-deno (or make test-deno-docker)
  • for Browsers: make test-web and open http://localhost:8080 in a browser.

Overview

The table below provides an overview of known functions affected by prototype pollution in the JavaScript language, or gadgets. This list is not exhaustive both in terms of affected APIs and usable properties.

All gadgets were tested on Node.js v24.0.1, Deno v1.46.1, Chromium (Desktop) v136, and Firefox (Desktop) v138.

The "Score" is an attempt at capturing how easy it is to exploit the gadget in an arbitrary program, lower is easier. The contributors to the score are defined in the gadget PoC and the scoring system is defined in score.js.

API Prop(s) Score Node.js Deno Chromium Firefox
[[ToPrimitive]] 'toString' 3 Yes Yes Yes Yes
'valueOf' 3 Yes Yes Yes Yes
new AggregateError 'cause' 0 Yes Yes Yes Yes
new ArrayBuffer 'maxByteLength' 1 Yes Yes Yes Yes
Array.from <n> 2 Yes Yes Yes Yes
Array.prototype.at <n> 1 Yes Yes Yes Yes
Array.prototype.concat @@isConcatSpreadable 5 Yes Yes Yes Yes
<n> 1 Yes Yes Yes Yes
Array.prototype.copyWithin <n> 1 Yes Yes Yes Yes
Array.prototype.every <n> 1 Yes Yes Yes Yes
Array.prototype.fill <n> 1 Yes Yes Yes Yes
Array.prototype.filter <n> 1 Yes Yes Yes Yes
Array.prototype.find <n> 1 Yes Yes Yes Yes
Array.prototype.findIndex <n> 1 Yes Yes Yes Yes
Array.prototype.findLast <n> 1 Yes Yes Yes Yes
Array.prototype.findLastIndex <n> 1 Yes Yes Yes Yes
Array.prototype.flat <n> 1 Yes Yes Yes Yes
Array.prototype.flatMap <n> 1 Yes Yes Yes Yes
<n> 1 Yes Yes Yes Yes
Array.prototype.forEach <n> 1 Yes Yes Yes Yes
Array.prototype.includes <n> 1 Yes Yes Yes Yes
Array.prototype.indexOf <n> 1 Yes Yes Yes Yes
Array.prototype.join <n> 1 Yes Yes Yes Yes
Array.prototype.lastIndexOf <n> 1 Yes Yes Yes Yes
Array.prototype.map <n> 1 Yes Yes Yes Yes
Array.prototype.pop <n> 1 Yes Yes Yes Yes
Array.prototype.reduce <n> 1 Yes Yes Yes Yes
<n> 1 Yes Yes Yes Yes
Array.prototype.reduceRight <n> 1 Yes Yes Yes Yes
Array.prototype.reverse <n> 1 Yes Yes Yes Yes
Array.prototype.shift <n> 1 Yes Yes Yes Yes
<n> 1 Yes Yes Yes Yes
Array.prototype.slice <n> 1 Yes Yes Yes Yes
Array.prototype.some <n> 1 Yes Yes Yes Yes
Array.prototype.sort <n> 1 Yes Yes Yes Yes
Array.prototype.splice <n> 1 Yes Yes Yes Yes
<n> 1 Yes Yes Yes Yes
<n> 1 Yes Yes Yes Yes
Array.prototype.toLocaleString <n> 1 Yes Yes Yes Yes
Array.prototype.toReversed <n> 1 Yes Yes Yes Yes
Array.prototype.toSorted <n> 1 Yes Yes Yes Yes
Array.prototype.toSpliced <n> 1 Yes Yes Yes Yes
<n> 1 Yes Yes Yes Yes
Array.prototype.toString join 5 Yes Yes Yes Yes
Array.prototype.unshift <n> 1 Yes Yes Yes Yes
Array.prototype.with <n> 1 Yes Yes Yes Yes
new Error 'cause' 0 Yes Yes Yes Yes
Function.prototype.apply <n> 2 Yes Yes Yes Yes
Function.prototype.bind 'name' 0 No No No No
isNaN 'valueOf' 3 Yes Yes Yes Yes
Iterator 'done' 2 Yes Yes Yes Yes
'next' 5 Yes Yes Yes Yes
'return' 5 Yes Yes Yes Yes
'value' 2 Yes Yes Yes Yes
JSON.stringify <n> 2 Yes Yes Yes Yes
'toJSON' 3 Yes Yes Yes Yes
new Map 0,1 1 Yes Yes Yes Yes
Object.defineProperties 'configurable' 0 Yes Yes Yes Yes
'enumerable' 0 Yes Yes Yes Yes
'get' 3 Yes Yes Yes Yes
'set' 3 Yes Yes Yes Yes
'value' 1 Yes Yes Yes Yes
'writable' 0 Yes Yes Yes Yes
Object.defineProperty 'configurable' 0 Yes Yes Yes Yes
'enumerable' 0 Yes Yes Yes Yes
'get' 3 Yes Yes Yes Yes
'set' 3 Yes Yes Yes Yes
'value' 1 Yes Yes Yes Yes
'writable' 0 Yes Yes Yes Yes
Object.entries 'enumerable' 2 Yes Yes Yes Yes
Object.fromEntries 0,1 1 Yes Yes Yes Yes
Object.keys <k>,'enumerable' 5 Yes Yes Yes Yes
'enumerable' 3 Yes Yes Yes Yes
Object.prototype.toString @@toStringTag 3 Yes Yes Yes Yes
Object.values 'enumerable' 3 Yes Yes Yes Yes
new Proxy 'apply' 3 Yes Yes Yes Yes
'construct' 3 Yes Yes Yes Yes
'defineProperty' 3 Yes Yes Yes Yes
'deleteProperty' 3 Yes Yes Yes Yes
'getOwnPropertyDescriptor' 3 Yes Yes Yes Yes
'isExtensible' 3 Yes Yes Yes Yes
'ownKeys' 3 Yes Yes Yes Yes
'preventExtensions' 3 Yes Yes Yes Yes
'set' 3 Yes Yes Yes Yes
'setPrototypeOf' 3 Yes Yes Yes Yes
Reflect.apply <n> 2 Yes Yes Yes Yes
Reflect.construct <n> 2 Yes Yes Yes Yes
Reflect.defineProperty 'configurable' 0 Yes Yes Yes Yes
'enumerable' 0 Yes Yes Yes Yes
'get' 3 Yes Yes Yes Yes
'set' 3 Yes Yes Yes Yes
'value' 1 Yes Yes Yes Yes
'writable' 0 Yes Yes Yes Yes
Reflect.ownKeys <n> 5 Yes Yes Yes Yes
new RegExp 'source' 2 Yes Yes Yes Yes
RegExp.prototype[@@match] 0 2 Yes Yes Yes Yes
'exec' 2 Yes Yes Yes Yes
'flags' 2 Yes No Yes Yes
'global' 2 No Yes No No
RegExp.prototype[@@matchAll] 'flags' 2 Yes Yes Yes Yes
'lastIndex' 2 Yes Yes Yes Yes
new SharedArrayBuffer 'maxByteLength' 1 Yes Yes Unsupported Unsupported
Set.prototype.difference 'has','size' 3 Yes Yes Yes Yes
Set.prototype.intersection 'has','size' 3 Yes Yes Yes Yes
Set.prototype.isDisjointFrom 'has','size' 3 Yes Yes Yes Yes
Set.prototype.isSubsetOf 'has','size' 3 Yes Yes Yes Yes
Set.prototype.isSupersetOf 'has','size' 3 Yes Yes Yes Yes
Set.prototype.symmetricDifference 'has','size' 3 Yes Yes Yes Yes
Set.prototype.union 'has','size' 3 Yes Yes Yes Yes
String.prototype.endsWith @@match 5 Yes Yes Yes Yes
String.prototype.includes @@match 5 Yes Yes Yes Yes
String.prototype.matchAll @@match,@@matchAll,'flags' 8 Yes Yes Yes Yes
String.prototype.replaceAll @@match,@@replace,'flags' 8 Yes Yes Yes Yes
String.prototype.startsWith @@match 5 Yes Yes Yes Yes
String.raw 'raw' 2 Yes Yes Yes Yes
TypedArray.from <n> 2 Yes Yes Yes Yes
with @@unscopables 3 Yes Unsupported Yes Yes

where:

  • /[0-9]+/: is a specific numeric property.
  • /'.+?'/: is a specific string property.
  • /@@.+?/: is a specific well-known symbol.
  • <n>: is any numeric property.
  • <k>: is any property.

notes:

  • Array.prototype[*] gadgets generally should also work on their TypedArray equivalent.
  • Iterator[*] gadgets generally should also work on their async equivalent.

Unaffected

The table below lists evaluated sections in the ECMAScript spec which were deemed unaffected by prototype pollution.

API Property Reason
[[DefineOwnProperty]] <k> Not evaluated
[[Get]] <k> Not evaluated
[[GetOwnProperty]] <k> Not evaluated
ArraySpeciesCreate 'constructor' Object on which lookup should happen must be an array, which means it must have a constructor property.
@@species Object on which lookup should happen must be an array constructor, which means it must have a @@species property.
Array.prototype.reduceRight <n> Cannot affect initial value because the last index necessarily coincides with the array length.
HasBinding @@unscopable Not evaluated
<n> Not evaluated
CopyDataProperties <k> Implementation should ToObject the subject, hence all own keys are actually own keys.
CreateRegExpStringIterator 0 Only ever invoked on a properly constructed RegExp, meaning 0 is always defined.
'lastIndex' This property is explicitly set in all functions calling this abstract operation.
Error.prototype.toString 'name' this will realistically only be an instance of Error, in which case name is defined on Error.prototype.
'message' this will realistically only be an instance of Error, in which case message is defined on Error.prototype.
Function.prototype.bind 'length' It is checked that the property is an own property before it is accessed.
GetBindingValue <n> Checks HasProperty before Get.
GetPrototypeFromConstructor 'prototype' Object on which lookup should happen must be a callable, which means it must have a prototype property.
GetSubstitution <k> Getting properties on the namedCapture object which always has a null prototype.
Object.assign <k> Will only access keys in the [[OwnPropertyKeys]] set.
Object.freeze <k> -
Object.seal <k> -
ObjectDefineProperties <k> Will only access keys in the [[OwnPropertyKeys]] set.
OrdinaryHasInstance 'prototype' Object on which lookup should happen must be a callable, which means it must have a prototype property.
PromiseResolve 'constructor' It is checked that x is a Promise.
RegExpBuiltinExec 'lastIndex' R is only ever an initialized RegExp.
RegExp.prototype.toString 'source' this will realistically only be an instance of RegExp, in which case source is always defined.
'flags' this will realistically only be an instance of RegExp, in which case flags is always defined.
RegExp.prototype[@@match] 'unicode' Only affects lastIndex incrementing, effect depends on user implementation (Note: may be accessed instead of "flags").
'unicodeSets' Only affects lastIndex incrementing, effect depends on user implementation (Note: may be accessed instead of "flags").

Methodology

Two approaches have been used to compile the list of usable and unusable runtime gadgets.

Manual (in progress)

A manual review of the ECMAScript spec has been conducted, specifically looking for use of the Get(O, P) function. This function gets property P from object O, hence if P is missing from O the lookup could be affected by prototype pollution.

During manual testing, a proxy object like the one shown below is used to find out what properties are being looked up exactly.

const proxy = new Proxy({}, {
  get(target, property, _receiver) {
   if (!Object.hasOwn(target, property)) {
    console.log("looked up:", property);
   }

   return target[property];
  },
});

tc39/test262

The tc39/test262 suite is an extensive conformance test suite for the ECMAScript specification. Hence, it should provide extensive coverage of all JavaScript language features, and can thus be used to help automatically find gadgets.

The tc39 directory contains the materials necessary for using the test262 suite in a semi-automated pipeline for finding runtime gadgets. More detail can be found in its README.md.

Related Work

About

Gadgets in the JavaScript runtime based on the ECMAScript specification

Topics

Resources

License

Stars

Watchers

Forks

Contributors 2

  •  
  •