網站首頁 編程語言 正文
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
?設置文本內容。 - 對于一些特殊字符,比如?
<
、>
,我們可以進行轉義,將其轉換為?<
?以及?>
- 對于富文本內容,我們可以設置黑名單,過濾一些屬性,比如?
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
,因此div
的firstChild
值存在,是個文本節點。此時直接更新這個文本節點的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
相關推薦
- 2022-04-25 turtle的基礎使用之python?turtle遞歸繪圖_python
- 2022-12-08 Flask框架運用Ajax實現數據交互的示例代碼_python
- 2022-09-03 PyQt5實現tableWidget?居中顯示_python
- 2022-05-15 Docker基本概念和底層原理解析_docker
- 2022-04-28 一篇文章帶你了解C++特殊類的設計_C 語言
- 2022-08-25 Zabbix對Kafka?topic積壓數據監控的問題(bug優化)_zabbix
- 2022-04-23 冷知識:font-size最小12px的誤區
- 2022-09-20 linux?shell字符串截取的詳細總結(實用!)_linux shell
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支