Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: csrf support check origin header with referer type #69

Merged
merged 5 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions app/extend/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,19 +203,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';
}
},

Expand Down
98 changes: 86 additions & 12 deletions test/csrf.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ describe('test/csrf.test.js', () => {
}
});

it('should return 200 with correct referer when type is referer', function* () {
it('should return 200 with correct referer or origin when type is referer', function* () {
mm(this.app.config, 'env', 'local');
mm(this.app.config.security.csrf, 'type', 'referer');
mm(this.app.config.security.csrf, 'refererWhiteList', [ '.nodejs.org' ]);
Expand All @@ -479,9 +479,14 @@ describe('test/csrf.test.js', () => {
.set('accept', 'text/html')
.set('referer', 'https://nodejs.org/en/')
.expect(200);
yield this.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', function* () {
it('should return 403 with correct referer or origin when type is referer', function* () {
mm(this.app.config, 'env', 'local');
mm(this.app.config.security.csrf, 'type', 'referer');
mm(this.app.config.security.csrf, 'refererWhiteList', [ 'nodejs.org' ]);
Expand All @@ -491,6 +496,12 @@ describe('test/csrf.test.js', () => {
.set('accept', 'text/html')
.set('referer', 'https://wwwnodejs.org/en/')
.expect(403);

yield this.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', function* () {
Expand All @@ -509,6 +520,19 @@ describe('test/csrf.test.js', () => {
.set('referer', 'https://nodejs.org/en/')
.set('host', 'nodejs.org')
.expect(200);

yield this.app.httpRequest()
.post('/update')
.set('accept', 'text/html')
.set('origin', 'https://www.nodejs.org/en/')
.set('host', 'nodejs.org')
.expect(200);
yield this.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', function* () {
Expand All @@ -521,9 +545,16 @@ describe('test/csrf.test.js', () => {
.set('referer', 'https://wwwnodejs.org/en/')
.set('host', 'nodejs.org')
.expect(403);

yield this.app.httpRequest()
.post('/update')
.set('accept', 'text/html')
.set('origin', 'https://wwwnodejs.org/en/')
.set('host', 'nodejs.org')
.expect(403);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix syntax error in test case.

The test case contains a syntax error using yield instead of await.

Apply this diff to fix the syntax error:

-    yield this.app.httpRequest()
+    await app.httpRequest()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
yield this.app.httpRequest()
.post('/update')
.set('accept', 'text/html')
.set('origin', 'https://wwwnodejs.org/en/')
.set('host', 'nodejs.org')
.expect(403);
await this.app.httpRequest()
.post('/update')
.set('accept', 'text/html')
.set('origin', 'https://wwwnodejs.org/en/')
.set('host', 'nodejs.org')
.expect(403);
🧰 Tools
🪛 Biome (1.9.4)

[error] 550-550: yield is only allowed within generator functions.

(parse)

});

it('should return 403 with evil referer when type is referer', function* () {
it('should return 403 with evil referer or origin when type is referer', function* () {
mm(this.app.config, 'env', 'local');
mm(this.app.config.security.csrf, 'type', 'referer');
mm(this.app.config.security.csrf, 'refererWhiteList', [ 'nodejs.org' ]);
Expand All @@ -533,9 +564,14 @@ describe('test/csrf.test.js', () => {
.set('accept', 'text/html')
.set('referer', 'https://nodejs.org!.evil.com/en/')
.expect(403);
yield this.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', function* () {
it('should return 403 with illegal referer or origin when type is referer', function* () {
mm(this.app.config, 'env', 'local');
mm(this.app.config.security.csrf, 'type', 'referer');
mm(this.app.config.security.csrf, 'refererWhiteList', [ 'nodejs.org' ]);
Expand All @@ -545,6 +581,11 @@ describe('test/csrf.test.js', () => {
.set('accept', 'text/html')
.set('referer', '/en/')
.expect(403);
yield this.app.httpRequest()
.post('/update')
.set('accept', 'text/html')
.set('origin', '/en/')
.expect(403);
});

it('should return 200 with same domain request', function* () {
Expand All @@ -557,6 +598,13 @@ describe('test/csrf.test.js', () => {
.set('accept', 'text/html')
.set('referer', `http://127.0.0.1:${port}/`)
.expect(200);

const httpRequestObj2 = this.app.httpRequest().post('/update');
const port2 = httpRequestObj2.app.address().port;
yield httpRequestObj2
.set('accept', 'text/html')
.set('origin', `http://127.0.0.1:${port2}/`)
.expect(200);
});

it('should return 403 with different domain request', function* () {
Expand All @@ -568,7 +616,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/);

yield this.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', function* () {
Expand All @@ -581,13 +636,19 @@ describe('test/csrf.test.js', () => {
.set('referer', 'https://eggjs.org/en/')
.expect(403)
.expect(/missing csrf token/);
yield this.app.httpRequest()
.post('/update')
.set('accept', 'text/html')
.set('origin', 'https://eggjs.org/en/')
.expect(403)
.expect(/missing csrf token/);
yield this.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', function* () {
Expand All @@ -599,6 +660,11 @@ describe('test/csrf.test.js', () => {
.set('accept', 'text/html')
.set('referer', 'https://eggjs.org/en/')
.expect(200);
yield this.app.httpRequest()
.post('/update')
.set('accept', 'text/html')
.set('origin', 'https://eggjs.org/en/')
.expect(200);
yield this.app.httpRequest()
.post('/update')
.send({ _csrf: '1' })
Expand All @@ -615,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', function* () {
it('should return 403 without referer and origin when type is referer', function* () {
mm(this.app.config, 'env', 'local');
mm(this.app.config.security.csrf, 'type', 'referer');
mm(this.app.config.security.csrf, 'refererWhiteList', [ 'https://eggjs.org/' ]);
Expand All @@ -624,11 +690,11 @@ describe('test/csrf.test.js', () => {
.post('/update')
.set('accept', 'text/html')
.expect(403)
.expect(/missing csrf referer/);
this.app.expectLog('missing csrf referer. See http');
.expect(/missing csrf referer or origin/);
this.app.expectLog('missing csrf referer or origin. See http');
});

it('should return 403 with invalid referer when type is referer', function* () {
it('should return 403 with invalid referer or origin when type is referer', function* () {
mm(this.app.config, 'env', 'local');
mm(this.app.config.security.csrf, 'type', 'referer');
mm(this.app.config.security.csrf, 'refererWhiteList', [ 'https://eggjs.org/' ]);
Expand All @@ -638,8 +704,16 @@ describe('test/csrf.test.js', () => {
.set('accept', 'text/html')
.set('referer', 'https://nodejs.org/en/')
.expect(403)
.expect(/invalid csrf referer/);
this.app.expectLog('invalid csrf referer. See http');
.expect(/invalid csrf referer or origin/);
this.app.expectLog('invalid csrf referer or origin. See http');

yield this.app.httpRequest()
.post('/update')
.set('accept', 'text/html')
.set('referer', 'https://nodejs.org/en/')
.expect(403)
.expect(/invalid csrf referer or origin/);
this.app.expectLog('invalid csrf referer or origin. See http');
});

it('should throw with error type', function* () {
Expand Down