詳解React?如何防止?XSS?攻擊論$$typeof?的作用
JSX
先來簡單復(fù)習(xí)一下 JSX 的基礎(chǔ)知識。JSX 是React.createElement的語法糖
<div id="container">hello</div>
經(jīng)過 babel 編譯后:
React.createElement(
"div" /* type */,
{ id: "container" } /* props */,
"hello" /* children */
);React.createElement最終返回的結(jié)果就是一個對象,如下:
{
type: 'div',
props: {
id: 'container',
children: 'hello',
},
key: null,
ref: null,
$$typeof: Symbol.for('react.element'),
}這就是一個 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ù)制這段代碼本地運行一下,可以發(fā)現(xiàn)瀏覽器彈出一個彈窗,并且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>";此時頁面就會彈出一個彈窗,彈窗內(nèi)容顯示為 1
因此,直接使用 innerHTML 插入文本內(nèi)容,存在 XSS 攻擊的風(fēng)險
防止 XSS 攻擊的方法
為了解決類似的 XSS 攻擊方法,我們可以使用一些安全的 API 添加文本內(nèi)容,比如:
- 使用
document.createTextNode('hello world')插入文本節(jié)點。 - 或者使用
textContent而不是innerHTML設(shè)置文本內(nèi)容。 - 對于一些特殊字符,比如
<、>,我們可以進行轉(zhuǎn)義,將其轉(zhuǎn)換為<以及> - 對于富文本內(nèi)容,我們可以設(shè)置黑名單,過濾一些屬性,比如
onerror等。
React 對于文本節(jié)點的處理
React 使用 createTextNode 或者 textContent 設(shè)置文本內(nèi)容。
對于下面的代碼:
render() {
const { count } = this.state
return (
<div onClick={() => this.setState({ count: count + 1})}>
{count}
</div>
);
}React 在渲染過程中會調(diào)用setTextContent方法為div節(jié)點設(shè)置內(nèi)容,其中,第一次渲染時,直接設(shè)置div節(jié)點的textContent,第二次或者第二次以后的更新渲染,由于第一次設(shè)置了textContent,因此div的firstChild值存在,是個文本節(jié)點。此時直接更新這個文本節(jié)點的nodeValue即可
var setTextContent = function (node, text) {
if (text) {
var firstChild = node.firstChild;
// 如果當(dāng)前node節(jié)點已經(jīng)設(shè)置過textContent,則firstChild不為空,是個文本節(jié)點TEXT_NODE
if (
firstChild &&
firstChild === node.lastChild &&
firstChild.nodeType === TEXT_NODE
) {
firstChild.nodeValue = text;
return;
}
}
// 第一次渲染,直接設(shè)置textContent
node.textContent = text;
};綜上,對于普通的文本節(jié)點來說,由于 React 是采用 textContent 或者 createTextNode 的方式添加的,因此是不會存在 XSS 攻擊的,即使上面示例中,count 的值為 '<img src="x" onerror="alert(1)">'也不會有被攻擊的風(fēng)險
dangerouslySetInnerHTML 處理富文本節(jié)點
有時候我們確實需要顯示富文本的內(nèi)容,React 提供了dangerouslySetInnerHTML方便我們顯式的插入富文本內(nèi)容
render() {
return (
<div
id="dangerous"
dangerouslySetInnerHTML={{ __html: '<img src="x" onerror="alert(1)">' }}
>
</div>
);
}React 在為 DOM 更新屬性時,會判斷屬性的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è)置 DOM 的innerHTML屬性來實現(xiàn)的。
對于富文本潛在的安全風(fēng)險,交由開發(fā)者自行把控。
$$typeof 的作用
render() {
const { text } = this.state
return (
<div>
{text}
</div>
);
}假設(shè)這個text是從后端返回來的,同時后端允許用戶存儲 JSON 對象,如果用戶傳入下面這樣的一個類似 React element 的對象:
{
type: "div",
props: {
dangerouslySetInnerHTML: {
__html: '<img src="x" onerror="alert(1)">'
},
},
ref: null
}別忘了前面說過,我們在 JSX 中直接插入 React element 對象也是能夠正常渲染的。
在這種情況下,在React0.13版本時,這是一個潛在的XSS攻擊,這個漏洞源于服務(wù)端。如果攻擊者惡意偽造一個類似 React element 對象的數(shù)據(jù)返回給前端,React 就會執(zhí)行惡意代碼。但是 React 可以采取措施預(yù)防這種攻擊。
從React0.14版本開始,React 為每個 element 都添加了一個Symbol標(biāo)志:
{
$$typeof: Symbol.for('react.element'),
props: {
id: 'container'
},
ref: null,
type: "div",
}這個行得通,是因為 JSON 不支持Symbol。因此即使是服務(wù)端有風(fēng)險漏洞并且返回一個 JSON,這個 JSON 也不會包含Symbol.for('react.element').,在 Reconcile 階段,React 會檢查element.$$typeof標(biāo)志是否合法。不合法的話直接報錯,React 不能接受對象作為 children
專門使用 Symbol.for() 的好處是, Symbols 在 iframe 和 worker 等環(huán)境之間是全局的。因此,即使在更奇特的條件下,Symbols 也能在不同的應(yīng)用程序之間傳遞受信任的元素。同樣,即使頁面上有多個 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)文章
每天一個hooks學(xué)習(xí)之useUpdateEffect
這篇文章主要為大家介紹了每天一個hooks學(xué)習(xí)之useUpdateEffect使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-05-05
詳解react使用react-bootstrap當(dāng)輪子造車
本篇文章主要介紹了詳解react使用react-bootstrap當(dāng)輪子造車,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-08-08

