Skip to content

Commit 2c950d3

Browse files
anthinkingcoderzhoulinfengmk2
authored
feat: csrf support check origin header with referer type (#69)
csrf 防范可以通过检查 Origin 头来验证来源 [ Identifying Source Origin (via Origin/Referer header)](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html) <!-- - any feature? - close https://github.com/eggjs/egg/ISSUE_URL --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Security Improvements** - Enhanced CSRF protection by validating both `referer` and `origin` headers. - Improved error handling and validation messages for CSRF token checks. - **Testing** - Updated test case descriptions to reflect dual validation of `referer` and `origin`. - Added assertions for scenarios involving the `origin` header. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: zhoulin <[email protected]> Co-authored-by: fengmk2 <[email protected]>
1 parent f4500db commit 2c950d3

File tree

2 files changed

+87
-18
lines changed

2 files changed

+87
-18
lines changed

app/extend/context.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -210,19 +210,21 @@ module.exports = {
210210

211211
[CSRF_REFERER_CHECK]() {
212212
const { refererWhiteList } = this.app.config.security.csrf;
213-
const referer = (this.headers.referer || '').toLowerCase();
213+
// check Origin/Referer headers
214+
const referer = (this.headers.referer || this.headers.origin || '').toLowerCase();
215+
214216
if (!referer) {
215-
debug('missing csrf referer');
216-
this[LOG_CSRF_NOTICE]('missing csrf referer');
217-
return 'missing csrf referer';
217+
debug('missing csrf referer or origin');
218+
this[LOG_CSRF_NOTICE]('missing csrf referer or origin');
219+
return 'missing csrf referer or origin';
218220
}
219221

220222
const host = utils.getFromUrl(referer, 'host');
221223
const domainList = refererWhiteList.concat(this.host);
222224
if (!host || !utils.isSafeDomain(host, domainList)) {
223-
debug('verify referer error');
224-
this[LOG_CSRF_NOTICE]('invalid csrf referer');
225-
return 'invalid csrf referer';
225+
debug('verify referer or origin error');
226+
this[LOG_CSRF_NOTICE]('invalid csrf referer or origin');
227+
return 'invalid csrf referer or origin';
226228
}
227229
},
228230

test/csrf.test.js

Lines changed: 78 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,7 @@ describe('test/csrf.test.js', () => {
469469
}
470470
});
471471

472-
it('should return 200 with correct referer when type is referer', async () => {
472+
it('should return 200 with correct referer or origin when type is referer', async () => {
473473
mm(app.config, 'env', 'local');
474474
mm(app.config.security.csrf, 'type', 'referer');
475475
mm(app.config.security.csrf, 'refererWhiteList', [ '.nodejs.org' ]);
@@ -479,9 +479,15 @@ describe('test/csrf.test.js', () => {
479479
.set('accept', 'text/html')
480480
.set('referer', 'https://nodejs.org/en/')
481481
.expect(200);
482+
483+
await app.httpRequest()
484+
.post('/update')
485+
.set('accept', 'text/html')
486+
.set('origin', 'https://nodejs.org/en/')
487+
.expect(200);
482488
});
483489

484-
it('should return 403 with correct referer when type is referer', async () => {
490+
it('should return 403 with correct referer or origin when type is referer', async () => {
485491
mm(app.config, 'env', 'local');
486492
mm(app.config.security.csrf, 'type', 'referer');
487493
mm(app.config.security.csrf, 'refererWhiteList', [ 'nodejs.org' ]);
@@ -491,6 +497,12 @@ describe('test/csrf.test.js', () => {
491497
.set('accept', 'text/html')
492498
.set('referer', 'https://wwwnodejs.org/en/')
493499
.expect(403);
500+
501+
await app.httpRequest()
502+
.post('/update')
503+
.set('accept', 'text/html')
504+
.set('origin', 'https://wwwnodejs.org/en/')
505+
.expect(403);
494506
});
495507

496508
it('should return 200 with same root host when type is referer', async () => {
@@ -509,6 +521,19 @@ describe('test/csrf.test.js', () => {
509521
.set('referer', 'https://nodejs.org/en/')
510522
.set('host', 'nodejs.org')
511523
.expect(200);
524+
525+
await app.httpRequest()
526+
.post('/update')
527+
.set('accept', 'text/html')
528+
.set('origin', 'https://www.nodejs.org/en/')
529+
.set('host', 'nodejs.org')
530+
.expect(200);
531+
await app.httpRequest()
532+
.post('/update')
533+
.set('accept', 'text/html')
534+
.set('origin', 'https://nodejs.org/en/')
535+
.set('host', 'nodejs.org')
536+
.expect(200);
512537
});
513538

514539
it('should return 403 with invalid host when type is referer', async () => {
@@ -521,9 +546,16 @@ describe('test/csrf.test.js', () => {
521546
.set('referer', 'https://wwwnodejs.org/en/')
522547
.set('host', 'nodejs.org')
523548
.expect(403);
549+
550+
await app.httpRequest()
551+
.post('/update')
552+
.set('accept', 'text/html')
553+
.set('origin', 'https://wwwnodejs.org/en/')
554+
.set('host', 'nodejs.org')
555+
.expect(403);
524556
});
525557

526-
it('should return 403 with evil referer when type is referer', async () => {
558+
it('should return 403 with evil referer or origin when type is referer', async () => {
527559
mm(app.config, 'env', 'local');
528560
mm(app.config.security.csrf, 'type', 'referer');
529561
mm(app.config.security.csrf, 'refererWhiteList', [ 'nodejs.org' ]);
@@ -533,9 +565,14 @@ describe('test/csrf.test.js', () => {
533565
.set('accept', 'text/html')
534566
.set('referer', 'https://nodejs.org!.evil.com/en/')
535567
.expect(403);
568+
await app.httpRequest()
569+
.post('/update')
570+
.set('accept', 'text/html')
571+
.set('origin', 'https://nodejs.org!.evil.com/en/')
572+
.expect(403);
536573
});
537574

538-
it('should return 403 with illegal referer when type is referer', async () => {
575+
it('should return 403 with illegal referer or origin when type is referer', async () => {
539576
mm(app.config, 'env', 'local');
540577
mm(app.config.security.csrf, 'type', 'referer');
541578
mm(app.config.security.csrf, 'refererWhiteList', [ 'nodejs.org' ]);
@@ -545,6 +582,11 @@ describe('test/csrf.test.js', () => {
545582
.set('accept', 'text/html')
546583
.set('referer', '/en/')
547584
.expect(403);
585+
await app.httpRequest()
586+
.post('/update')
587+
.set('accept', 'text/html')
588+
.set('origin', '/en/')
589+
.expect(403);
548590
});
549591

550592
it('should return 200 with same domain request', async () => {
@@ -557,6 +599,13 @@ describe('test/csrf.test.js', () => {
557599
.set('accept', 'text/html')
558600
.set('referer', `http://127.0.0.1:${port}/`)
559601
.expect(200);
602+
603+
const httpRequestObj2 = app.httpRequest().post('/update');
604+
const port2 = httpRequestObj2.app.address().port;
605+
await httpRequestObj2
606+
.set('accept', 'text/html')
607+
.set('origin', `http://127.0.0.1:${port2}/`)
608+
.expect(200);
560609
});
561610

562611
it('should return 403 with different domain request', async () => {
@@ -568,7 +617,14 @@ describe('test/csrf.test.js', () => {
568617
.set('accept', 'text/html')
569618
.set('referer', 'https://nodejs.org/en/')
570619
.expect(403)
571-
.expect(/invalid csrf referer/);
620+
.expect(/invalid csrf referer or origin/);
621+
622+
await app.httpRequest()
623+
.post('/update')
624+
.set('accept', 'text/html')
625+
.set('origin', 'https://nodejs.org/en/')
626+
.expect(403)
627+
.expect(/invalid csrf referer or origin/);
572628
});
573629

574630
it('should check both ctoken and referer when type is all', async () => {
@@ -581,13 +637,19 @@ describe('test/csrf.test.js', () => {
581637
.set('referer', 'https://eggjs.org/en/')
582638
.expect(403)
583639
.expect(/missing csrf token/);
640+
await app.httpRequest()
641+
.post('/update')
642+
.set('accept', 'text/html')
643+
.set('origin', 'https://eggjs.org/en/')
644+
.expect(403)
645+
.expect(/missing csrf token/);
584646
await app.httpRequest()
585647
.post('/update')
586648
.send({ _csrf: '1' })
587649
.set('accept', 'text/html')
588650
.set('cookie', 'csrfToken=1')
589651
.expect(403)
590-
.expect(/missing csrf referer/);
652+
.expect(/missing csrf referer or origin/);
591653
});
592654

593655
it('should check one of ctoken and referer when type is any', async () => {
@@ -599,6 +661,11 @@ describe('test/csrf.test.js', () => {
599661
.set('accept', 'text/html')
600662
.set('referer', 'https://eggjs.org/en/')
601663
.expect(200);
664+
await app.httpRequest()
665+
.post('/update')
666+
.set('accept', 'text/html')
667+
.set('origin', 'https://eggjs.org/en/')
668+
.expect(200);
602669
await app.httpRequest()
603670
.post('/update')
604671
.send({ _csrf: '1' })
@@ -614,7 +681,7 @@ describe('test/csrf.test.js', () => {
614681
.expect(/ForbiddenError: both ctoken and referer check error: invalid csrf token, missing csrf referer/);
615682
});
616683

617-
it('should return 403 without referer when type is referer', async () => {
684+
it('should return 403 without referer or origin when type is referer', async () => {
618685
mm(app.config, 'env', 'local');
619686
mm(app.config.security.csrf, 'type', 'referer');
620687
mm(app.config.security.csrf, 'refererWhiteList', [ 'https://eggjs.org/' ]);
@@ -624,10 +691,10 @@ describe('test/csrf.test.js', () => {
624691
.set('accept', 'text/html')
625692
.expect(403)
626693
.expect(/missing csrf referer/);
627-
app.expectLog('missing csrf referer. See http');
694+
app.expectLog('missing csrf referer or origin. See http');
628695
});
629696

630-
it('should return 403 with invalid referer when type is referer', async () => {
697+
it('should return 403 with invalid referer or origin when type is referer', async () => {
631698
mm(app.config, 'env', 'local');
632699
mm(app.config.security.csrf, 'type', 'referer');
633700
mm(app.config.security.csrf, 'refererWhiteList', [ 'https://eggjs.org/' ]);
@@ -637,8 +704,8 @@ describe('test/csrf.test.js', () => {
637704
.set('accept', 'text/html')
638705
.set('referer', 'https://nodejs.org/en/')
639706
.expect(403)
640-
.expect(/invalid csrf referer/);
641-
app.expectLog('invalid csrf referer. See http');
707+
.expect(/invalid csrf referer or origin/);
708+
app.expectLog('invalid csrf referer or origin. See http');
642709
});
643710

644711
it('should throw with error type', async () => {

0 commit comments

Comments
 (0)