Sanitize unknown attribute names for SSR (#13302)
Dan Abramov committed
Aug 1, 2018 at 20:23 UTC
ff41519ec2fea37643ecae7f87f8e15856247faf
2 files changed
+152
-16
packages/react-dom/src/__tests__/ReactDOMComponent-test.js
+150
-15
@@ -554,39 +554,174 @@ describe('ReactDOMComponent', () => {
554
expect(stubStyle.color).toEqual('green');
555
});
556
557
- it('should reject attribute key injection attack on markup', () => {
557
+ it('should reject attribute key injection attack on markup for regular DOM (SSR)', () => {
558
expect(() => {
559
for (let i = 0; i < 3; i++) {
560
- const container = document.createElement('div');
561
- const element = React.createElement(
560
+ const element1 = React.createElement(
561
+ 'div',
562
+ {'blah" onclick="beevil" noise="hi': 'selected'},
563
+ null,
564
+ );
565
+ const element2 = React.createElement(
566
+ 'div',
567
+ {'></div><script>alert("hi")</script>': 'selected'},
568
+ null,
569
+ );
570
+ let result1 = ReactDOMServer.renderToString(element1);
571
+ let result2 = ReactDOMServer.renderToString(element2);
572
+ expect(result1.toLowerCase()).not.toContain('onclick');
573
+ expect(result2.toLowerCase()).not.toContain('script');
574
+ }
575
+ }).toWarnDev([
576
+ 'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`',
577
+ 'Warning: Invalid attribute name: `></div><script>alert("hi")</script>`',
578
+ ]);
579
+ });
580
+
581
+ it('should reject attribute key injection attack on markup for custom elements (SSR)', () => {
582
+ expect(() => {
583
+ for (let i = 0; i < 3; i++) {
584
+ const element1 = React.createElement(
585
'x-foo-component',
586
{'blah" onclick="beevil" noise="hi': 'selected'},
587
null,
588
);
566
- ReactDOM.render(element, container);
589
+ const element2 = React.createElement(
590
+ 'x-foo-component',
591
+ {'></x-foo-component><script>alert("hi")</script>': 'selected'},
592
+ null,
593
+ );
594
+ let result1 = ReactDOMServer.renderToString(element1);
595
+ let result2 = ReactDOMServer.renderToString(element2);
596
+ expect(result1.toLowerCase()).not.toContain('onclick');
597
+ expect(result2.toLowerCase()).not.toContain('script');
598
}
568
- }).toWarnDev(
599
+ }).toWarnDev([
600
'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`',
570
- );
601
+ 'Warning: Invalid attribute name: `></x-foo-component><script>alert("hi")</script>`',
602
+ ]);
603
});
604
573
- it('should reject attribute key injection attack on update', () => {
605
+ it('should reject attribute key injection attack on mount for regular DOM', () => {
606
expect(() => {
607
for (let i = 0; i < 3; i++) {
608
const container = document.createElement('div');
577
- const beforeUpdate = React.createElement('x-foo-component', {}, null);
609
+ ReactDOM.render(
610
+ React.createElement(
611
+ 'div',
612
+ {'blah" onclick="beevil" noise="hi': 'selected'},
613
+ null,
614
+ ),
615
+ container,
616
+ );
617
+ expect(container.firstChild.attributes.length).toBe(0);
618
+ ReactDOM.unmountComponentAtNode(container);
619
+ ReactDOM.render(
620
+ React.createElement(
621
+ 'div',
622
+ {'></div><script>alert("hi")</script>': 'selected'},
623
+ null,
624
+ ),
625
+ container,
626
+ );
627
+ expect(container.firstChild.attributes.length).toBe(0);
628
+ }
629
+ }).toWarnDev([
630
+ 'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`',
631
+ 'Warning: Invalid attribute name: `></div><script>alert("hi")</script>`',
632
+ ]);
633
+ });
634
+
635
+ it('should reject attribute key injection attack on mount for custom elements', () => {
636
+ expect(() => {
637
+ for (let i = 0; i < 3; i++) {
638
+ const container = document.createElement('div');
639
+ ReactDOM.render(
640
+ React.createElement(
641
+ 'x-foo-component',
642
+ {'blah" onclick="beevil" noise="hi': 'selected'},
643
+ null,
644
+ ),
645
+ container,
646
+ );
647
+ expect(container.firstChild.attributes.length).toBe(0);
648
+ ReactDOM.unmountComponentAtNode(container);
649
+ ReactDOM.render(
650
+ React.createElement(
651
+ 'x-foo-component',
652
+ {'></x-foo-component><script>alert("hi")</script>': 'selected'},
653
+ null,
654
+ ),
655
+ container,
656
+ );
657
+ expect(container.firstChild.attributes.length).toBe(0);
658
+ }
659
+ }).toWarnDev([
660
+ 'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`',
661
+ 'Warning: Invalid attribute name: `></x-foo-component><script>alert("hi")</script>`',
662
+ ]);
663
+ });
664
+
665
+ it('should reject attribute key injection attack on update for regular DOM', () => {
666
+ expect(() => {
667
+ for (let i = 0; i < 3; i++) {
668
+ const container = document.createElement('div');
669
+ const beforeUpdate = React.createElement('div', {}, null);
670
ReactDOM.render(beforeUpdate, container);
671
+ ReactDOM.render(
672
+ React.createElement(
673
+ 'div',
674
+ {'blah" onclick="beevil" noise="hi': 'selected'},
675
+ null,
676
+ ),
677
+ container,
678
+ );
679
+ expect(container.firstChild.attributes.length).toBe(0);
680
+ ReactDOM.render(
681
+ React.createElement(
682
+ 'div',
683
+ {'></div><script>alert("hi")</script>': 'selected'},
684
+ null,
685
+ ),
686
+ container,
687
+ );
688
+ expect(container.firstChild.attributes.length).toBe(0);
689
+ }
690
+ }).toWarnDev([
691
+ 'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`',
692
+ 'Warning: Invalid attribute name: `></div><script>alert("hi")</script>`',
693
+ ]);
694
+ });
695
580
- const afterUpdate = React.createElement(
581
- 'x-foo-component',
582
- {'blah" onclick="beevil" noise="hi': 'selected'},
583
- null,
696
+ it('should reject attribute key injection attack on update for custom elements', () => {
697
+ expect(() => {
698
+ for (let i = 0; i < 3; i++) {
699
+ const container = document.createElement('div');
700
+ const beforeUpdate = React.createElement('x-foo-component', {}, null);
701
+ ReactDOM.render(beforeUpdate, container);
702
+ ReactDOM.render(
703
+ React.createElement(
704
+ 'x-foo-component',
705
+ {'blah" onclick="beevil" noise="hi': 'selected'},
706
+ null,
707
+ ),
708
+ container,
709
+ );
710
+ expect(container.firstChild.attributes.length).toBe(0);
711
+ ReactDOM.render(
712
+ React.createElement(
713
+ 'x-foo-component',
714
+ {'></x-foo-component><script>alert("hi")</script>': 'selected'},
715
+ null,
716
+ ),
717
+ container,
718
);
585
- ReactDOM.render(afterUpdate, container);
719
+ expect(container.firstChild.attributes.length).toBe(0);
720
}
587
- }).toWarnDev(
721
+ }).toWarnDev([
722
'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`',
589
- );
723
+ 'Warning: Invalid attribute name: `></x-foo-component><script>alert("hi")</script>`',
724
+ ]);
725
});
726
727
it('should update arbitrary attributes for tags containing dashes', () => {
packages/react-dom/src/server/DOMMarkupOperations.js
+2
-1
@@ -60,9 +60,10 @@ export function createMarkupForProperty(name: string, value: mixed): string {
60
} else {
61
return attributeName + '=' + quoteAttributeValueForBrowser(value);
62
}
63
- } else {
63
+ } else if (isAttributeNameSafe(name)) {
64
return name + '=' + quoteAttributeValueForBrowser(value);
65
}
66
+ return '';
67
}
68
69
/**