詳解React?如何防止?XSS?攻擊論$$typeof?的作用
JSX
先來簡單復(fù)習(xí)一下 JSX 的基礎(chǔ)知識(shí)。JSX 是React.createElement的語法糖
<div id="container">hello</div>
經(jīng)過 babel
編譯后:
React.createElement( "div" /* type */, { id: "container" } /* props */, "hello" /* children */ );
React.createElement
最終返回的結(jié)果就是一個(gè)對象,如下:
{ type: 'div', props: { id: 'container', children: 'hello', }, key: null, ref: null, $$typeof: Symbol.for('react.element'), }
這就是一個(gè) React element 對象。
我們甚至可以在代碼中直接寫 React element 對象,React 照樣能正常渲染我們的內(nèi)容:
render() { return ( <div> {{ $$typeof: Symbol.for('react.element'), props: { dangerouslySetInnerHTML: { __html: '<img src="x" onerror="alert(1)">' }, }, ref: null, type: "div", }} </div> ); }
可以復(fù)制這段代碼本地運(yùn)行一下,可以發(fā)現(xiàn)瀏覽器彈出一個(gè)彈窗,并且img
已經(jīng)插入了 dom 中。
這里,$$typeof
的作用是啥?為什么使用 Symbol()
作為值?
在了解之前,我們先來簡單看下 XSS
攻擊
XSS 攻擊
我們經(jīng)常需要構(gòu)造 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>";
此時(shí)頁面就會(huì)彈出一個(gè)彈窗,彈窗內(nèi)容顯示為 1
因此,直接使用 innerHTML 插入文本內(nèi)容,存在 XSS 攻擊的風(fēng)險(xiǎn)
防止 XSS 攻擊的方法
為了解決類似的 XSS 攻擊方法,我們可以使用一些安全的 API 添加文本內(nèi)容,比如:
- 使用
document.createTextNode('hello world')
插入文本節(jié)點(diǎn)。 - 或者使用
textContent
而不是innerHTML
設(shè)置文本內(nèi)容。 - 對于一些特殊字符,比如
<
、>
,我們可以進(jìn)行轉(zhuǎn)義,將其轉(zhuǎn)換為<
以及>
- 對于富文本內(nèi)容,我們可以設(shè)置黑名單,過濾一些屬性,比如
onerror
等。
React 對于文本節(jié)點(diǎn)的處理
React 使用 createTextNode
或者 textContent
設(shè)置文本內(nèi)容。
對于下面的代碼:
render() { const { count } = this.state return ( <div onClick={() => this.setState({ count: count + 1})}> {count} </div> ); }
React 在渲染過程中會(huì)調(diào)用setTextContent
方法為div
節(jié)點(diǎn)設(shè)置內(nèi)容,其中,第一次渲染時(shí),直接設(shè)置div
節(jié)點(diǎn)的textContent
,第二次或者第二次以后的更新渲染,由于第一次設(shè)置了textContent
,因此div
的firstChild
值存在,是個(gè)文本節(jié)點(diǎn)。此時(shí)直接更新這個(gè)文本節(jié)點(diǎn)的nodeValue
即可
var setTextContent = function (node, text) { if (text) { var firstChild = node.firstChild; // 如果當(dāng)前node節(jié)點(diǎn)已經(jīng)設(shè)置過textContent,則firstChild不為空,是個(gè)文本節(jié)點(diǎn)TEXT_NODE if ( firstChild && firstChild === node.lastChild && firstChild.nodeType === TEXT_NODE ) { firstChild.nodeValue = text; return; } } // 第一次渲染,直接設(shè)置textContent node.textContent = text; };
綜上,對于普通的文本節(jié)點(diǎn)來說,由于 React 是采用 textContent 或者 createTextNode 的方式添加的,因此是不會(huì)存在 XSS 攻擊的,即使上面示例中,count 的值為 '<img src="x" onerror="alert(1)">'
也不會(huì)有被攻擊的風(fēng)險(xiǎn)
dangerouslySetInnerHTML 處理富文本節(jié)點(diǎn)
有時(shí)候我們確實(shí)需要顯示富文本的內(nèi)容,React 提供了dangerouslySetInnerHTML
方便我們顯式的插入富文本內(nèi)容
render() { return ( <div id="dangerous" dangerouslySetInnerHTML={{ __html: '<img src="x" onerror="alert(1)">' }} > </div> ); }
React 在為 DOM 更新屬性時(shí),會(huì)判斷屬性的key
是不是dangerouslySetInnerHTML
,如果是的話,調(diào)用setInnerHTML
方法直接給 dom 的innerHTML
屬性設(shè)置文本內(nèi)容
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 在處理富文本時(shí),也僅僅是簡單的設(shè)置 DOM 的innerHTML
屬性來實(shí)現(xiàn)的。
對于富文本潛在的安全風(fēng)險(xiǎn),交由開發(fā)者自行把控。
$$typeof 的作用
render() { const { text } = this.state return ( <div> {text} </div> ); }
假設(shè)這個(gè)text
是從后端返回來的,同時(shí)后端允許用戶存儲(chǔ) JSON 對象,如果用戶傳入下面這樣的一個(gè)類似 React element 的對象:
{ type: "div", props: { dangerouslySetInnerHTML: { __html: '<img src="x" onerror="alert(1)">' }, }, ref: null }
別忘了前面說過,我們在 JSX 中直接插入 React element 對象也是能夠正常渲染的。
在這種情況下,在React0.13
版本時(shí),這是一個(gè)潛在的XSS
攻擊,這個(gè)漏洞源于服務(wù)端。如果攻擊者惡意偽造一個(gè)類似 React element 對象的數(shù)據(jù)返回給前端,React 就會(huì)執(zhí)行惡意代碼。但是 React 可以采取措施預(yù)防這種攻擊。
從React0.14
版本開始,React 為每個(gè) element 都添加了一個(gè)Symbol
標(biāo)志:
{ $$typeof: Symbol.for('react.element'), props: { id: 'container' }, ref: null, type: "div", }
這個(gè)行得通,是因?yàn)?JSON 不支持Symbol
。因此即使是服務(wù)端有風(fēng)險(xiǎn)漏洞并且返回一個(gè) JSON,這個(gè) JSON 也不會(huì)包含Symbol.for('react.element').
,在 Reconcile 階段,React 會(huì)檢查element.$$typeof
標(biāo)志是否合法。不合法的話直接報(bào)錯(cuò),React 不能接受對象作為 children
專門使用 Symbol.for() 的好處是, Symbols 在 iframe 和 worker 等環(huán)境之間是全局的。因此,即使在更奇特的條件下,Symbols 也能在不同的應(yīng)用程序之間傳遞受信任的元素。同樣,即使頁面上有多個(gè) React 副本,它們?nèi)匀豢梢?ldquo;同意”有效的 $$typeof 值
如果瀏覽器不支持Symbols
,React 使用0xeac7
代替
{ $$typeof: '0xeac7', }
到此這篇關(guān)于詳解React 如何防止 XSS 攻擊論$$typeof 的作用的文章就介紹到這了,更多相關(guān)React $$typeof 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Can't?perform?a?React?state?update?on?an?unmoun
這篇文章主要為大家介紹了Can't?perform?a?React?state?update?on?an?unmounted?component報(bào)錯(cuò)解決方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12React路由的history對象的插件history的使用解讀
這篇文章主要介紹了React路由的history對象的插件history的使用,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-10-10React實(shí)現(xiàn)文件上傳和斷點(diǎn)續(xù)傳功能的示例代碼
這篇文章主要為大家詳細(xì)介紹了React實(shí)現(xiàn)文件上傳和斷點(diǎn)續(xù)傳功能的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-02-02React?Hooks的useState、useRef使用小結(jié)
React Hooks 是 React 16.8 版本引入的新特性,useState和useRef是兩個(gè)常用的Hooks,本文主要介紹了React?Hooks的useState、useRef使用,感興趣的可以了解一下2024-01-01React如何將組件渲染到指定DOM節(jié)點(diǎn)詳解
這篇文章主要給大家介紹了關(guān)于React如何將組件渲染到指定DOM節(jié)點(diǎn)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)下吧。2017-09-09React如何實(shí)現(xiàn)瀏覽器打印部分內(nèi)容詳析
這篇文章主要給大家介紹了關(guān)于利用React如何實(shí)現(xiàn)瀏覽器打印部分內(nèi)容的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用React具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05