Skip to content

Commit 698984a

Browse files
fix: do not convert special array like objects to Array
This commit fixes a security vulnerability.
1 parent c391cf3 commit 698984a

File tree

2 files changed

+58
-1
lines changed

2 files changed

+58
-1
lines changed

src/TransformOperationExecutor.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,15 @@ export class TransformOperationExecutor {
131131
} else if (typeof value === 'object' && value !== null) {
132132
// try to guess the type
133133
if (!targetType && value.constructor !== Object /* && TransformationType === TransformationType.CLASS_TO_PLAIN*/)
134-
targetType = value.constructor;
134+
if (!Array.isArray(value) && value.constructor === Array) {
135+
// Somebody attempts to convert special Array like object to Array, eg:
136+
// const evilObject = { '100000000': '100000000', __proto__: [] };
137+
// This could be used to cause Denial-of-service attack so we don't allow it.
138+
// See prevent-array-bomb.spec.ts for more details.
139+
} else {
140+
// We are good we can use the built-in constructor
141+
targetType = value.constructor;
142+
}
135143
if (!targetType && source) targetType = source.constructor;
136144

137145
if (this.options.enableCircularCheck) {
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import 'reflect-metadata';
2+
import { plainToInstance } from '../../src/index';
3+
import { defaultMetadataStorage } from '../../src/storage';
4+
5+
describe('Prevent array bomb when used with other packages', () => {
6+
it('should not convert specially crafted evil JS object to array', () => {
7+
defaultMetadataStorage.clear();
8+
9+
class TestClass {
10+
readonly categories!: string[];
11+
}
12+
13+
/**
14+
* We use the prototype of values to guess what is the type of the property. This behavior can be used
15+
* to pass a specially crafted array like object what would be transformed into an array.
16+
*
17+
* Because arrays are numerically indexed, specifying a big enough numerical property as key
18+
* would cause other libraries to iterate over each (undefined) element until the specified value is reached.
19+
* This can be used to cause denial-of-service attacks.
20+
*
21+
* An example of such scenario is the following:
22+
*
23+
* ```ts
24+
* class TestClass {
25+
* @IsArray()
26+
* @IsString({ each: true })
27+
* readonly categories!: string[];
28+
* }
29+
* ```
30+
*
31+
* Using the above class definition with class-validator and receiving the following specially crafted payload without
32+
* the correct protection in place:
33+
*
34+
* `{ '9007199254740990': '9007199254740990', __proto__: [] };`
35+
*
36+
* would result in the creation of an array with length of 9007199254740991 (MAX_SAFE_INTEGER) looking like this:
37+
*
38+
* `[ <9007199254740989 empty elements>, 9007199254740990 ]`
39+
*
40+
* Iterating over this array would take significant time and cause the server to become unresponsive.
41+
*/
42+
43+
const evilObject = { '100000000': '100000000', __proto__: [] };
44+
const result = plainToInstance(TestClass, { categories: evilObject });
45+
46+
expect(Array.isArray(result.categories)).toBe(false);
47+
expect(result.categories).toEqual({ '100000000': '100000000' });
48+
});
49+
});

0 commit comments

Comments
 (0)