本文作者:IMWeb went 原文出处:IMWeb社区 未经同意,禁止转载
原文链接:Why Do React Elements Have a $$typeof Property?
你可能以为你在编写的是JSX:
代码语言:javascript复制<marquee bgcolor="#ffa7c4">hi</marquee>
但是实际上,你调用的是一个函数。
代码语言:javascript复制React.createElement(
/* type */ 'marquee',
/* props */ { bgcolor: '#ffa7c4' },
/* children */ 'hi'
)
这个函数给你返回一个Object对象,我们叫这个对象为React元素。它告诉React接下来要渲染什么。你编写的组件将组成一个组件树。
代码语言:javascript复制{
type: 'marquee',
props: {
bgcolor: '#ffa7c4',
children: 'hi',
},
key: null,
ref: null,
typeof: Symbol.for('react.element'),
}
如果你经常使用React,你可能会对type、props、key、ref属性很熟悉,但是,什么是typeof属性?为什么他的属性是一个Symbol值?
这是另一件你使用React时不需要知道的事情,但是当你知道了你会收获良多。这些也是一些你可能想知道的安全相关的贴士。或许有一天你将会编写你的UI库,到了那时候这些知识将会得到实践。我非常希望看到这件事情发生。
在客户端侧UI库变得常用和增加了基础的保护前,应用代码常用它来构建HTML和把生成的HTML插入DOM节点。
代码语言:javascript复制const messageEl = document.getElementById('message');
messageEl = '<p>' message.text '</p>';
这将会工作得很好,除非当你的 message.text 是一些诸如 < img src onerror="stealYourPassword()" > 这样的代码。我猜你不会想陌生人写的东西一字不差地出现在你的应用渲染的HTML上。
(有趣的事实:如果你只是做单纯的客户端侧渲染,一个 < script> 标签在这里将不会让你执行JavaScript。但是不要让它麻痹了你,让你陷入虚假的安全感当中。)
为了防范这些攻击,你可以使用安全的一些API,比如 document.createTextNode() 或者 textContent ,这些只会处理你的文本。你当然也可以先发制人地通过替换潜在的危险字符比如< , >和其他用户提供的文本来转义用户的输入。
仍然,这个犯错成本还是很高昂的,它也存在一个争论就是它需要你每时每刻都记住你要处理一个用户输入的字符串在你的输出中。这就是为什么现代的库比如React会默认地转义字符串的文本内容。
代码语言:javascript复制<p>
{message.text}
</p>
如果message.text是带有< img >或其他标记的恶意字符串,则它不会变成真正的< img >标记。React将转义内容,然后将其插入DOM节点中。所以,你将不会看到< img >标签,而只是看到它的标记。
要在React元素中渲染任意HTML,你必须编写 dangerouslySe = {{ __ html:message.text }}。事实上这种笨拙的写法是一个特性。
这是否意味着React完全免受注入攻击?不是的,HTML和DOM提供了大量的攻击面,对于React或其他UI库而言,这些攻击面太难或者会很慢以致于不能缓解。大部分剩下的攻击方向都包括了属性。举个栗子,如果你渲染 < a href={user.website} > 这个元素,要注意的是用户的网址是' stealYourPassword() '。像 < div {...userData} > 这样传递用户的输入很少见,但是同样危险。
React可以一直为用户提供更多保护,但在许多场景下,这些都是服务器问题导致的结果,无论如何它们应该在服务器层面那里修复。
仍然,转义文本内容是一个合理的第一道防线,可以捕获大量潜在的攻击。知道像这样的代码是安全的,这不是很好吗?
代码语言:javascript复制// Escaped automatically
<p>
{message.text}
</p>
很好,但这也不是总是正确的。这就是 $typeof 的用武之地了。
从设计上来说,React 元素是一个普通的对象。
代码语言:javascript复制{
type: 'marquee',
props: {
bgcolor: '#ffa7c4',
children: 'hi',
},
key: null,
ref: null,
$ $typeof: Symbol.for('react.element'),
}
虽然你们通常使用React.createElement()创建React元素,但是这不是必需的方法。 React有一些有效的例子来支持像我刚刚在上面做的那样编写的普通元素对象。当然,你可能不想这样编写它们 ---- 但这对于优化编译器,在worker之间传递UI元素或者将JSX与React包解耦是有用的。
但是,如果你的服务器侧有一个允许用户存储任意JSON对象的漏洞,而客户端代码期待一个字符串,这可能会成为一个问题:
代码语言:javascript复制// Server could have a hole that lets user store JSON
let expectedTextButGotJSON = {
type: 'div',
props: {
dangerouslySe: {
__html: '/* put your exploit here */'
},
},
// ...
};
let message = { text: expectedTextButGotJSON };
// Dangerous in React 0.13
<p>
{message.text}
</p>
在这种情况下,React 0.13很容易受到XSS攻击。再次澄清,这种攻击取决于现有的服务器漏洞。尽管如此,React可以更好地保护人们免受它侵害。从React 0.14开始,它做到了。
在React 0.14版本,它的修复方法是对每一个React元素使用Symbol来进行标记。
因为你不能把Symbol放在JSON中,所以它是有效的。因此,即使服务器具有安全漏洞并返回JSON而不是文本,该JSON也不能包含Symbol.for('react.element')。 React将检查元素的$typeof属性,如果$typeof属性丢失或无效,将拒绝处理该元素。
特别是使用 Symbol.for() 的好处是,Symbols在iframe和worker等环境之间是全局的。因此,即使在更特殊的条件下,此修复也不会阻止在应用程序的不同部分之间传递可信元素。相同的,即使页面上有多个React副本,它们仍然可以“同意”有效的$ typeof值。
那些不支持Symbols特性的浏览器呢?
唉,他们将不会受到这种额外的保护。 React仍然在元素上包含$typeof字段以保持一致性,但它将被设置为一个数字 ---- 0xeac7。
为什么会是这个数字?因为0xeac7看起来有点像“React”。。。