Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use a Symbol to tag every ReactElement #4832

Merged
merged 1 commit into from
Sep 10, 2015

Conversation

sebmarkbage
Copy link
Collaborator

Fixes #3473

I tag each React element with $$typeof: Symbol.for('react.element'). We need
this to be able to safely distinguish these from plain objects that might have
come from user provided JSON.

The idiomatic JavaScript way of tagging an object is for it to inherent some
prototype and then use instanceof to test for it.

However, this has limitations since it doesn't work with value types which
require typeof checks. They also don't work across realms. Which is why there
are alternative tag checks like Array.isArray or the toStringTag. Another
problem is that different instances of React that might have been created not knowing about eachother. npm tends to make this kind of problem occur a lot.

Additionally, it is our hope that ReactElement will one day be specified in
terms of a "Value Type" style record instead of a plain Object.

This Value Types proposal by @nikomatsakis is currently on hold but does satisfy all these requirements:

https://github.com/nikomatsakis/typed-objects-explainer/blob/master/valuetypes.md#the-typeof-operator

Additionally, there is already a system for coordinating tags across module
systems and even realms in ES6. Namely using Symbol.for. (thx @sebmck)

Currently these objects are not able to transfer between Workers but there is
nothing preventing that from being possible in the future. You could imagine
even Symbol.for working across Worker boundaries. You could also build a
system that coordinates Symbols and Value Types from server to client or through
serialized forms. That's beyond the scope of React itself, and if it was built
it seems like it would belong with the Symbol system. A system could override
the Symbol.for('react.element') to return a plain yet
cryptographically random or unique number. That would allow ReactElements to
pass through JSON without risking the XSS issue.

The fallback solution is a plain well-known number. This makes it unsafe with
regard to the XSS issue described in #3473. We could have used a much more
convoluted solution to protect against JSON specifically but that would require
some kind of significant coordination, or change the check to do a
typeof element.$$typeof === 'function' check which would not make it unique to
React. It seems cleaner to just use a fixed number since the protection is just
a secondary layer anyway. I'm not sure if this is the right tradeoff.

In short, if you want the XSS protection, use a proper Symbol polyfill.

Finally, the reason for calling it $$typeof is to avoid confusion with .type
and the use case is to add a tag that the typeof operator would refer to.
I would use @@typeof but that seems to deopt in JSC. I also don't use
__typeof because this is more than a framework private. It should really be
part of the polyfilling layer.

Test Plan:

examples/basic/index.html works in Chrome and Firefox which both have native Symbols - and in Safari which doesn't.

React.createElement('div').$$typeof // Symbol(react.element).

} else {
this._store.validated = false;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed this bug in older browsers.

this._store.validated = false;
element._store.validated = false;
element._self = self;
element._source = source;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used a single underscore here for consistency with _owner and _store while the properties in config use the double underscore.

Fixes facebook#3473

I tag each React element with `$$typeof: Symbol.for('react.element')`. We need
this to be able to safely distinguish these from plain objects that might have
come from user provided JSON.

The idiomatic JavaScript way of tagging an object is for it to inherent some
prototype and then use `instanceof` to test for it.

However, this has limitations since it doesn't work with value types which
require `typeof` checks. They also don't work across realms. Which is why there
are alternative tag checks like `Array.isArray` or the `toStringTag`. Another
problem is that different instances of React that might have been created not knowing about eachother. npm tends to make this kind of problem occur a lot.

Additionally, it is our hope that ReactElement will one day be specified in
terms of a "Value Type" style record instead of a plain Object.

This Value Types proposal by @nikomatsakis is currently on hold but does satisfy all these requirements:

https://github.com/nikomatsakis/typed-objects-explainer/blob/master/valuetypes.md#the-typeof-operator

Additionally, there is already a system for coordinating tags across module
systems and even realms in ES6. Namely using `Symbol.for`.

Currently these objects are not able to transfer between Workers but there is
nothing preventing that from being possible in the future. You could imagine
even `Symbol.for` working across Worker boundaries. You could also build a
system that coordinates Symbols and Value Types from server to client or through
serialized forms. That's beyond the scope of React itself, and if it was built
it seems like it would belong with the `Symbol` system. A system could override
the `Symbol.for('react.element')` to return a plain yet
cryptographically random or unique number. That would allow ReactElements to
pass through JSON without risking the XSS issue.

The fallback solution is a plain well-known number. This makes it unsafe with
regard to the XSS issue described in facebook#3473. We could have used a much more
convoluted solution to protect against JSON specifically but that would require
some kind of significant coordination, or change the check to do a
`typeof element.$$typeof === 'function'` check which would not make it unique to
React. It seems cleaner to just use a fixed number since the protection is just
a secondary layer anyway. I'm not sure if this is the right tradeoff.

In short, if you want the XSS protection, use a proper Symbol polyfill.

Finally, the reason for calling it `$$typeof` is to avoid confusion with `.type`
and the use case is to add a tag that the `typeof` operator would refer to.
I would use `@@typeof` but that seems to deopt in JSC. I also don't use
`__typeof` because this is more than a framework private. It should really be
part of the polyfilling layer.
sebmarkbage added a commit that referenced this pull request Sep 10, 2015
Use a Symbol to tag every ReactElement
@sebmarkbage sebmarkbage merged commit 7a00239 into facebook:master Sep 10, 2015
@glenjamin
Copy link
Contributor

This is a great solution to the XSS issue! Just need to make sure no-one adds a way to run symbol.for via JSON parsing :)

