GHSA-654m-c8p4-x5fp Axios has a Patch Bypass: Proxy-Authorization Header Injection via Prototype Pollution — Incomplete Null-Prototype Fix
[Patch Bypass] Proxy-Authorization Header Injection via Prototype Pollution — Incomplete Null-Prototype Fix in Axios 1.15.2SummaryThe `Object.create(null)` fix introduced in Axios 1.15.2 (GHSA-q8qp-cvcw-x6jj) protects the **top-level config object** from prototype pollution. However, **nested objects** created by `utils.merge()` (e.g., `config.proxy`) are still constructed as plain `{}` with `Object.prototype` in their chain.The `setProxy()` function at `lib/adapters/http.js:209-223` reads `proxy.username`, `proxy.password`, and `proxy.auth` **without `hasOwnProperty` checks**. When `Object.prototype.username` is polluted, `setProxy()` constructs a `Proxy-Authorization` header with attacker-controlled credentials and injects it into **every proxied HTTP request**.**Severity:** Medium (CVSS 5.4)
**Affected Versions:** 1.15.2 (and potentially 1.15.1)
**Vulnerable Component:** `lib/adapters/http.js` (`setProxy()`) + `lib/utils.js` (`merge()`)CWE**CWE-1321:** Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')**CWE-113:** Improper Neutralization of CRLF Sequences in HTTP Headers ('HTTP Response Splitting')CVSS 3.1**Score: 5.6 (Medium)**Vector: `CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L`| Metric | Value | Justification |
|---|---|---|
| Attack Vector | Network | PP triggered remotely via vulnerable dependency |
| Attack Complexity | **High** | Requires **two** preconditions: (1) PP in dependency tree, AND (2) the application must explicitly configure `config.proxy`. Unlike GHSA-q8qp-cvcw-x6jj which affected all requests unconditionally |
| Privileges Required | None | No authentication needed |
| User Interaction | None | No user interaction required |
| Scope | Unchanged | Within the proxy authentication context |
| Confidentiality | **Low** | Attacker-controlled identity appears in proxy authentication logs, but the attacker does NOT see request/response data (unlike `config.baseURL` hijack) |
| Integrity | **Low** | Proxy-Authorization header injected; proxy may apply different access policies based on injected identity |
| Availability | **Low** | If proxy rejects the injected credentials, legitimate requests may fail |Why This Is Lower Severity Than GHSA-q8qp-cvcw-x6jj (7.4 High)| Factor | GHSA-q8qp-cvcw-x6jj | This Finding |
|---|---|---|
| Precondition | **None** — all requests affected | Must have `config.proxy` set |
| `config.baseURL` PP | Hijacks **all** relative URL requests | Not applicable |
| `config.auth` PP | Injects `Authorization` to **target server** | Only injects `Proxy-Authorization` to **proxy** |
| Attacker sees traffic | Yes (via baseURL redirect) | **No** — only proxy identity affected |
| Impact scope | Universal — every axios request | Only requests with explicit proxy config |This Is a Patch BypassThis vulnerability **bypasses the fix** introduced in Axios 1.15.2 for GHSA-q8qp-cvcw-x6jj. The fix correctly uses `Object.create(null)` for the config object, blocking direct prototype pollution on `config.proxy`, `config.auth`, etc.However, the fix is **incomplete**: when a user legitimately sets `config.proxy = { host: 'proxy.corp', port: 8080 }`, the `mergeConfig()` function passes this object through `utils.merge()`, which creates a **new plain `{}` object** (`lib/utils.js:406: const result = {};`). This new object inherits from `Object.prototype`, re-opening the prototype pollution attack surface on the **nested** proxy object.| Layer | Protection | Status |
|---|---|---|
| `config` (top-level) | `Object.create(null)` | ✓ Fixed |
| `config.proxy` (nested) | `utils.merge()` → `const result = {}` | **✗ NOT Fixed** |
| `setProxy()` reads | `proxy.username`, `proxy.auth` without `hasOwnProperty` | **✗ NOT Fixed** |Root Cause AnalysisStep 1: `utils.merge()` creates plain `{}` for nested objects**File:** `lib/utils.js`, line 406When `mergeConfig()` processes `config.proxy`, `getMergedValue()` calls `utils.merge()`, which creates a plain `{}` for the nested object. This plain object inherits from `Object.prototype`.Step 2: `setProxy()` reads proxy properties without `hasOwnProperty`**File:** `lib/adapters/http.js`, lines 209-223Complete Attack ChainProof of ConceptReproduction EnvironmentReproduction StepsVerified PoC OutputConfirming the Bypass MechanismFix 2: Use null-prototype objects in `utils.merge()`Fix 3 (Comprehensive): Apply null-prototype to all objects created by `getMergedValue()`References[CWE-1321: Prototype Pollution](https://cwe.mitre.org/data/definitions/1321.html)[GHSA-q8qp-cvcw-x6jj: Original PP Gadgets Fix (Axios 1.15.2)](https://github.com/advisories/GHSA-q8qp-cvcw-x6jj)[GHSA-fvcv-3m26-pcqx: Related PP Gadget (Axios 1.15.0)](https://github.com/advisories/GHSA-fvcv-3m26-pcqx)[Axios GitHub Repository](https://github.com/axios/axios)
CVSS v3.1 score: 3.7