@samitouri / QOS-React / commits / ff41519ec2

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 /**