logo

Database

Insecure deserialization In axios

Description

Axios: Fetch Adapter Header Injection via Inherited FormData getHeaders

Summary

Axios contains a guard in the Node HTTP adapter to avoid using an inherited Object.prototype.getHeaders as a FormData header source. The fetch adapter calls the shared resolveConfig() helper before dispatch, and that helper lacks the same guard. If another vulnerability pollutes Object.prototype with FormData-like properties and getHeaders(), the fetch adapter can merge attacker-controlled headers into the outbound request.

Axios does not create the prototype pollution source. This is a read-side gadget in the fetch adapter configuration path.

Impact

An attacker with a prior same-process prototype-pollution primitive can inject headers into fetch-adapter requests. Depending on the target service, this may affect authorization, metadata-service access, cache behavior, conditional request handling, or other application-specific header logic.

Plain objects are blocked by current FormData detection. The confirmed path uses arrays or non-plain class instances whose prototype chain can resolve polluted FormData-like properties.

Affected Functionality

Affected:

    Fetch adapter requests.

    resolveConfig() handling of utils.isFormData(data).

    Request bodies that can be spoofed as FormData through inherited Symbol.toStringTag, append, and getHeaders.

Not affected:

    Node HTTP adapter's later FormData header path, which checks data.getHeaders !== Object.prototype.getHeaders.

    Plain object request bodies rejected by current isFormData() plain-object guard.

    Processes without prototype pollution.

Technical Details

lib/helpers/resolveConfig.js currently contains:

if (utils.isFormData(data)) {
  if (platform.hasStandardBrowserEnv || platform.hasStandardBrowserWebWorkerEnv || utils.isReactNative(data)) {
    headers.setContentType(undefined);
  } else if (utils.isFunction(data.getHeaders)) {
    setFormDataHeaders(headers, data.getHeaders(), own('formDataHeaderPolicy'));
  }
}

Unlike lib/adapters/http.js, this code does not reject Object.prototype.getHeaders. Local verification on axios 1.18.1 polluted Object.prototype[Symbol.toStringTag], append, and getHeaders, then sent an array body with adapter: 'fetch'. The loopback server received X-Poisoned: yes.

Proof of Concept of Attack

Constrained local demonstration:

Object.prototype[Symbol.toStringTag] = 'FormData';
Object.prototype.append = function () {};
Object.prototype.getHeaders = () => ({ 'X-Poisoned': 'yes' });

await axios.post(url, ['a', 'b'], { adapter: 'fetch' });

Expected safe behavior is that inherited Object.prototype.getHeaders is ignored. Current affected behavior merges the returned header.

Workarounds

Use the Node HTTP adapter for server-side requests that may run in a polluted process. Avoid passing array or class-instance bodies through the fetch adapter when prototype pollution is suspected.

Original report

Summary

The Node HTTP adapter contains a guard that prevents Object.prototype.getHeaders from being used as a FormData header source. The shared resolveConfig() helper does not have the same guard. The fetch adapter calls resolveConfig(), so it can still merge headers returned by inherited data.getHeaders().

This is a patch mismatch for the FormData prototype-pollution header-injection class.

Affected Version

Validated on:

    axios: 1.17.0

    commit: 4306df2

    runtime: Node.js v24.15.0

Preconditions

    Application uses adapter: 'fetch'.

    A separate prototype-pollution primitive can write:

      Object.prototype[Symbol.toStringTag] = 'FormData'

      Object.prototype.append = function () {}

      Object.prototype.getHeaders = function () { ... }

    The request body is an array or custom class instance. Plain objects are blocked by the current isFormData() plain-object guard.

Root Cause

lib/adapters/http.js contains:

data.getHeaders !== Object.prototype.getHeaders

But lib/helpers/resolveConfig.js only checks:

} else if (utils.isFunction(data.getHeaders)) {
  setFormDataHeaders(headers, data.getHeaders(), own('formDataHeaderPolicy'));
}

The fetch adapter calls resolveConfig(config) before dispatching the request.

Impact

An attacker can inject arbitrary headers into fetch-adapter requests. This may be used to influence internal APIs, metadata services, cache behavior, or application-specific authorization checks.

Proof of Concept

import axios from './index.js';
import http from 'http';

const start = (handler) => new Promise((resolve) => {
  const server = http.createServer((req, res) => {
    let body = '';
    req.on('data', (chunk) => (body += chunk));
    req.on('end', () => handler(req, res, body));...

Observed wire request:

{
  "headers": {
    "x-poisoned": "yes",
    "content-type": "text/plain;charset=UTF-8",
    "content-length": "3"
  },
  "body": "a,b"
}...

References


Mitigation

Update Impact

Minimal update. May introduce new vulnerabilities or breaking changes.

Ecosystem
Component
Affected version
Patched versions