Skip to content

Commit 5b19684

Browse files
committed
Sanitize unknown attribute names for SSR
1 parent 9725065 commit 5b19684

File tree

2 files changed

+152
-16
lines changed

2 files changed

+152
-16
lines changed

packages/react-dom/src/__tests__/ReactDOMComponent-test.js

+150-15
Original file line numberDiff line numberDiff line change
@@ -554,39 +554,174 @@ describe('ReactDOMComponent', () => {
554554
expect(stubStyle.color).toEqual('green');
555555
});
556556

557-
it('should reject attribute key injection attack on markup', () => {
557+
it('should reject attribute key injection attack on markup for regular DOM (SSR)', () => {
558558
expect(() => {
559559
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(
562585
'x-foo-component',
563586
{'blah" onclick="beevil" noise="hi': 'selected'},
564587
null,
565588
);
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');
567598
}
568-
}).toWarnDev(
599+
}).toWarnDev([
569600
'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`',
570-
);
601+
'Warning: Invalid attribute name: `></x-foo-component><script>alert("hi")</script>`',
602+
]);
571603
});
572604

573-
it('should reject attribute key injection attack on update', () => {
605+
it('should reject attribute key injection attack on mount for regular DOM', () => {
574606
expect(() => {
575607
for (let i = 0; i < 3; i++) {
576608
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);
578670
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+
});
579695

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,
584718
);
585-
ReactDOM.render(afterUpdate, container);
719+
expect(container.firstChild.attributes.length).toBe(0);
586720
}
587-
}).toWarnDev(
721+
}).toWarnDev([
588722
'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`',
589-
);
723+
'Warning: Invalid attribute name: `></x-foo-component><script>alert("hi")</script>`',
724+
]);
590725
});
591726

592727
it('should update arbitrary attributes for tags containing dashes', () => {

packages/react-dom/src/server/DOMMarkupOperations.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,10 @@ export function createMarkupForProperty(name: string, value: mixed): string {
6060
} else {
6161
return attributeName + '=' + quoteAttributeValueForBrowser(value);
6262
}
63-
} else {
63+
} else if (isAttributeNameSafe(name)) {
6464
return name + '=' + quoteAttributeValueForBrowser(value);
6565
}
66+
return '';
6667
}
6768

6869
/**

0 commit comments

Comments
 (0)