Cross-Origin Read Blocking (CORB)

騰訊IVWEB團隊發表於2019-04-26

本文的開始源於落地頁專案中遇到的 Chrome 控制檯 warn 提示,擔心影響頁面渲染,特此弄個究竟。提示如下,

Cross-Origin Read Blocking (CORB) blocked cross-origin response https://www.example.com/example.html with MIME type text/html. See https://www.chromestatus.com/feature/5629709824032768 for more details.
複製程式碼

除非特殊說明,否則本文中的瀏覽器均指 Chrome Browser

前言

本文將從以下幾個方面對 CORB 進行探討,

  • 什麼是 CORB
  • 為什麼會產生 CORB
  • 什麼情況下會出現 CORB
  • 出現 CORB 時,我們可以如何看待

CORB 發生時瀏覽器表現

CORB 是一種判斷是否要在跨站資源資料到達頁面之前阻斷其到達當前站點程式中的演算法,降低了敏感資料暴露的風險。

- Chrome 瀏覽器提示

當請求發生 CORB 時,瀏覽器控制檯會列印如下警告內容,

Content-Type: MIME

Cross-Origin Read Blocking (CORB) blocked cross-origin response https://www.example.com/example.html with MIME type text/html. See https://www.chromestatus.com/feature/5629709824032768 for more details
複製程式碼

chrome 66或這個版本之前,提示資訊有細微不同,

Blocked current origin from receiving cross-site document at https://www.example.com/example.html with MIME type text/html
複製程式碼

當請求的響應結果本身就出錯或為空時,早期版本 Chrome 依舊會出現上述提示,但 Chrome 69 之後的版本不再出現上述提示。下文實驗一和實驗二驗證了該描述。

- Chrome 瀏覽器行為

The response body is replaced with an empty body. // 響應資料置為空
The response headers are removed. // 移除響應請求頭
複製程式碼

CORB 啟動時,雖然響應結果會被置空,但是請求的服務仍然成功,`status: 200`。比如:使用 `img` 標籤上報頁面監控資料,儘管響應結果為空,但請求依舊傳送成功,伺服器亦正常響應。下文實驗一已驗證。

為什麼會有 CORB 的出現?

簡單來說,就是出現了一些網路安全漏洞,為防止漏洞肆虐,便出現了站點隔離(Site Isolation),CORB 則是其中的一種實現策略。

Spectre 和 Meltdown 漏洞

當惡意程式碼和正常站點存在於同一個程式時,惡意程式碼便可以訪問程式內的記憶體,進行一系列訪問攻擊,此時,惡意程式碼竊取資料的唯一難點在於不知道敏感資料的具體儲存位置,但通過 CPU 預執行 和 SCA 可以一步步 試探 出來。詳細瞭解可參看: zhuanlan.zhihu.com/p/32784852

什麼是 CPU 預執行?

if(condition)
   do_sth();
複製程式碼

CPU 執行速度大於記憶體讀取速度,為了提升 CPU 使用率,在從記憶體中讀取 condition 完成之前,CPU 就已經開始執行下文內容。即不管 if 條件是否返回 true,CPU 都會提前執行裡面的語句do_sth()。 CPU 預執行是晶片製造者決定的,為了提升 CPU 使用速度和效率而建的,預執行紅利不是輕易就能放棄的,因此,目前或短期來看基本沒可能改變。

普通瀏覽器中,不同的站點可能共享同一個程式

在某些情況下,沒有實現 Site Isolution 的普通瀏覽器會出現一個程式裡面同時執行多個站點的程式碼,這就讓惡意站點有機可乘。比如惡意站點 a.dd.com 在自己的程式碼中嵌入 <iframe src="https://v.qq.com" frameborder="0"></iframe>,這時,普通瀏覽器就會把帶有惡意站點 a.dd.com 的惡意程式碼 和 v.qq.com 放在同一個記憶體中執行。

共享程式

SCA(Side-Channel Attacks) 旁道攻擊

簡單來說,就是利用程式執行時,系統產生的一些物理特徵(如:時延,能耗,電磁,錯誤訊息,頻率等)進行推測型攻擊。看起來有點不可思議,但早在 1956 年,英國已經利用 SCA 獲取了埃及駐倫敦的加密機。

緩衝時延(Cache Timing)旁路是通過記憶體訪問時間的不同來產生的旁路。假設訪問一個變數,這個變數在記憶體中,這需要上百個時鐘週期才能完成,但如果變數訪問過一次,這個變數被載入到緩衝(Cache)中了,下次再訪問,可能幾個時鐘週期就可以完成了,可根據這種訪問速度竊取特定資料。Spectre 和 Meltdown 漏洞便是利用了這種特性。

如何預防 Spectre 和 Meltdown 漏洞呢?

