爲什麼說
$$typeof
是最重要的屬性?因爲它是代碼安全的一道重要防線。
如果你用過 React,對 type
、 props
、 key
、 和 ref
應該熟悉。 但你不一定知道 $$typeof
?
首先簡單介紹下JSX
當你在寫 JSX 時,其實你在調用createElement
方法。
React.createElement(
/* type */ 'marquee',
/* props */ { bgcolor: '#ffa7c4' },
/* children */ 'hi'
)
createElement
會返回一個對象,我們稱此對象爲React的 元素(element),它告訴 React 下一個要渲染什麼。你的組件(component)返回一個它們組成的樹(tree)。
{
type: 'marquee',
props: { //... },
key: null,
ref: null,
$$typeof: Symbol.for('react.element'),
}
HTML的插入轉義
在客戶端 UI 庫變得普遍且具有基本保護作用之前,應用程序代碼通常是先構建 HTML,然後把它插入 DOM 中:
const messageEl = document.getElementById('message');
messageEl.innerHTML = '<p>' + message.text + '</p>';
這樣看起來沒什麼問題,但當你 message.text
的值類似 '<img src onerror="stealYourPassword()">'
時, 你不會希望別人寫的內容在你應用的 HTML 中逐字顯示的。
爲什麼防止此類攻擊,你可以用只處理文本的 document.createTextNode()
或者 textContent
等安全的 API。你也可以事先將用戶輸入的內容,用轉義符把潛在危險字符( <
、 >
等)替換掉。
儘管如此,這個問題的成本代價很高,且很難做到用戶每次輸入都記得轉換一次。 因此像React等新庫會默認進行文本轉義:
如果 message.text
是一個帶有 <img>
或其他標籤的惡意字符串,它不會被當成真的 <img>
標籤處理,React 會先進行轉義然後插入 DOM 裏。所以 <img>
標籤會以文本的形式展現出來。
在 React 中如果元素要渲染 HTML,那麼需要使用 dangerouslySetInnerHTML={{ __html: message.text }}
這意味着React完全不懼注入攻擊了嗎?不,HTML 和 DOM 暴露了大量攻擊點,對 React 或者其他 UI 庫來說,要減輕傷害太難或進展緩慢。大部分存在的攻擊方向涉及到屬性,例如,如果你渲染 <a href={user.website}
,要提防用戶的網址是 'javascript: stealYourPassword()'
。 像 <div {...userData}>
寫法幾乎不受用戶輸入影響,但也有危險。
不過,轉義文本這第一道防線可以攔下許多潛在攻擊,知道這樣的代碼是安全的就夠了嗎?不一定,所以我們需要$$typeof
關於 $$typeof
如果你的服務器有允許用戶存儲任意 JSON 對象的漏洞,而前端需要一個字符串,這可能會發生一個問題:
// 服務端允許用戶存儲 JSON
let expectedTextButGotJSON = {
type: 'div',
props: {
dangerouslySetInnerHTML: {
__html: '/* 把你想的放在這裏 */'
},
},
// ...
};
let message = { text: expectedTextButGotJSON };
// React 0.13 中有風險
<p>
{message.text}
</p>
在這個例子中,React 0.13 很容易受到 XSS 攻擊。雖然 這個攻擊是服務端存在漏洞導致的。不過,從 React 0.14 開始,這個問題修復了。
React 0.14 修復手段是在虛擬DOM中添加 $$typeof
,使用 Symbol
標記每個 React 元素(element):
Symbol
類型是非常重要的,因爲JSON不支持 Symbol
類型。 所以即使服務器存在用JSON作爲文本返回安全漏洞,JSON 裏也不包含 Symbol.for('react.element')
。React 會檢測 element.$$typeof
,如果元素丟失或者無效,會拒絕處理該元素。
特意用 Symbol.for()
的好處是 Symbols 通用於 iframes 和 workers 等環境中。因此無論在多奇怪的條件下,這方案也不會影響到應用不同部分傳遞可信的元素。同樣,即使頁面上有很多個 React 副本,它們也 「接受」 有效的 $$typeof
值。
爲什麼是這個數字?因爲 0xeac7
看起來有點像 「React」。
最後
- 覺得有用點個讚唄
- 歡迎關注公衆號「前端進階課」認真學前端,一起進階。