GHSA-42h9-826w-cgv3 Axios: Excessive recursion in formDataToJSON can cause denial of service
SummaryAxios versions `0.28.0` and later contain uncontrolled recursion in `formDataToJSON`, the helper behind the public `axios.formToJSON()` / named `formToJSON` API and the default request transform used when FormData is sent with an `application/json` content type.Applications are affected when they pass attacker-controlled `FormData` field names into this functionality. A field name with thousands of nested bracket segments can exhaust the JavaScript call stack and throw `RangeError: Maximum call stack size exceeded`, causing request failure and, in applications that do not handle the exception or rejected promise, possible process termination.ImpactThe impact is denial of service against applications that process untrusted `FormData` field names through axios' FormData-to-JSON conversion.The vulnerable path is not reached by merely installing axios, by normal multipart `FormData` pass-through, or by ordinary axios requests that do not request JSON serialisation of `FormData`. In the default axios request, the error is produced before network I/O and returned as a rejected Promise. Direct use of `formToJSON()` throws synchronously.Server-side applications are the primary risk when remote users can submit arbitrary form field names, and the application converts those fields with `formToJSON()` or sends them through axios as JSON.Affected FunctionalityAffected APIs and paths:`axios.formToJSON(formData)``import { formToJSON } from "axios"``lib/helpers/formDataToJSON.js`axios default `transformRequest` when `data` is `FormData` and `Content-Type` contains `application/json`Unaffected or lower-risk paths:Normal multipart `FormData` requests without `JSON Content-Type``toFormData()` object-to-FormData serialisation, which already has a `maxDepth` guardAxios versions before 0.28.0, where this helper and public API were not presentTechnical Details`lib/helpers/formDataToJSON.js` parses a form field name into path segments with `parsePropPath()`. For a key such as `a[x][x][x]`, each bracketed segment becomes another path element.`formDataToJSON()` then calls the nested `buildPath(path, value, target, index)` function. `buildPath()` recursively calls itself once for each path segment and does not enforce a maximum depth:`const result = buildPath(path, value, target[name], index);`A key containing thousands of bracket segments, therefore, creates thousands of recursive calls. At sufficient depth, V8 throws `RangeError: Maximum call stack size exceeded`.Axios already applies a depth guard to the inverse serializer in `lib/helpers/toFormData.js`, where `maxDepth` defaults to 100 and exceeding it throws `AxiosError` with code `ERR_FORM_DATA_DEPTH_EXCEEDED`. `formDataToJSON()` does not currently have equivalent protection.Proof of Concept of AttackExpected vulnerable result:RangeError: Maximum call stack size exceededThe axios request transform path can also be reached before network I/O:Expected vulnerable result:RangeError: Maximum call stack size exceededWorkaroundsApplications can avoid the vulnerable path by not converting attacker-controlled `FormData` to JSON with axios.If conversion is required before a fixed axios release is available, validate `FormData` field names before calling `formToJSON()` or before sending `FormData` with `Content-Type: application/json`. Reject keys whose parsed nesting depth exceeds the application's expected schema.For axios requests carrying untrusted `FormData`, avoid setting `Content-Type: application/json`; leaving the data as multipart FormData bypasses `formDataToJSON()`.Catching the resulting error can prevent process termination, but it does not remove the uncontrolled-recursion behaviour and should not be treated as the primary mitigation.1. Classification| CWE | CVSS Score | Severity | Type |
|-----|-----------|----------|------|
| CWE-918 | 7.5 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L) | HIGH | Server-Side Request Forgery |2. DescriptionSummaryThe `shouldBypassProxy()` function in Axios fails to recognise `0.0.0.0`, `::`, and `::ffff:0.0.0.0` as loopback addresses. When `NO_PROXY=localhost` is configured, requests to these addresses are incorrectly forwarded through the proxy instead of being sent directly, enabling an SSRF attack against internal services reachable via the proxy's loopback interface.Root Cause**File:** `lib/helpers/shouldBypassProxy.js`**`isIPv4Loopback` (lines 3-8):** Only checks for `127.x.x.x` addresses by inspecting `parts[0] !== '127'`. The `0.0.0.0` address has `parts[0] === '0'`, so it falls through as non-loopback, even though on Linux `0.0.0.0` routes to the loopback interface.**`isIPv6Loopback` (lines 10-38):** Only checks `host === '::1'`. The `::` address (unspecified IPv6) also routes to the loopback, but is not recognised.**Attack Flow:**Attack Vector**Access Vector:** Network (AV:N)**Access Complexity:** Low (AC:L) — attacker only needs control of a URL**Privileges Required:** None (PR:N)**User Interaction:** None (UI:N)3. Proof of ConceptPhase 1: Logic VerificationPhase 2: Docker E2E ReproductionA full 3-container Docker reproduction was created and tested:**Proxy container:** Simple HTTP forward proxy on port 8888**Internal container:** Internal service on port 9999 (simulates sensitive internal resource)**Attacker container:** Runs the test script with Axios source mounted**Reproduction steps:****Results:**Test 1: `127.0.0.1 + NO_PROXY=localhost` → BYPASS (correct)Test 2: `0.0.0.0 + NO_PROXY=localhost` → VIA_PROXY (SSRF)Test 3: `[::] + NO_PROXY=localhost` → VIA_PROXY (SSRF)Test 4: `[::ffff:0.0.0.0] + NO_PROXY=localhost` → VIA_PROXY (SSRF)Phase 3: Actual Axios ClientThe real Axios HTTP client (v1.16.1, source tree) was tested through proxy configuration:Axios with `proxy: { host: 'proxy', port: 8888 }`Setting `NO_PROXY=localhost` and requesting `http://0.0.0.0:9999/`Result: Axios forwarded the request through the proxy instead of bypassing it4. ImpactAttack ScenarioAttacker has control over a URL that an Axios client will request (direct input, redirect target, open redirect chain)The Axios client is configured with a proxy (e.g., corporate proxy) and `NO_PROXY=localhost` to protect internal servicesAttacker supplies `http://0.0.0.0:8080/admin` as the target URLAxios sends the request through the proxyThe proxy resolves `0.0.0.0` → the proxy's own loopback → reaches the internal admin service on port 8080Potential Consequences**Information disclosure (C:L):** Internal service responses become accessible**Integrity impact (I:L):** Attacker can trigger actions on internal services (if proxy supports PUT/POST/DELETE)**Availability impact (A:L):** Limited — depends on internal service behaviorLikelihood**High** — proxy bypass is a common pattern in microservice architectures**Medium** — requires attacker control of a URL (not always available)5. RemediationCode Fix**File:** `lib/helpers/shouldBypassProxy.js`WorkaroundsAdd `0.0.0.0` and `::` to the `NO_PROXY` environment variable explicitlyUse `127.0.0.1` instead of `0.0.0.0` in all internal service URLsImplement URL validation to reject `0.0.0.0` and `::` before passing to Axios
CVSS v4.0 score: 6.3