漏洞三大關鍵點是 CPU 預執行、SCA 和 共享程式。預防就得從這三個方面著手。先看 SCA,演算法執行時間的變化本質就是源於資料處理,根據時間變化推測運算操作和資料儲存位置,因此 SCA 可預防性極低。再看 CPU 預執行,效能至少提高 10%,一片可觀的紅利,晶片廠商如何捨得放棄。如此,只能針對共享程式下手了,Site Isolation 便是剝離共享程式的一項技術,採用獨立站點獨立程式的方式實現,降低漏洞的威脅。

Site Isolation

站點隔離保證了不同站點頁面始終被放入不同的程式,每個程式執行在一個有限制的沙箱環境中,在該環境中可能會阻止程式接收其它站點返回的某些特殊型別敏感資訊,惡意站點不再和正常站點共享程式,這就讓惡意站點竊取其它站點的資訊變得更加困難。從 Chrome 67 開始,已預設啟用 Site Isolation。

Site Isolation

經驗證,Site Isolation 關於程式獨立的原則是 只要一級域名一樣,站點例項就共享一個程式,無論子域名是否一樣。如果使用 iframe 嵌入了一級域名不一樣的跨域站點,則會生成一個新的程式維護該跨域站點執行,這一點同前文介紹的普通瀏覽器共享程式不同。更詳細的內容參看 www.yaoyanhuo.com/blog/site_i…

這是 Site Isolation 的程式設計,那麼其中的 CORB 扮演了什麼角色呢?

在同源策略下,Site Isolation 已經很好地隔離了站點,只是還有跨域標籤這樣的東西存在,敏感資料依舊會暴露,依舊會進駐惡意站點記憶體空間。 有這樣一個場景,使用者登入某站點 some.qq.com後,又訪問了 bad.dd.com 惡意站點,惡意站點有如下程式碼,<script src="some.qq.com/login">,跨域請求了原站點的登入請求,此時,普通瀏覽器會正常返回登入後的敏感資訊,且敏感資訊會進駐 bad.dd.com 記憶體空間。好不容易站點隔離把各個站點資訊分開了,這因為跨域又在一起了。咋整?CORB 來了。CORB 會在敏感資訊到達 web apge 之前,將其攔截掉,如此,敏感資訊既不會暴露於瀏覽器,也不會進駐記憶體空間,得到了很好的保護。


CORB 發生時機

當跨域請求回來的資料 MIME type 同跨域標籤應有的 MIME 型別不匹配時,瀏覽器會啟動 CORB 保護資料不被洩漏,被保護的資料型別只有 html xmljson。很明顯 <script><img> 等跨域標籤應有的 MIME type 和 htmlxmljson 不一樣。

MIME type (Multipurpose Internet Mail Extensions)

MIME type 同 CORB 有著相當緊密的關係,可以說 CORB 的產生直接依附 MIME 型別。因此,閱讀本文前,有必要先理解一下什麼是 MIME type。

MIME 是一個網際網路標準,擴充套件了電子郵件標準,使其可以支援更多的訊息型別。常見 MIME 型別如:text/html text/plain image/png application/javascript ,用於標識返回訊息屬於哪一種文件型別。寫法為 type/subtype。 在 HTTP 請求的響應頭中,以 Content-Type: application/javascript; charset=UTF-8 的形式出現,MIME typeContent-Type 值的一部分。如下圖,

Content-Type: MIME

內容嗅探技術(MIME sniffing)

內容嗅探技術是指 當響應頭沒有指明 MIME type 或 瀏覽器認為指定型別有誤時,瀏覽器會對內容資源進行檢查並執行,來猜測內容的正確MIME型別。嗅探技術的實現細節,不同的瀏覽器在不同的場景下有不同的方式,本文不做詳述。詳細內容參見:www.keycdn.com/support/wha…

如何禁用 MIME sniffing 呢?

伺服器在響應首部新增 X-Content-Type-Options: nosniff,用來告訴瀏覽器一定要相信 Content-Type 中指定的 MIME 型別,不要再使用內容嗅探技術探測響應內容型別。該方法僅對 <script><style> 有效。

官方解釋:developer.mozilla.org/en-US/docs/…

瀏覽器如何判斷響應內容是否需要 CORB 保護?

這可能是本文最需要關心的內容了,到底什麼情況下會出現 CORB 。在滿足跨域標籤(如:<script><img>)請求的響應內容的 MIME typeHTML MIME typeXML MIME typeJSON MIME typetext/plain 時,以下三個條件任何一個滿足,就享受 CORB 保護。(image/svg+xml 不在內,屬圖片型別)

  • 響應頭包含 X-Content-Type-Options: nosniff
  • 響應結果狀態碼是 206 Partial Contentdeveloper.mozilla.org/zh-CN/docs/…
  • 瀏覽器嗅探響應內容的 MIME 型別結果就是 json/xml/html

