React元素$$typeof属性

理解React使用$$typeof防止XSS攻击的机制

问题

为什么 React 元素有一个 $$typeof 属性?它的作用是什么?

解答

React 元素的结构

React 元素本质上是普通的 JavaScript 对象:

// JSX
const element = <div className="container">Hello</div>;

// 编译后的对象结构
const element = {
  $$typeof: Symbol.for('react.element'),
  type: 'div',
  props: {
    className: 'container',
    children: 'Hello'
  },
  key: null,
  ref: null
};

为什么需要 $$typeof

$$typeof 是 React 用来防止 XSS 攻击的安全机制。

假设你的应用允许用户存储 JSON 数据,并在服务端渲染时使用:

// 服务端从数据库读取用户数据
const userData = JSON.parse(dataFromDatabase);

// 直接渲染用户数据
function UserProfile({ user }) {
  return <div>{user.bio}</div>;
}

如果没有 $$typeof 保护,攻击者可以存储恶意的 JSON:

// 攻击者存储的恶意数据
{
  "bio": {
    "type": "div",
    "props": {
      "dangerouslySetInnerHTML": {
        "__html": "<script>alert('XSS')</script>"
      }
    }
  }
}

Symbol 的作用

React 使用 Symbol.for('react.element') 作为 $$typeof 的值:

// React 源码中的定义
const REACT_ELEMENT_TYPE = Symbol.for('react.element');

function createElement(type, props, ...children) {
  return {
    $$typeof: REACT_ELEMENT_TYPE,  // 标记为合法的 React 元素
    type,
    props: {
      ...props,
      children
    },
    key: null,
    ref: null
  };
}

关键点:JSON 不支持 Symbol 类型

// Symbol 无法被 JSON 序列化
JSON.stringify({ $$typeof: Symbol.for('react.element') });
// 输出: "{}"

// 攻击者无法通过 JSON 注入伪造 $$typeof
const malicious = JSON.parse('{"$$typeof": "Symbol.for(react.element)"}');
// malicious.$$typeof 是字符串,不是 Symbol

React 的验证逻辑

React 在渲染时会检查 $$typeof

// 简化的验证逻辑
function isValidElement(object) {
  return (
    typeof object === 'object' &&
    object !== null &&
    object.$$typeof === Symbol.for('react.element')
  );
}

// 渲染时
function render(element) {
  if (!isValidElement(element)) {
    // 当作普通文本处理,自动转义
    return escapeHtml(String(element));
  }
  // 作为 React 元素渲染
  return renderElement(element);
}

浏览器兼容处理

对于不支持 Symbol 的旧浏览器,React 使用特殊数字:

const REACT_ELEMENT_TYPE = typeof Symbol === 'function' && Symbol.for
  ? Symbol.for('react.element')
  : 0xeac7;  // 看起来像 "React"

关键点

  • $$typeof 是 React 元素的身份标识,用于区分合法元素和普通对象
  • 使用 Symbol 类型是因为 JSON 无法序列化 Symbol,防止服务端 JSON 注入攻击
  • React 渲染前会验证 $$typeof,不合法的对象会被当作文本处理并转义
  • 这是一种纵深防御策略,即使存在其他漏洞也能提供保护
  • 不支持 Symbol 的环境使用 0xeac7 作为降级方案