Compare commits

...

3 Commits

Author SHA1 Message Date
Dan Abramov
54adb2674a 16.4.2 2018-08-01 19:26:37 +01:00
Dan Abramov
d922ed2cf2 Fix SSR crash on a hasOwnProperty attribute 2018-08-01 19:17:58 +01:00
Dan Abramov
5b19684fc3 Sanitize unknown attribute names for SSR 2018-08-01 19:17:50 +01:00
14 changed files with 220 additions and 69 deletions

View File

@@ -1,6 +1,6 @@
{
"private": true,
"version": "16.4.1",
"version": "16.4.2",
"workspaces": [
"packages/*"
],

View File

@@ -1,7 +1,7 @@
{
"name": "create-subscription",
"description": "utility for subscribing to external data sources inside React components",
"version": "16.4.1",
"version": "16.4.2",
"repository": "facebook/react",
"files": [
"LICENSE",

View File

@@ -1,7 +1,7 @@
{
"name": "react-art",
"description": "React ART is a JavaScript library for drawing vector graphics using React. It provides declarative and reactive bindings to the ART library. Using the same declarative API you can render the output to either Canvas, SVG or VML (IE8).",
"version": "16.4.1",
"version": "16.4.2",
"main": "index.js",
"repository": "facebook/react",
"keywords": [

View File

@@ -1,6 +1,6 @@
{
"name": "react-dom",
"version": "16.4.1",
"version": "16.4.2",
"description": "React package for working with the DOM.",
"main": "index.js",
"repository": "facebook/react",

View File

@@ -554,39 +554,174 @@ describe('ReactDOMComponent', () => {
expect(stubStyle.color).toEqual('green');
});
it('should reject attribute key injection attack on markup', () => {
it('should reject attribute key injection attack on markup for regular DOM (SSR)', () => {
expect(() => {
for (let i = 0; i < 3; i++) {
const container = document.createElement('div');
const element = React.createElement(
const element1 = React.createElement(
'div',
{'blah" onclick="beevil" noise="hi': 'selected'},
null,
);
const element2 = React.createElement(
'div',
{'></div><script>alert("hi")</script>': 'selected'},
null,
);
let result1 = ReactDOMServer.renderToString(element1);
let result2 = ReactDOMServer.renderToString(element2);
expect(result1.toLowerCase()).not.toContain('onclick');
expect(result2.toLowerCase()).not.toContain('script');
}
}).toWarnDev([
'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`',
'Warning: Invalid attribute name: `></div><script>alert("hi")</script>`',
]);
});
it('should reject attribute key injection attack on markup for custom elements (SSR)', () => {
expect(() => {
for (let i = 0; i < 3; i++) {
const element1 = React.createElement(
'x-foo-component',
{'blah" onclick="beevil" noise="hi': 'selected'},
null,
);
ReactDOM.render(element, container);
const element2 = React.createElement(
'x-foo-component',
{'></x-foo-component><script>alert("hi")</script>': 'selected'},
null,
);
let result1 = ReactDOMServer.renderToString(element1);
let result2 = ReactDOMServer.renderToString(element2);
expect(result1.toLowerCase()).not.toContain('onclick');
expect(result2.toLowerCase()).not.toContain('script');
}
}).toWarnDev(
}).toWarnDev([
'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`',
);
'Warning: Invalid attribute name: `></x-foo-component><script>alert("hi")</script>`',
]);
});
it('should reject attribute key injection attack on update', () => {
it('should reject attribute key injection attack on mount for regular DOM', () => {
expect(() => {
for (let i = 0; i < 3; i++) {
const container = document.createElement('div');
ReactDOM.render(
React.createElement(
'div',
{'blah" onclick="beevil" noise="hi': 'selected'},
null,
),
container,
);
expect(container.firstChild.attributes.length).toBe(0);
ReactDOM.unmountComponentAtNode(container);
ReactDOM.render(
React.createElement(
'div',
{'></div><script>alert("hi")</script>': 'selected'},
null,
),
container,
);
expect(container.firstChild.attributes.length).toBe(0);
}
}).toWarnDev([
'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`',
'Warning: Invalid attribute name: `></div><script>alert("hi")</script>`',
]);
});
it('should reject attribute key injection attack on mount for custom elements', () => {
expect(() => {
for (let i = 0; i < 3; i++) {
const container = document.createElement('div');
ReactDOM.render(
React.createElement(
'x-foo-component',
{'blah" onclick="beevil" noise="hi': 'selected'},
null,
),
container,
);
expect(container.firstChild.attributes.length).toBe(0);
ReactDOM.unmountComponentAtNode(container);
ReactDOM.render(
React.createElement(
'x-foo-component',
{'></x-foo-component><script>alert("hi")</script>': 'selected'},
null,
),
container,
);
expect(container.firstChild.attributes.length).toBe(0);
}
}).toWarnDev([
'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`',
'Warning: Invalid attribute name: `></x-foo-component><script>alert("hi")</script>`',
]);
});
it('should reject attribute key injection attack on update for regular DOM', () => {
expect(() => {
for (let i = 0; i < 3; i++) {
const container = document.createElement('div');
const beforeUpdate = React.createElement('div', {}, null);
ReactDOM.render(beforeUpdate, container);
ReactDOM.render(
React.createElement(
'div',
{'blah" onclick="beevil" noise="hi': 'selected'},
null,
),
container,
);
expect(container.firstChild.attributes.length).toBe(0);
ReactDOM.render(
React.createElement(
'div',
{'></div><script>alert("hi")</script>': 'selected'},
null,
),
container,
);
expect(container.firstChild.attributes.length).toBe(0);
}
}).toWarnDev([
'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`',
'Warning: Invalid attribute name: `></div><script>alert("hi")</script>`',
]);
});
it('should reject attribute key injection attack on update for custom elements', () => {
expect(() => {
for (let i = 0; i < 3; i++) {
const container = document.createElement('div');
const beforeUpdate = React.createElement('x-foo-component', {}, null);
ReactDOM.render(beforeUpdate, container);
const afterUpdate = React.createElement(
'x-foo-component',
{'blah" onclick="beevil" noise="hi': 'selected'},
null,
ReactDOM.render(
React.createElement(
'x-foo-component',
{'blah" onclick="beevil" noise="hi': 'selected'},
null,
),
container,
);
ReactDOM.render(afterUpdate, container);
expect(container.firstChild.attributes.length).toBe(0);
ReactDOM.render(
React.createElement(
'x-foo-component',
{'></x-foo-component><script>alert("hi")</script>': 'selected'},
null,
),
container,
);
expect(container.firstChild.attributes.length).toBe(0);
}
}).toWarnDev(
}).toWarnDev([
'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`',
);
'Warning: Invalid attribute name: `></x-foo-component><script>alert("hi")</script>`',
]);
});
it('should update arbitrary attributes for tags containing dashes', () => {

View File

@@ -174,6 +174,19 @@ describe('ReactDOMServer', () => {
(__DEV__ ? '\n in iframe (at **)' : ''),
);
});
it('should not crash on poisoned hasOwnProperty', () => {
let html;
expect(
() =>
(html = ReactDOMServer.renderToString(
<div hasOwnProperty="poison">
<span unknown="test" />
</div>,
)),
).toWarnDev(['React does not recognize the `hasOwnProperty` prop']);
expect(html).toContain('<span unknown="test">');
});
});
describe('renderToStaticMarkup', () => {

View File

@@ -60,9 +60,10 @@ export function createMarkupForProperty(name: string, value: mixed): string {
} else {
return attributeName + '=' + quoteAttributeValueForBrowser(value);
}
} else {
} else if (isAttributeNameSafe(name)) {
return name + '=' + quoteAttributeValueForBrowser(value);
}
return '';
}
/**

View File

@@ -308,6 +308,7 @@ function processContext(type, context) {
return maskedContext;
}
const hasOwnProperty = Object.prototype.hasOwnProperty;
const STYLE = 'style';
const RESERVED_PROPS = {
children: null,
@@ -327,7 +328,7 @@ function createOpenTagMarkup(
let ret = '<' + tagVerbatim;
for (const propKey in props) {
if (!props.hasOwnProperty(propKey)) {
if (!hasOwnProperty.call(props, propKey)) {
continue;
}
let propValue = props[propKey];

View File

@@ -66,14 +66,15 @@ export const VALID_ATTRIBUTE_NAME_REGEX = new RegExp(
'^[' + ATTRIBUTE_NAME_START_CHAR + '][' + ATTRIBUTE_NAME_CHAR + ']*$',
);
const hasOwnProperty = Object.prototype.hasOwnProperty;
const illegalAttributeNameCache = {};
const validatedAttributeNameCache = {};
export function isAttributeNameSafe(attributeName: string): boolean {
if (validatedAttributeNameCache.hasOwnProperty(attributeName)) {
if (hasOwnProperty.call(validatedAttributeNameCache, attributeName)) {
return true;
}
if (illegalAttributeNameCache.hasOwnProperty(attributeName)) {
if (hasOwnProperty.call(illegalAttributeNameCache, attributeName)) {
return false;
}
if (VALID_ATTRIBUTE_NAME_REGEX.test(attributeName)) {

View File

@@ -1,6 +1,6 @@
{
"name": "react-is",
"version": "16.4.1",
"version": "16.4.2",
"description": "Brand checking of React Elements.",
"main": "index.js",
"repository": "facebook/react",

View File

@@ -1,6 +1,6 @@
{
"name": "react-test-renderer",
"version": "16.4.1",
"version": "16.4.2",
"description": "React package for snapshot testing.",
"main": "index.js",
"repository": "facebook/react",
@@ -18,7 +18,7 @@
"fbjs": "^0.8.16",
"object-assign": "^4.1.1",
"prop-types": "^15.6.0",
"react-is": "^16.4.1"
"react-is": "^16.4.2"
},
"peerDependencies": {
"react": "^16.0.0"

View File

@@ -4,7 +4,7 @@
"keywords": [
"react"
],
"version": "16.4.1",
"version": "16.4.2",
"homepage": "https://reactjs.org/",
"bugs": "https://github.com/facebook/react/issues",
"license": "MIT",

View File

@@ -8,4 +8,4 @@
'use strict';
// TODO: this is special because it gets imported during build.
module.exports = '16.4.1';
module.exports = '16.4.2';

View File

@@ -46,29 +46,29 @@
"filename": "react-dom.development.js",
"bundleType": "UMD_DEV",
"packageName": "react-dom",
"size": 641505,
"gzip": 149286
"size": 641575,
"gzip": 149303
},
{
"filename": "react-dom.production.min.js",
"bundleType": "UMD_PROD",
"packageName": "react-dom",
"size": 96507,
"gzip": 31258
"size": 96528,
"gzip": 31268
},
{
"filename": "react-dom.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-dom",
"size": 625496,
"gzip": 145193
"size": 625566,
"gzip": 145209
},
{
"filename": "react-dom.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-dom",
"size": 94969,
"gzip": 30225
"size": 94990,
"gzip": 30219
},
{
"filename": "ReactDOM-dev.js",
@@ -110,7 +110,7 @@
"bundleType": "NODE_PROD",
"packageName": "react-dom",
"size": 9913,
"gzip": 3696
"gzip": 3697
},
{
"filename": "ReactTestUtils-dev.js",
@@ -124,7 +124,7 @@
"bundleType": "UMD_DEV",
"packageName": "react-dom",
"size": 62921,
"gzip": 16559
"gzip": 16560
},
{
"filename": "react-dom-unstable-native-dependencies.production.min.js",
@@ -165,29 +165,29 @@
"filename": "react-dom-server.browser.development.js",
"bundleType": "UMD_DEV",
"packageName": "react-dom",
"size": 105006,
"gzip": 27524
"size": 105186,
"gzip": 27547
},
{
"filename": "react-dom-server.browser.production.min.js",
"bundleType": "UMD_PROD",
"packageName": "react-dom",
"size": 15463,
"gzip": 5917
"size": 15521,
"gzip": 5938
},
{
"filename": "react-dom-server.browser.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-dom",
"size": 94052,
"gzip": 25217
"size": 94232,
"gzip": 25238
},
{
"filename": "react-dom-server.browser.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-dom",
"size": 14810,
"gzip": 5665
"size": 14866,
"gzip": 5690
},
{
"filename": "ReactDOMServer-dev.js",
@@ -207,22 +207,22 @@
"filename": "react-dom-server.node.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-dom",
"size": 96020,
"gzip": 25767
"size": 96200,
"gzip": 25790
},
{
"filename": "react-dom-server.node.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-dom",
"size": 15634,
"gzip": 5964
"size": 15690,
"gzip": 5986
},
{
"filename": "react-art.development.js",
"bundleType": "UMD_DEV",
"packageName": "react-art",
"size": 417844,
"gzip": 93214
"gzip": 93213
},
{
"filename": "react-art.production.min.js",
@@ -369,7 +369,7 @@
"bundleType": "NODE_PROD",
"packageName": "react-noop-renderer",
"size": 6829,
"gzip": 2631
"gzip": 2632
},
{
"filename": "react-reconciler.development.js",
@@ -404,7 +404,7 @@
"bundleType": "NODE_DEV",
"packageName": "react-reconciler",
"size": 11847,
"gzip": 3642
"gzip": 3643
},
{
"filename": "react-reconciler-reflection.production.min.js",
@@ -446,7 +446,7 @@
"bundleType": "NODE_DEV",
"packageName": "react-is",
"size": 4596,
"gzip": 1264
"gzip": 1265
},
{
"filename": "react-is.production.min.js",
@@ -509,21 +509,21 @@
"bundleType": "FB_WWW_PROD",
"packageName": "react",
"size": 13708,
"gzip": 3835
"gzip": 3836
},
{
"filename": "ReactDOM-dev.js",
"bundleType": "FB_WWW_DEV",
"packageName": "react-dom",
"size": 634902,
"gzip": 144512
"size": 634972,
"gzip": 144538
},
{
"filename": "ReactDOM-prod.js",
"bundleType": "FB_WWW_PROD",
"packageName": "react-dom",
"size": 275897,
"gzip": 51771
"size": 275965,
"gzip": 51777
},
{
"filename": "ReactTestUtils-dev.js",
@@ -550,15 +550,15 @@
"filename": "ReactDOMServer-dev.js",
"bundleType": "FB_WWW_DEV",
"packageName": "react-dom",
"size": 97859,
"gzip": 25094
"size": 98039,
"gzip": 25126
},
{
"filename": "ReactDOMServer-prod.js",
"bundleType": "FB_WWW_PROD",
"packageName": "react-dom",
"size": 32295,
"gzip": 7922
"size": 32512,
"gzip": 7964
},
{
"filename": "ReactART-dev.js",
@@ -579,14 +579,14 @@
"bundleType": "RN_FB_DEV",
"packageName": "react-native-renderer",
"size": 468596,
"gzip": 102411
"gzip": 102412
},
{
"filename": "ReactNativeRenderer-prod.js",
"bundleType": "RN_FB_PROD",
"packageName": "react-native-renderer",
"size": 210858,
"gzip": 36787
"gzip": 36788
},
{
"filename": "ReactNativeRenderer-dev.js",
@@ -614,7 +614,7 @@
"bundleType": "RN_FB_PROD",
"packageName": "react-native-renderer",
"size": 190842,
"gzip": 33372
"gzip": 33376
},
{
"filename": "ReactFabric-dev.js",
@@ -712,14 +712,14 @@
"bundleType": "NODE_PROD",
"packageName": "react-noop-renderer",
"size": 6851,
"gzip": 2637
"gzip": 2638
},
{
"filename": "react-dom.profiling.min.js",
"bundleType": "NODE_PROFILING",
"packageName": "react-dom",
"size": 95896,
"gzip": 30574
"size": 95917,
"gzip": 30579
},
{
"filename": "ReactNativeRenderer-profiling.js",