Skip to content

Commit

Permalink
fix(security): fixed CVE-2023-45857 by backporting withXSRFToken op…
Browse files Browse the repository at this point in the history
…tion to v0.x (#6091)
  • Loading branch information
lnjbr committed Jan 19, 2024
1 parent 880b42e commit 2755df5
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 25 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Expand Up @@ -24,5 +24,5 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
cache: npm
- run: npm install
- run: npm ci
- run: npm test
3 changes: 3 additions & 0 deletions README.md
Expand Up @@ -399,6 +399,9 @@ These are the available config options for making requests. Only the `url` is re
// `xsrfHeaderName` is the name of the http header that carries the xsrf token value
xsrfHeaderName: 'X-XSRF-TOKEN', // default

// `undefined` (default) - set XSRF header only for the same origin requests
withXSRFToken: boolean | undefined | ((config: AxiosRequestConfig) => boolean | undefined),

// `onUploadProgress` allows handling of progress events for uploads
// browser only
onUploadProgress: function (progressEvent) {
Expand Down
1 change: 1 addition & 0 deletions index.d.ts
Expand Up @@ -165,6 +165,7 @@ export interface AxiosRequestConfig<D = any> {
FormData?: new (...args: any[]) => object;
};
formSerializer?: FormSerializerOptions;
withXSRFToken?: boolean | ((config: AxiosRequestConfig) => boolean | undefined);
}

export interface HeadersDefaults {
Expand Down
14 changes: 8 additions & 6 deletions lib/adapters/xhr.js
Expand Up @@ -18,6 +18,7 @@ module.exports = function xhrAdapter(config) {
var requestData = config.data;
var requestHeaders = config.headers;
var responseType = config.responseType;
var withXSRFToken = config.withXSRFToken;
var onCanceled;
function done() {
if (config.cancelToken) {
Expand Down Expand Up @@ -145,12 +146,13 @@ module.exports = function xhrAdapter(config) {
// Specifically not if we're in a web worker, or react-native.
if (utils.isStandardBrowserEnv()) {
// Add xsrf header
var xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath)) && config.xsrfCookieName ?
cookies.read(config.xsrfCookieName) :
undefined;

if (xsrfValue) {
requestHeaders[config.xsrfHeaderName] = xsrfValue;
withXSRFToken && utils.isFunction(withXSRFToken) && (withXSRFToken = withXSRFToken(config));
if (withXSRFToken || (withXSRFToken !== false && isURLSameOrigin(fullPath))) {
// Add xsrf header
var xsrfValue = config.xsrfHeaderName && config.xsrfCookieName && cookies.read(config.xsrfCookieName);
if (xsrfValue) {
requestHeaders[config.xsrfHeaderName] = xsrfValue;
}
}
}

Expand Down
1 change: 1 addition & 0 deletions lib/core/mergeConfig.js
Expand Up @@ -73,6 +73,7 @@ module.exports = function mergeConfig(config1, config2) {
'timeout': defaultToConfig2,
'timeoutMessage': defaultToConfig2,
'withCredentials': defaultToConfig2,
'withXSRFToken': defaultToConfig2,
'adapter': defaultToConfig2,
'responseType': defaultToConfig2,
'xsrfCookieName': defaultToConfig2,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -5,7 +5,7 @@
"main": "index.js",
"types": "index.d.ts",
"scripts": {
"test": "node bin/ssl_hotfix.js grunt test && node bin/ssl_hotfix.js dtslint",
"test": "node bin/ssl_hotfix.js grunt test && node bin/ssl_hotfix.js dtslint --localTs node_modules/typescript/lib",
"start": "node ./sandbox/server.js",
"preversion": "grunt version && npm test",
"build": "cross-env NODE_ENV=production grunt build",
Expand Down
98 changes: 81 additions & 17 deletions test/specs/xsrf.spec.js
@@ -1,82 +1,146 @@
'use strict';

var cookies = require('../../lib/helpers/cookies');

describe('xsrf', function () {
beforeEach(function () {
describe('xsrf', function() {
beforeEach(function() {
jasmine.Ajax.install();
});

afterEach(function () {
afterEach(function() {
document.cookie = axios.defaults.xsrfCookieName + '=;expires=' + new Date(Date.now() - 86400000).toGMTString();
jasmine.Ajax.uninstall();
});

it('should not set xsrf header if cookie is null', function (done) {
it('should not set xsrf header if cookie is null', function(done) {
axios('/foo');

getAjaxRequest().then(function (request) {
getAjaxRequest().then(function(request) {
expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toEqual(undefined);
done();
});
});

it('should set xsrf header if cookie is set', function (done) {
it('should set xsrf header if cookie is set', function(done) {
document.cookie = axios.defaults.xsrfCookieName + '=12345';

axios('/foo');

getAjaxRequest().then(function (request) {
getAjaxRequest().then(function(request) {
expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toEqual('12345');
done();
});
});

it('should not set xsrf header if xsrfCookieName is null', function (done) {
it('should not set xsrf header if xsrfCookieName is null', function(done) {
document.cookie = axios.defaults.xsrfCookieName + '=12345';

axios('/foo', {
xsrfCookieName: null
});

getAjaxRequest().then(function (request) {
getAjaxRequest().then(function(request) {
expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toEqual(undefined);
done();
});
});

it('should not read cookies at all if xsrfCookieName is null', function (done) {
spyOn(cookies, "read");
it('should not read cookies at all if xsrfCookieName is null', function(done) {
spyOn(cookies, 'read');

axios('/foo', {
xsrfCookieName: null
});

getAjaxRequest().then(function (request) {
getAjaxRequest().then(function() {
expect(cookies.read).not.toHaveBeenCalled();
done();
});
});

it('should not set xsrf header for cross origin', function (done) {
it('should not set xsrf header for cross origin', function(done) {
document.cookie = axios.defaults.xsrfCookieName + '=12345';

axios('http://example.com/');

getAjaxRequest().then(function (request) {
getAjaxRequest().then(function(request) {
expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toEqual(undefined);
done();
});
});

it('should set xsrf header for cross origin when using withCredentials', function (done) {
it('should set xsrf header for cross origin when using withCredentials and withXSRFToken', function(done) {
document.cookie = axios.defaults.xsrfCookieName + '=12345';

axios('http://example.com/', {
withCredentials: true
withCredentials: true,
withXSRFToken: true
});

getAjaxRequest().then(function (request) {
getAjaxRequest().then(function(request) {
expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toEqual('12345');
done();
});
});
describe('withXSRFToken option', function() {
it('should set xsrf header for cross origin when withXSRFToken = true', function(done) {
var token = '12345';

document.cookie = axios.defaults.xsrfCookieName + '=' + token;

axios('http://example.com/', {
withXSRFToken: true
});

getAjaxRequest().then(function(request) {
expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toEqual(token);
done();
});
});

it('should not set xsrf header for the same origin when withXSRFToken = false', function(done) {
var token = '12345';

document.cookie = axios.defaults.xsrfCookieName + '=' + token;

axios('/foo', {
withXSRFToken: false
});

getAjaxRequest().then(function(request) {
expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toEqual(undefined);
done();
});
});

it('should not set xsrf header for the same origin when withXSRFToken = false', function(done) {
var token = '12345';

document.cookie = axios.defaults.xsrfCookieName + '=' + token;

axios('/foo', {
withXSRFToken: false
});

getAjaxRequest().then(function(request) {
expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toEqual(undefined);
done();
});
});

it('should support function resolver', function(done) {
var token = '12345';

document.cookie = axios.defaults.xsrfCookieName + '=' + token;
axios('/foo', {
withXSRFToken: function(config) { return config.userFlag === 'yes';},
userFlag: 'yes'
});

getAjaxRequest().then(function(request) {
expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toEqual(token);
done();
});
});
});
});

0 comments on commit 2755df5

Please sign in to comment.