日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

詳解React?如何防止?XSS?攻擊論$$typeof?的作用_React

作者:??runnerdancer? ? 更新時間: 2022-09-17 編程語言

JSX

先來簡單復習一下 JSX 的基礎知識。JSX 是React.createElement的語法糖

<div id="container">hello</div>

經過?babel?編譯后:

React.createElement(
  "div" /* type */,
  { id: "container" } /* props */,
  "hello" /* children */
);

React.createElement最終返回的結果就是一個對象,如下:

{
  type: 'div',
  props: {
    id: 'container',
    children: 'hello',
  },
  key: null,
  ref: null,
  $$typeof: Symbol.for('react.element'),
}

這就是一個 React element 對象。

我們甚至可以在代碼中直接寫 React element 對象,React 照樣能正常渲染我們的內容:

render() {
  return (
    <div>
      {{
        $$typeof: Symbol.for('react.element'),
        props: {
          dangerouslySetInnerHTML: {
            __html: '<img src="x" onerror="alert(1)">'
          },
        },
        ref: null,
        type: "div",
      }}
    </div>
  );
}

可以復制這段代碼本地運行一下,可以發現瀏覽器彈出一個彈窗,并且img已經插入了 dom 中。

這里,$$typeof?的作用是啥?為什么使用?Symbol()?作為值?

在了解之前,我們先來簡單看下?XSS?攻擊

XSS 攻擊

我們經常需要構造 HTML 字符串并插入到 DOM 中,比如:

const messageEl = document.getElementById("message");
var message = "hello world";
messageEl.innerHTML = "<p>" + message + "</p>";

頁面正常顯示。但是如果我們插入一些惡意代碼,比如:

const messageEl = document.getElementById("message");
var message = '<img src onerror="alert(1)">';
messageEl.innerHTML = "<p>" + message + "</p>";

此時頁面就會彈出一個彈窗,彈窗內容顯示為 1

因此,直接使用 innerHTML 插入文本內容,存在 XSS 攻擊的風險

防止 XSS 攻擊的方法

為了解決類似的 XSS 攻擊方法,我們可以使用一些安全的 API 添加文本內容,比如:

  • 使用?document.createTextNode('hello world')?插入文本節點。
  • 或者使用?textContent?而不是?innerHTML?設置文本內容。
  • 對于一些特殊字符,比如?<>,我們可以進行轉義,將其轉換為?&#60;?以及?&#62;
  • 對于富文本內容,我們可以設置黑名單,過濾一些屬性,比如?onerror?等。

React 對于文本節點的處理

React 使用?createTextNode?或者?textContent?設置文本內容。

對于下面的代碼:

render() {
  const { count } = this.state
  return (
    <div onClick={() => this.setState({ count: count + 1})}>
      {count}
    </div>
  );
}

React 在渲染過程中會調用setTextContent方法為div節點設置內容,其中,第一次渲染時,直接設置div節點的textContent,第二次或者第二次以后的更新渲染,由于第一次設置了textContent,因此divfirstChild值存在,是個文本節點。此時直接更新這個文本節點的nodeValue即可

var setTextContent = function (node, text) {
  if (text) {
    var firstChild = node.firstChild;
    // 如果當前node節點已經設置過textContent,則firstChild不為空,是個文本節點TEXT_NODE
    if (
      firstChild &&
      firstChild === node.lastChild &&
      firstChild.nodeType === TEXT_NODE
    ) {
      firstChild.nodeValue = text;
      return;
    }
  }
  // 第一次渲染,直接設置textContent
  node.textContent = text;
};

綜上,對于普通的文本節點來說,由于 React 是采用 textContent 或者 createTextNode 的方式添加的,因此是不會存在 XSS 攻擊的,即使上面示例中,count 的值為?'<img src="x" onerror="alert(1)">'也不會有被攻擊的風險

dangerouslySetInnerHTML 處理富文本節點

有時候我們確實需要顯示富文本的內容,React 提供了dangerouslySetInnerHTML方便我們顯式的插入富文本內容

render() {
  return (
    <div
      id="dangerous"
      dangerouslySetInnerHTML={{ __html: '<img src="x" onerror="alert(1)">' }}
    >
    </div>
  );
}

React 在為 DOM 更新屬性時,會判斷屬性的key是不是dangerouslySetInnerHTML,如果是的話,調用setInnerHTML?方法直接給 dom 的innerHTML屬性設置文本內容

function setInitialDOMProperties(
  tag,
  domElement,
  rootContainerElement,
  nextProps
) {
  for (var propKey in nextProps) {
    if (propKey === "dangerouslySetInnerHTML") {
      var nextHtml = nextProp ? nextProp.__html : undefined;
      if (nextHtml != null) {
        setInnerHTML(domElement, nextHtml);
      }
    }
  }
}
var setInnerHTML = function (node, html) {
  node.innerHTML = html;
};

可以看出,React 在處理富文本時,也僅僅是簡單的設置 DOM 的innerHTML屬性來實現的。

對于富文本潛在的安全風險,交由開發者自行把控。

$$typeof 的作用

render() {
  const { text } = this.state
  return (
    <div>
      {text}
    </div>
  );
}

假設這個text是從后端返回來的,同時后端允許用戶存儲 JSON 對象,如果用戶傳入下面這樣的一個類似 React element 的對象:

{
  type: "div",
  props: {
    dangerouslySetInnerHTML: {
      __html: '<img src="x" onerror="alert(1)">'
    },
  },
  ref: null
}

別忘了前面說過,我們在 JSX 中直接插入 React element 對象也是能夠正常渲染的。

在這種情況下,在React0.13版本時,這是一個潛在的XSS攻擊,這個漏洞源于服務端。如果攻擊者惡意偽造一個類似 React element 對象的數據返回給前端,React 就會執行惡意代碼。但是 React 可以采取措施預防這種攻擊。

React0.14版本開始,React 為每個 element 都添加了一個Symbol標志:

{
  $$typeof: Symbol.for('react.element'),
  props: {
    id: 'container'
  },
  ref: null,
  type: "div",
}

這個行得通,是因為 JSON 不支持Symbol。因此即使是服務端有風險漏洞并且返回一個 JSON,這個 JSON 也不會包含Symbol.for('react.element').,在 Reconcile 階段,React 會檢查element.$$typeof標志是否合法。不合法的話直接報錯,React 不能接受對象作為 children

專門使用 Symbol.for() 的好處是, Symbols 在 iframe 和 worker 等環境之間是全局的。因此,即使在更奇特的條件下,Symbols 也能在不同的應用程序之間傳遞受信任的元素。同樣,即使頁面上有多個 React 副本,它們仍然可以“同意”有效的 $$typeof 值

如果瀏覽器不支持Symbols,React 使用0xeac7代替

{
  $$typeof: '0xeac7',
}

原文鏈接:https://juejin.cn/post/7122828523923832840

欄目分類
最近更新