這種嗅探用於防止某些內容因被錯誤標記 MIME 型別 而被 CORB 阻斷不能正常響應返回,且該嗅探基於 Content-Type 進行,比如型別是 text/json,便只會對內容進行 json 型別檢查,而不會進行 xml 或 html 的檢查。

HTML MIME typeXML MIME typeJSON MIME type 的出現能理解,為什麼 text/plain 型別也會在保護範圍內?

因為 當 Content-Type 缺失的時候,響應內容 MIME type 有可能就是 text/plain;且據可靠資料顯示, HTML, JSON, or XML 有時候也會被標記為 text/palin。如,

data.txt

{
  "ret_code": 0,
  "msg": "請求成功!",
  "data": [1, 2, 3, 4, 5]
}
複製程式碼

server.js

app.get('/file', function getState(req,res,next){
  // res.type('json')
  res.sendfile(`${__dirname}/public/data.txt`)
})
複製程式碼

如上程式碼,啟動 server.js ,Chrome 瀏覽器訪問 /file 時服務返回 data.txt 內容,儘管響應頭是 Content-Type: text/plain; charset=UTF-8,響應內容依舊能被識別為 json。由此, text/plain 會作為 json 的標記也是一種常見現象。如果跨域訪問 /file 就會出現 CORB,驗證結果如下圖,

跨域訪問

如果使用 script 跨域請求本就是 js 資源,但該資源卻被打上了錯誤的 Content-Type,還新增了 nosiniff,會發生什麼?

很多時候 script 檔案被會打上 html xml json 這些 MIME 型別,如果 Chrome 瀏覽器直接 block,將相應內容置空,當前域下的網站便會 因為缺少 js 執行內容而不能正常執行。為避免這種情況出現,Chrome 瀏覽器在決定是否保護響應內容前,會先判斷 script 的響應內容是否是受保護的 MIME 型別(html xml json )。如果檢測結果是,則啟動 CORB,如果無法檢測會直接返回,不啟用 CORB。

對於跨域請求 js 資源,如果已經存在 nosniff 的情況下,還把 js 資源設定成了其它型別(如:json),那麼必定觸發 CORB 保護機制,無法返回 js 資源內容,如果此時本域站點剛好需要這個 js 資源,就 GG 了。相當於 錯誤的 MIME type 加上 X-Content-Type-Options: nosniff 會觸發 CORB ,即使資源真正的型別同跨域標籤一致。


為了最佳安全策略,建議開發者

  1. 為響應內容標記正確的 Content-Type
  2. 使用 X-Content-Type-Options: nosniff 禁止 MIME sniffing,如此,可以讓瀏覽器不進行內容 MIME 型別嗅探,從而更簡單快速地保護資源或響應返回。

控制檯出現 CORB 提示時,不用擔心,一般不會對頁面產生本質性的影響,可以直接忽略。


Chrome 發生 CORB 保護時的提示和行為驗證

雖然該部分內容屬於驗證類,但想了解一項知識點,僅僅簡單地閱讀是不夠的,實際操作試驗後才能獲得更深的印象和理解。

環境準備

Chrome 版本: Chrome 73

Node 服務程式碼 index.js

const express = require('express')
const path = require('path')
const app = express()
const port = process.argv[2] || 3002

app.get('/', function (req, res) {
  res.send('<p>hello world!</p>')
})

app.get('/data', function (req, res) {
  console.log('請求正常,只是瀏覽器將響應資料置空')
  res.json({greeting: 'hello chrome!'})
})

app.listen(port, () => console.log(`app is listening at localhost:${port}`))
複製程式碼

配置 host

127.0.0.1 a.dd.com
127.0.0.1 c.dd.com
127.0.0.1 test.pp.com
複製程式碼

實驗一:在test.pp.com中使用 img 標籤跨域請求 c.dd.com 的資料,資料 MIME 型別為 json

  1. 執行 npm initnpm install 安裝服務依賴包
  2. 執行 node index.js 啟動服務
  3. 瀏覽器中訪問 test.pp.com:3002 並開啟 開發者工具
  4. 開發者工具 Elements 中插入 <img src="http://c.dd.com:3002/data"/> (選中 body 元素,再按 F2 即可進入 html 編輯模式)

Elements 插入 img 標籤

  1. 檢視控制檯 console 即可見,CORB 提示。

控制檯結果

  1. 刪掉 app.get('/data') 方法返回的資料 {greeting: 'hello chrome!'},即 將服務本身返回的資料本身置空,CORB 提示消失,但依舊看不到請求頭 和 響應結果。

