【为什么react元素有一个$$typeof属性】

举个例子:例如我们的jsx长这个样子:

<Button type="primary">点击</Button>

但是实际上,在经过babel后,它会变成下面这段代码:


React.createElement(
  /* type */ 'Button',
  /* props */ { type: 'primary' },
  /* children */ '点击'

之后,这个函数执行结果会返回一个对象,这个对象我们称为React Element。它是一个用来描述我们将要渲染的页面结构的一个不可变对象。想了解更多与React Component,ElementsInastances的可以点击这里

// React Element
{
  type: 'Button',
  props: {
    type: 'primary',
    children: '点击',
  },
  key: null,
  ref: null,
  $$typeof: Symbol.for('react.element'), // 为什么有这个东西
}

对于React开发者来说,上面这些属性大部分都是比较常见的。可是为什么混进了一个奇怪的$$typeof??它是干嘛的呢?它的值为什么是一个Symbol呢?

这个属性的引入,其实要从一个安全漏洞说起。

假如我们要显示一个变量,如果你使用纯js来写的话,可能是这样:

const messageEl = document.getElementById('message');
messageEl.innerHTML = `<div>${message}</div>`;

这一段代码,对于熟悉或者了解过XSS攻击的人来说,一看就知道会有问题,存在着XSS攻击。如果message是用户可以控制的变量(比如说是用户输入的评论)的话,那么用户就可以进行攻击了。比如用户可以构造下面的代码来进行攻击:

message = '<img onerror="alert(2)" src="" />';

这样页面一加载到这段代码,就会弹出一个alert框。

如果我们明确知道,我们只想单纯的渲染文本,不想把它当成html来渲染的话,那么我们可以通过textContent来避免这个问题。

const messageEl = document.getElementById('message');
messageEl.textContent = `<div>${message}</div>`;

而对于React而言的话,想要实现相同的效果,只需要:

<div>{message}</div>

即使message里面含有imgscript类似的标签,它们最终也不会以实际上的标签显示。React会对渲染的内容进行转译,比如说上面的攻击代码会被转译为:

message = '<img onerror="alert(2)" src=""/>';
// 转译为
message = '&lt;img onerror="alert(2)" src=""/&gt;'

因此,这样就可以避免大部分场景下的XSS攻击了。

当然,React也提供了另一种方式来将用户输入的内容当成html来渲染:

<div dangerouslySetInnerHTML={{ __html: message }}></div>

前面说了这么多,那么跟$$typeof又有什么关系呢?别急,重点来了。

对于下面这种写法,我们一般都知道,message可以传基本类型、自定义组件和jsx片段。

<div>{message}</div>

可是,其实我们还可以直接传React Element。比如,我们可以直接这样写

class App extends React.Component {
  render() {
    const message = {
      type: "div",
      props: {
        dangerouslySetInnerHTML: {
          __html: `<h1>Arbitrary HTML</h1>
            <img onerror="alert(2)" src="" />
            <a href='http://danlec.com'>link</a>`
        }
      },
      key: null,
      ref: null,
      $$typeof: Symbol.for("react.element")
    };
    return <>{message}</>;
  }
}

这样在运行的时候,就会弹出一个alert框了。查看demo。那么,这样会有什么风险呢?

考虑一个场景,比如一个博客网站的评论信息message是由用户提供的,并且支持传入JSON。那么如果用户直接将上文的message发送给后台保存。之后,通过下面这种方式展示的话,用户就可以进行XSS攻击了。

<div>{message}</div>

假设如果没有$$typeof属性的话,这种攻击确实可行。因为其他的属性都是可序列化的。

const message = {
  type: "div",
  props: {
    dangerouslySetInnerHTML: {
      __html: `<h1>Arbitrary HTML</h1>
<img onerror="alert(2)" src="" />
<a href='http://danlec.com'>link</a>`
    }
  },
  key: null,
  ref: null,
};
​
JSON.stringify(message);

事实上,React 0.13当时就存在着这个漏洞。之后,React 0.14就修复了这个问题,修复方式就是通过引入$$typeof属性,并且用Symbol来作为它的值。

// 引入 $$typeof
const message = {
  type: "div",
  props: {
    dangerouslySetInnerHTML: {
      __html: `<h1>Arbitrary HTML</h1>
<img onerror="alert(2)" src="" />
<a href='http://danlec.com'>link</a>`
    }
  },
  key: null,
  ref: null,
  $$typeof: Symbol.for("react.element")
};
​
JSON.stringify(message); // Symbol无法被序列化

这是一个有效的方法,因为JSON是不支持Symbol类型的。所以,即使用户提交了如上的message信息,到最后服务端也不会保存$$typeof属性。而在渲染的时候,React 会检测是否有$$typeof属性。如果没有这个属性,则拒绝处理该元素。

那么如果浏览器不支持Symbol怎么办?

是的,那这种保护方案就没用了。React 依然会加上$$typeof字段,并且将其值设置为0xeac7。(为什么是这个数字呢,因为这个数字看起来有点像React)。

想查看具体的攻击流程,可以查看这篇博客

 

 


                
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Clover‘s Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值