This repository aims to provide a list of JavaScript language functionality that can be affected by prototype pollution.
- Continue in the spec (fixed reference), currently at
Get(
120/160. - Replicate gadgets from
Object.defineProperty
withObject.defineProperties
. - Replicate
Reflect.ownKeys
' gadget with other uses of[[OwnPropertyKeys]]
. - Similar gadgets to those for
RegExp.prototype[@@match]
andRegExp.prototype[@@matchAll]
in otherRegExp.prototype
functions. - Remaining gadgets in
Array.prototype
(fairly certain they're affected based on the existing gadgets, but a PoC still needs to be created).- https://tc39.es/ecma262/#sec-array.prototype.indexof
- https://tc39.es/ecma262/#sec-array.prototype.lastindexof
- https://tc39.es/ecma262/#sec-array.prototype.map
- https://tc39.es/ecma262/#sec-array.prototype.reverse
- https://tc39.es/ecma262/#sec-array.prototype.slice
- https://tc39.es/ecma262/#sec-array.prototype.tolocalestring
- https://tc39.es/ecma262/#sec-array.prototype.unshift
To reproduce the results:
- for Node.js:
make test-node
(ormake test-node-docker
) - for Deno:
make test-deno
(ormake test-deno-docker
) - for Browsers:
make test-web
and open http://localhost:8080 in a browser.
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.
We indicate what kind of pollution is required to use the gadgets. This helps indicate how easy it is to exploit a given gadget because data only attacks are easier in practice.
1
: Data - Gadget that work when polluting with data only.2
: Function - Gadget that require polluting with a function.3
: Function/DoS - Gadget that require polluting with a function, but polluting with a value will cause an exception.
We try to categorize the gadgets into types. These types are very subjective and mostly try to give an indication of how problematic the gadget is in terms of language design.
1
: Language - Gadgets that occur because of the language design.2
: User provided object - Gadgets that occur because a user provided object is used.3
: Faulty implementation - Gadgets that occur because a user uses an object that is implemented incorrectly.
All gadgets were tested on Node.js v22.7.0, Deno v1.46.1, Chromium (Desktop) v131, and Firefox (Desktop) v133.
API | Prop(s) | Level | Type | Node.js | Deno | Chromium | Firefox |
---|---|---|---|---|---|---|---|
[[ToPrimitive]] |
'toString' |
3 |
1 |
Yes | Yes | Yes | Yes |
'valueOf' |
2 |
1 |
Yes | Yes | Yes | Yes | |
new AggregateError |
'cause' |
1 |
1 |
Yes | Yes | Yes | Yes |
new ArrayBuffer |
'maxByteLength' |
1 |
2 |
Yes | Yes | Yes | Yes |
Array.from |
<n> |
1 |
3 |
Yes | Yes | Yes | Yes |
Array.prototype.at |
<n> |
1 |
1 |
Yes | Yes | Yes | Yes |
Array.prototype.concat |
<n> |
1 |
3 |
Yes | Yes | Yes | Yes |
@@isConcatSpreadable |
1 |
3 |
Yes | Yes | Yes | Yes | |
Array.prototype.copyWithin |
<n> |
1 |
1 |
Yes | Yes | Yes | Yes |
Array.prototype.every |
<n> |
1 |
1 |
Yes | Yes | Yes | Yes |
Array.prototype.fill |
<n> |
1 |
1 |
Yes | Yes | Yes | Yes |
Array.prototype.filter |
<n> |
1 |
1 |
Yes | Yes | Yes | Yes |
Array.prototype.find |
<n> |
1 |
1 |
Yes | Yes | Yes | Yes |
Array.prototype.findIndex |
<n> |
1 |
1 |
Yes | Yes | Yes | Yes |
Array.prototype.findLast |
<n> |
1 |
1 |
Yes | Yes | Yes | Yes |
Array.prototype.findLastIndex |
<n> |
1 |
1 |
Yes | Yes | Yes | Yes |
Array.prototype.flat |
<n> |
1 |
1 |
Yes | Yes | Yes | Yes |
Array.prototype.flatMap |
<n> |
1 |
1 |
Yes | Yes | Yes | Yes |
<n> |
1 |
1 |
Yes | Yes | Yes | Yes | |
Array.prototype.forEach |
<n> |
1 |
1 |
Yes | Yes | Yes | Yes |
Array.prototype.includes |
<n> |
1 |
1 |
Yes | Yes | Yes | Yes |
Array.prototype.join |
<n> |
1 |
1 |
Yes | Yes | Yes | Yes |
Array.prototype.pop |
<n> |
1 |
3 |
Yes | Yes | Yes | Yes |
Array.prototype.reduce |
<n> |
1 |
1 |
Yes | Yes | Yes | Yes |
Array.prototype.reduceRight |
<n> |
1 |
1 |
Yes | Yes | Yes | Yes |
Array.prototype.shift |
<n> |
1 |
3 |
Yes | Yes | Yes | Yes |
Array.prototype.some |
<n> |
1 |
1 |
Yes | Yes | Yes | Yes |
Array.prototype.sort |
<n> |
1 |
1 |
Yes | Yes | Yes | Yes |
Array.prototype.splice |
<n> |
1 |
1 |
Yes | Yes | Yes | Yes |
Array.prototype.toReversed |
<n> |
1 |
1 |
Yes | Yes | Yes | Yes |
Array.prototype.toSorted |
<n> |
1 |
1 |
Yes | Yes | Yes | Yes |
Array.prototype.toSpliced |
<n> |
1 |
1 |
Yes | Yes | Yes | Yes |
Array.prototype.toString |
join |
3 |
3 |
Yes | Yes | Yes | Yes |
Array.prototype.with |
<n> |
1 |
1 |
Yes | Yes | Yes | Yes |
new Error |
'cause' |
1 |
1 |
Yes | Yes | Yes | Yes |
Function.prototype.apply |
<n> |
1 |
3 |
Yes | Yes | Yes | Yes |
Function.prototype.bind |
'name' |
1 |
3 |
No | No | No | No |
Iterator |
'done' |
3 |
1 |
Yes | Yes | Yes | Yes |
'next' |
3 |
3 |
Yes | Yes | Yes | Yes | |
'return' |
3 |
3 |
Yes | Yes | Yes | Yes | |
'value' |
3 |
1 |
Yes | Yes | Yes | Yes | |
JSON.stringify |
'toJSON' |
2 |
1 |
Yes | Yes | Yes | Yes |
Object.defineProperty |
'configurable' |
1 |
2 |
Yes | Yes | Yes | Yes |
'enumerable' |
1 |
2 |
Yes | Yes | Yes | Yes | |
'get' |
3 |
2 |
Yes | Yes | Yes | Yes | |
'set' |
3 |
2 |
Yes | Yes | Yes | Yes | |
'value' |
1 |
2 |
Yes | Yes | Yes | Yes | |
'writable' |
1 |
2 |
Yes | Yes | Yes | Yes | |
Object.entries |
'enumerable' |
1 |
3 |
Yes | Yes | Yes | Yes |
Object.fromEntries |
0,1 |
1 |
1 |
Yes | Yes | Yes | Yes |
Object.keys |
'enumerable' |
1 |
3 |
Yes | Yes | Yes | Yes |
Object.prototype.toString |
@@toStringTag |
1 |
3 |
Yes | Yes | Yes | Yes |
Object.values |
'enumerable' |
1 |
3 |
Yes | Yes | Yes | Yes |
new Proxy |
'apply' |
3 |
2 |
Yes | Yes | Yes | Yes |
'construct' |
3 |
2 |
Yes | Yes | Yes | Yes | |
'defineProperty' |
3 |
2 |
Yes | Yes | Yes | Yes | |
'deleteProperty' |
3 |
2 |
Yes | Yes | Yes | Yes | |
'getOwnPropertyDescriptor' |
3 |
2 |
Yes | Yes | Yes | Yes | |
'isExtensible' |
3 |
2 |
Yes | Yes | Yes | Yes | |
'ownKeys' |
3 |
2 |
Yes | Yes | Yes | Yes | |
'preventExtensions' |
3 |
2 |
Yes | Yes | Yes | Yes | |
'set' |
3 |
2 |
Yes | Yes | Yes | Yes | |
'setPrototypeOf' |
3 |
2 |
Yes | Yes | Yes | Yes | |
Reflect.apply |
<n> |
1 |
3 |
Yes | Yes | Yes | Yes |
Reflect.construct |
<n> |
1 |
3 |
Yes | Yes | Yes | Yes |
Reflect.defineProperty |
'configurable' |
1 |
2 |
Yes | Yes | Yes | Yes |
'enumerable' |
1 |
2 |
Yes | Yes | Yes | Yes | |
'get' |
3 |
2 |
Yes | Yes | Yes | Yes | |
'set' |
3 |
2 |
Yes | Yes | Yes | Yes | |
'value' |
1 |
2 |
Yes | Yes | Yes | Yes | |
'writable' |
1 |
2 |
Yes | Yes | Yes | Yes | |
Reflect.ownKeys |
<n> |
1 |
3 |
Yes | Yes | Yes | Yes |
new RegExp |
'source' |
1 |
3 |
Yes | Yes | Yes | Yes |
RegExp.prototype[@@match] |
0 |
1 |
3 |
Yes | Yes | Yes | Yes |
'exec' |
3 |
3 |
Yes | Yes | Yes | Yes | |
'flags' |
1 |
3 |
No | No | No | Yes | |
'global' |
1 |
3 |
Yes | Yes | Yes | No | |
RegExp.prototype[@@matchAll] |
'flags' |
1 |
3 |
Yes | Yes | Yes | Yes |
'lastIndex' |
1 |
3 |
Yes | Yes | Yes | Yes | |
new SharedArrayBuffer |
'maxByteLength' |
1 |
2 |
Yes | Yes | Unsupported | Unsupported |
Set.prototype.difference |
'has','size' |
3 |
2 |
Yes | Yes | Yes | Yes |
Set.prototype.intersection |
'has','size' |
3 |
2 |
Yes | Yes | Yes | Yes |
Set.prototype.isDisjointFrom |
'has','size' |
3 |
2 |
Yes | Yes | Yes | Yes |
Set.prototype.symmetricDifference |
'has','size' |
3 |
2 |
Yes | Yes | Yes | Yes |
Set.prototype.union |
'has','size' |
3 |
2 |
Yes | Yes | Yes | Yes |
String.prototype.endsWith |
@@match |
1 |
2 |
Yes | Yes | Yes | Yes |
String.prototype.includes |
@@match |
1 |
2 |
Yes | Yes | Yes | Yes |
String.prototype.matchAll |
@@match,@@matchAll,'flags' |
3 |
2 |
Yes | Yes | Yes | Yes |
String.prototype.replaceAll |
@@match,@@replace,'flags' |
3 |
2 |
Yes | Yes | Yes | Yes |
String.prototype.startsWith |
@@match |
1 |
2 |
Yes | Yes | Yes | Yes |
String.raw |
'raw' |
1 |
3 |
Yes | Yes | Yes | Yes |
TypedArray |
<n> |
1 |
3 |
Yes | Yes | Yes | Yes |
with |
@@unscopables |
1 |
1 |
Yes | Unsupported | Not tested | Not tested |
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.
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. |
|
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. |
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. |
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"). |
Two approaches have been used to compile the list of usable and unusable runtime gadgets.
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];
},
});
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
.
-
Toolchain the find prototype pollution gadgets in websites. It cannot find the gadgets described in this repository because it cannot instrument the relevant code.
-
Toolchain to find prototype pollution in the Node.js template engines. It cannot find the gadgets described in this repository because it cannot statically analyze built-in functionality.
-
GHunter: Universal Prototype Pollution Gadgets in JavaScript Runtimes
Toolchain to find prototype pollution gadgets in the Node.js runtime. It cannot find the gadgets described in this repository because they're not covered by the binding code.
-
Silent Spring: Prototype Pollution Leads to Remote Code Execution in Node.js
Toolchain to find prototype pollution and gadgets in libraries and the Node.js runtime. It cannot find the gadgets described in this repository because it cannot statically analyze built-in functionality.