實驗結果:1. 使用 `img` 跨域請求 json 型別的資料確實會出現 CORB;2. 當服務本身返回資料為空時,CORB 提示會消失,但其行為依然保持。

實驗二:在test.pp.com 中使用 script 跨域請求 c.dd.com 的資料,資料 MIME 型別為 json

  1. 補回實驗一刪掉的程式碼 {greeting: 'hello chrome!'},瀏覽器中訪問 test.pp.com 並 開啟開發者工具。
  2. 在開發者工具 console 欄中執行下方程式碼,即可插入 js 標籤併傳送跨域請求。
s = document.createElement('script')
s.src = 'http://c.dd.com:3002/data'
document.head.appendChild(s)
複製程式碼

效果如圖,

控制檯結果

img 表現一致,出現了 CORB 提示。清除{greeting: 'hello chrome!'},將服務返回資料置空,效果同 img 方式表現一致,CROB 提示消失。其它行為也同 img

實驗結果:同 `img` 行為效果一模一樣。

看了瀏覽器中 請求的響應 情況,現在看看,兩次實驗的 請求執行 情況,

實驗結果

可以看到儘管產生了 CORB 保護,讓響應結果變為空,也隱藏了請求頭,但服務請求本身始終正常接收請求並進行處理。由此,看到 CORB 後,一般可以直接忽略該提示。

如果跨域請求 http://a.dd.com:3002/data 本身發生錯誤,則完全無需 CORB 的保護,本身就已經不能正常返回了。因此,更不需要 CORB 的提示和行為。

實驗三:在 test.pp.com 中跨域請求 c.dd.com 服務的 server.js 檔案

本實驗旨在驗證 在某站點跨域請求 js 檔案,而該 js 檔案被設定了不同的 MIME 型別 和 nosniff 時,Chrome 是否會出現 CORB 。

第一步,server.js 檔案 MIME 型別為預設,不設定 nosniffc.dd.com服務程式碼 index.js 中新增如下程式碼,

程式碼片段一,

app.get('/file', function getState(req,res,next){
  res.sendfile(`${__dirname}/public/js/server.js`)
})
複製程式碼

開啟 Chrome test.pp.com 的開發者工具,並在開發者工具中執行如下程式碼,跨域請求 server.js

程式碼片段二,

s = document.createElement('script')
s.src = 'http://c.dd.com:3002/file'
document.head.appendChild(s)
複製程式碼

執行結果如下圖,

請求頭和響應頭
響應結果

如圖所示:真實請求頭被隱藏,Provisional headers are shown;響應頭可見;響應結果可見

第二步,設定 MIME 型別為 json,即 Content-Type: application/json; charset=utf-8,不設定 nosniff。修改 index.js /file 部分程式碼如下,

程式碼片段三,

app.get('/file', function getState(req,res,next){
  res.type('json')
  res.sendfile(`${__dirname}/public/js/server.js`)
})
複製程式碼

再次執行本實驗 程式碼片段二,發現執行結果同第一步(即預設 MIME 型別)完全一樣:真實請求頭被隱藏,Provisional headers are shown;響應頭可見;響應結果可見。

第三步,設定 MIME 型別為 json,即 Content-Type: application/json; charset=utf-8,並新增 'X-Content-Type-Options': 'nosniff' 響應頭。(如果不理解該響應頭的含義,請再次閱讀文頂內容嗅探相關描述) 兩個響應頭加在一起的意思是,明明自己是 js ,卻告訴瀏覽器 MIME 型別是 json,還非不讓瀏覽器使用嗅探技術修正 MIME 型別。 修改 index.js /file部分程式碼如下。

程式碼片段四,

app.get('/file', function getState(req,res,next){
  res.type('json')
  res.set({
    'X-Content-Type-Options': 'nosniff'
  })
  res.sendfile(`${__dirname}/public/js/server.js`)
})
複製程式碼

再次執行本實驗 程式碼片段二,執行結果如下圖,

控制檯提示
請求頭
響應結果

如圖所示: 跨域請求 js 檔案時,如果沒有設定 nosniff,甭管 MIME 型別設定了什麼,都只是請求頭不顯示,響應頭和響應結果正常顯示。如果設定了 nosniff 且 MIME 型別不是 js,則會觸發 CORB 保護,跨域 js 無法正常載入。

因此,如果作為跨域站點 c.dd.com 和 本域站點 test.pp.com 合作時,如果為了 減少 MIME 型別嗅探時間 加上了 nosniff 請求頭,同時,需務必保證設定的 MIME 型別同 js 檔案一致!否則 本域站點 無法拿到 跨域站點 的 js 資源資料!

----------------- 關於 CORB , Chrome 表現和行為驗證結束 -------------------

原文地址:www.yaoyanhuo.com/blog/corb

參考內容

作者部落格主頁:www.yaoyanhuo.com

相關文章