sophiebits added a commit to sophiebits/babel that referenced this pull request Sep 10, 2015
sophiebits added a commit to sophiebits/babel that referenced this pull request Sep 10, 2015
@trueadm
Copy link
Contributor

trueadm commented Sep 10, 2015

works in Chrome and Firefox which both have native Symbols - and in Safari which doesn't.

I do believe this is very soon to change with the latest Safari for iOS 9 and El Capitan (16th September and 30th September according to yesterday's Apple event). :)

// The Symbol used to tag the ReactElement type. If there is no native Symbol
// nor polyfill, then a plain number is used for performance.
var TYPE_SYMBOL = (typeof Symbol === 'function' && Symbol.for &&
Symbol.for('react.element')) || 0xeac7;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0xeac7 0x0cc5!

(edited for hexiness, thanks @RReverser for the tip)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gaearon o is not valid hex char :P

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

someone please tell me why 0xeac7? I can't sleep

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0xeac7 sorta kinda looks like React

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not use Math.random()

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't cover postMessage though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want postMessage to work, right? In my testing, it actually does, since structured cloning supports much more advanced serialization than JSON.stringify().

This is confirmed in the w3c tests: https://github.com/w3c/web-platform-tests/blob/master/workers/interfaces/DedicatedWorkerGlobalScope/postMessage/structured-clone-message.html#L32

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't want it to work cross domain by default. That's still a security risk.

However, we do want it to work if you're able to coordinate the Symbol across the worker boundaries.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would argue that if you're deliberately catching cross-origin postMessage and inserting objects directly into your views without validation, you're asking for it.

This would be nice for webworkers though, as Symbols don't transfer via structured cloning (so you'd have to explicitly catch elements and re-tag them).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #3473 for more context.

It is too easy to expect a string and not realize it might be an object:

loadString(function(stringData) {
  React.render(<div>Content: {stringData}</div>, container);
});

minimal added a commit to minimal/devcards that referenced this pull request Oct 16, 2015
Adds minimal support for react 0.14 without affecting 0.13 users.

React 0.14 removes React.initializeTouchEvents as it's no longer
needed.

_isReactElement was removed and replaced with the $$typeof field which
contains an ES6 symbol if supported or a number.

Warnings are visible for 0.14 which can be stopped by using ReactDOM for
render and findDOMNode etc.

https://facebook.github.io/react/blog/2015/10/07/react-v0.14.html#breaking-changes
facebook/react#4832
minimal added a commit to minimal/devcards that referenced this pull request Oct 17, 2015
Adds minimal support for react 0.14 without affecting 0.13 users.

React 0.14 removes React.initializeTouchEvents as it's no longer
needed.

_isReactElement was removed and replaced with the $$typeof field which
contains an ES6 symbol if supported or a number.

Warnings are visible for 0.14 which can be stopped by using ReactDOM for
render and findDOMNode etc.

https://facebook.github.io/react/blog/2015/10/07/react-v0.14.html#breaking-changes
facebook/react#4832
STRML added a commit to STRML/react that referenced this pull request Jan 12, 2016
This closes the XSS hole on olders browsers that don't support Symbol.

More discussion: facebook#4832 (comment)
STRML added a commit to STRML/react that referenced this pull request Jan 12, 2016
This closes the XSS hole on older browsers that don't support Symbol.

More discussion: facebook#4832 (comment)
@gaearon gaearon mentioned this pull request Jun 26, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

How Much XSS Vulnerability Protection is React Responsible For?