diff --git a/app/extend/context.js b/app/extend/context.js index 6086c44..7822c39 100644 --- a/app/extend/context.js +++ b/app/extend/context.js @@ -210,19 +210,21 @@ module.exports = { [CSRF_REFERER_CHECK]() { const { refererWhiteList } = this.app.config.security.csrf; - const referer = (this.headers.referer || '').toLowerCase(); + // check Origin/Referer headers + const referer = (this.headers.referer || this.headers.origin || '').toLowerCase(); + if (!referer) { - debug('missing csrf referer'); - this[LOG_CSRF_NOTICE]('missing csrf referer'); - return 'missing csrf referer'; + debug('missing csrf referer or origin'); + this[LOG_CSRF_NOTICE]('missing csrf referer or origin'); + return 'missing csrf referer or origin'; } const host = utils.getFromUrl(referer, 'host'); const domainList = refererWhiteList.concat(this.host); if (!host || !utils.isSafeDomain(host, domainList)) { - debug('verify referer error'); - this[LOG_CSRF_NOTICE]('invalid csrf referer'); - return 'invalid csrf referer'; + debug('verify referer or origin error'); + this[LOG_CSRF_NOTICE]('invalid csrf referer or origin'); + return 'invalid csrf referer or origin'; } }, diff --git a/test/csrf.test.js b/test/csrf.test.js index 9c3a678..912fed9 100644 --- a/test/csrf.test.js +++ b/test/csrf.test.js @@ -469,7 +469,7 @@ describe('test/csrf.test.js', () => { } }); - it('should return 200 with correct referer when type is referer', async () => { + it('should return 200 with correct referer or origin when type is referer', async () => { mm(app.config, 'env', 'local'); mm(app.config.security.csrf, 'type', 'referer'); mm(app.config.security.csrf, 'refererWhiteList', [ '.nodejs.org' ]); @@ -479,9 +479,15 @@ describe('test/csrf.test.js', () => { .set('accept', 'text/html') .set('referer', 'https://nodejs.org/en/') .expect(200); + + await app.httpRequest() + .post('/update') + .set('accept', 'text/html') + .set('origin', 'https://nodejs.org/en/') + .expect(200); }); - it('should return 403 with correct referer when type is referer', async () => { + it('should return 403 with correct referer or origin when type is referer', async () => { mm(app.config, 'env', 'local'); mm(app.config.security.csrf, 'type', 'referer'); mm(app.config.security.csrf, 'refererWhiteList', [ 'nodejs.org' ]); @@ -491,6 +497,12 @@ describe('test/csrf.test.js', () => { .set('accept', 'text/html') .set('referer', 'https://wwwnodejs.org/en/') .expect(403); + + await app.httpRequest() + .post('/update') + .set('accept', 'text/html') + .set('origin', 'https://wwwnodejs.org/en/') + .expect(403); }); it('should return 200 with same root host when type is referer', async () => { @@ -509,6 +521,19 @@ describe('test/csrf.test.js', () => { .set('referer', 'https://nodejs.org/en/') .set('host', 'nodejs.org') .expect(200); + + await app.httpRequest() + .post('/update') + .set('accept', 'text/html') + .set('origin', 'https://www.nodejs.org/en/') + .set('host', 'nodejs.org') + .expect(200); + await app.httpRequest() + .post('/update') + .set('accept', 'text/html') + .set('origin', 'https://nodejs.org/en/') + .set('host', 'nodejs.org') + .expect(200); }); it('should return 403 with invalid host when type is referer', async () => { @@ -521,9 +546,16 @@ describe('test/csrf.test.js', () => { .set('referer', 'https://wwwnodejs.org/en/') .set('host', 'nodejs.org') .expect(403); + + await app.httpRequest() + .post('/update') + .set('accept', 'text/html') + .set('origin', 'https://wwwnodejs.org/en/') + .set('host', 'nodejs.org') + .expect(403); }); - it('should return 403 with evil referer when type is referer', async () => { + it('should return 403 with evil referer or origin when type is referer', async () => { mm(app.config, 'env', 'local'); mm(app.config.security.csrf, 'type', 'referer'); mm(app.config.security.csrf, 'refererWhiteList', [ 'nodejs.org' ]); @@ -533,9 +565,14 @@ describe('test/csrf.test.js', () => { .set('accept', 'text/html') .set('referer', 'https://nodejs.org!.evil.com/en/') .expect(403); + await app.httpRequest() + .post('/update') + .set('accept', 'text/html') + .set('origin', 'https://nodejs.org!.evil.com/en/') + .expect(403); }); - it('should return 403 with illegal referer when type is referer', async () => { + it('should return 403 with illegal referer or origin when type is referer', async () => { mm(app.config, 'env', 'local'); mm(app.config.security.csrf, 'type', 'referer'); mm(app.config.security.csrf, 'refererWhiteList', [ 'nodejs.org' ]); @@ -545,6 +582,11 @@ describe('test/csrf.test.js', () => { .set('accept', 'text/html') .set('referer', '/en/') .expect(403); + await app.httpRequest() + .post('/update') + .set('accept', 'text/html') + .set('origin', '/en/') + .expect(403); }); it('should return 200 with same domain request', async () => { @@ -557,6 +599,13 @@ describe('test/csrf.test.js', () => { .set('accept', 'text/html') .set('referer', `http://127.0.0.1:${port}/`) .expect(200); + + const httpRequestObj2 = app.httpRequest().post('/update'); + const port2 = httpRequestObj2.app.address().port; + await httpRequestObj2 + .set('accept', 'text/html') + .set('origin', `http://127.0.0.1:${port2}/`) + .expect(200); }); it('should return 403 with different domain request', async () => { @@ -568,7 +617,14 @@ describe('test/csrf.test.js', () => { .set('accept', 'text/html') .set('referer', 'https://nodejs.org/en/') .expect(403) - .expect(/invalid csrf referer/); + .expect(/invalid csrf referer or origin/); + + await app.httpRequest() + .post('/update') + .set('accept', 'text/html') + .set('origin', 'https://nodejs.org/en/') + .expect(403) + .expect(/invalid csrf referer or origin/); }); it('should check both ctoken and referer when type is all', async () => { @@ -581,13 +637,19 @@ describe('test/csrf.test.js', () => { .set('referer', 'https://eggjs.org/en/') .expect(403) .expect(/missing csrf token/); + await app.httpRequest() + .post('/update') + .set('accept', 'text/html') + .set('origin', 'https://eggjs.org/en/') + .expect(403) + .expect(/missing csrf token/); await app.httpRequest() .post('/update') .send({ _csrf: '1' }) .set('accept', 'text/html') .set('cookie', 'csrfToken=1') .expect(403) - .expect(/missing csrf referer/); + .expect(/missing csrf referer or origin/); }); it('should check one of ctoken and referer when type is any', async () => { @@ -599,6 +661,11 @@ describe('test/csrf.test.js', () => { .set('accept', 'text/html') .set('referer', 'https://eggjs.org/en/') .expect(200); + await app.httpRequest() + .post('/update') + .set('accept', 'text/html') + .set('origin', 'https://eggjs.org/en/') + .expect(200); await app.httpRequest() .post('/update') .send({ _csrf: '1' }) @@ -614,7 +681,7 @@ describe('test/csrf.test.js', () => { .expect(/ForbiddenError: both ctoken and referer check error: invalid csrf token, missing csrf referer/); }); - it('should return 403 without referer when type is referer', async () => { + it('should return 403 without referer or origin when type is referer', async () => { mm(app.config, 'env', 'local'); mm(app.config.security.csrf, 'type', 'referer'); mm(app.config.security.csrf, 'refererWhiteList', [ 'https://eggjs.org/' ]); @@ -624,10 +691,10 @@ describe('test/csrf.test.js', () => { .set('accept', 'text/html') .expect(403) .expect(/missing csrf referer/); - app.expectLog('missing csrf referer. See http'); + app.expectLog('missing csrf referer or origin. See http'); }); - it('should return 403 with invalid referer when type is referer', async () => { + it('should return 403 with invalid referer or origin when type is referer', async () => { mm(app.config, 'env', 'local'); mm(app.config.security.csrf, 'type', 'referer'); mm(app.config.security.csrf, 'refererWhiteList', [ 'https://eggjs.org/' ]); @@ -637,8 +704,8 @@ describe('test/csrf.test.js', () => { .set('accept', 'text/html') .set('referer', 'https://nodejs.org/en/') .expect(403) - .expect(/invalid csrf referer/); - app.expectLog('invalid csrf referer. See http'); + .expect(/invalid csrf referer or origin/); + app.expectLog('invalid csrf referer or origin. See http'); }); it('should throw with error type', async () => {