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:
@@ -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
@@ -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]');
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
Reference in New Issue
Block a user