2
0
mirror of https://github.com/tenrok/axios.git synced 2026-06-17 19:21:29 +03:00

fix: header security issues (#10749)

* fix: improve regex in AxiosURLSearchParams

* fix: header should check has own property

* chore: implement cubic feedback in test case
This commit is contained in:
Jay
2026-04-18 14:56:39 +02:00
committed by GitHub
parent 0a73b7c585
commit 37cf18f2e2
3 changed files with 61 additions and 11 deletions
+2 -1
View File
@@ -520,7 +520,8 @@ export default isHttpAdapterSupported &&
}
);
// support for https://www.npmjs.com/package/form-data api
} else if (utils.isFormData(data) && utils.isFunction(data.getHeaders)) {
} else if (utils.isFormData(data) && utils.isFunction(data.getHeaders) &&
data.getHeaders !== Object.prototype.getHeaders) {
headers.set(data.getHeaders());
if (!headers.hasContentLength()) {
+10 -10
View File
@@ -257,16 +257,16 @@ const G = getGlobal();
const FormDataCtor = typeof G.FormData !== 'undefined' ? G.FormData : undefined;
const isFormData = (thing) => {
let kind;
return thing && (
(FormDataCtor && thing instanceof FormDataCtor) || (
isFunction(thing.append) && (
(kind = kindOf(thing)) === 'formdata' ||
// detect form-data instance
(kind === 'object' && isFunction(thing.toString) && thing.toString() === '[object FormData]')
)
)
);
if (!thing) return false;
if (FormDataCtor && thing instanceof FormDataCtor) return true;
// Reject plain objects inheriting directly from Object.prototype so prototype-pollution gadgets can't spoof FormData (GHSA-6chq-wfr3-2hj9).
const proto = getPrototypeOf(thing);
if (!proto || proto === Object.prototype) return false;
if (!isFunction(thing.append)) return false;
const kind = kindOf(thing);
return kind === 'formdata' ||
// detect form-data instance
(kind === 'object' && isFunction(thing.toString) && thing.toString() === '[object FormData]');
};
/**
+49
View File
@@ -2512,6 +2512,55 @@ describe('supports http with nodejs', () => {
}
});
});
describe('prototype pollution (GHSA-6chq-wfr3-2hj9)', () => {
const pollutedKeys = ['getHeaders', 'append', 'pipe', 'on', 'once'];
const toStringTagSym = Symbol.toStringTag;
function pollute() {
Object.prototype[toStringTagSym] = 'FormData';
Object.prototype.append = () => {};
Object.prototype.getHeaders = () => ({
'x-injected': 'attacker',
'authorization': 'Bearer ATTACKER_TOKEN',
});
Object.prototype.pipe = function (d) { if (d && d.end) d.end(); return d; };
Object.prototype.on = function () { return this; };
Object.prototype.once = function () { return this; };
}
function cleanup() {
for (const k of pollutedKeys) delete Object.prototype[k];
delete Object.prototype[toStringTagSym];
}
it('should not merge prototype-polluted getHeaders into outgoing request', async () => {
let receivedHeaders;
const server = await startHTTPServer(
(req, res) => {
receivedHeaders = req.headers;
res.end('{}');
},
{ port: SERVER_PORT }
);
try {
pollute();
await axios.post(
`http://localhost:${server.address().port}/`,
{ userId: 42 },
{ headers: { 'Authorization': 'Bearer VALID_USER_TOKEN' } }
);
} finally {
cleanup();
await stopHTTPServer(server);
}
assert.ok(receivedHeaders, 'request did not reach server');
assert.strictEqual(receivedHeaders['x-injected'], undefined);
assert.notStrictEqual(receivedHeaders['authorization'], 'Bearer ATTACKER_TOKEN');
});
});
});
describe('toFormData helper', () => {