30分鐘理解CORB是什麼

NO IMAGE

寫在前面

前些日子在調試 bug 的時候,偶然發現這麼一個警告:

Cross-Origin Read Blocking (CORB) blocked cross-origin response www.chromium.org/ with MIME type text/html. See www.chromestatus.com/feature/562… for more details.

我當前的 chrome 版本是 v68,如果是 v66 或更低版本可能提示的警告信息略有不同。印象中只對 CORS 比較熟悉,CORB 是個什麼鬼?好奇心迫使我想要了解一下它到底是什麼,於是暫時把手頭工作放下查了一些資料並花時間彙總了一下,就有了這篇文章。

再介紹 CORB 是什麼以及有什麼用之前,需要先了解一些背景知識以做鋪墊,下面進入正文。

旁路攻擊(side-channel attacks)

首先需要了解的是旁路攻擊這個術語,關於術語本身的解釋,可以去維基百科搜索。簡單講的話,就是從軟件系統的物理實現層獲取信息進行攻擊的手段,軟件系統在正常運行時,產生了一些邊緣特徵,這些特徵可以體現一些隱私信息。

這麼說可能略顯抽象,就拿後文視頻鏈接中列舉的例子說明一下,假設小 A 的賬戶密碼是 gyease,小 B 想破解小 A 的密碼,他可以這麼做:

  • 首先他可以先輸入 aaaaaa,之後記錄一下從點擊登錄按鈕到錯誤提示的間隔時間(雖然很短,假設有工具可以記錄)
  • 之後再輸入 baaaaa,同樣記錄時間
  • 重複以上過程直到 gaaaaa,會發現從點擊登錄按鈕到錯誤提示的間隔時間稍微變長了一些
  • 之後小 B 即知道小 A 的密碼第一位是 g,之後重複以上步驟即可破解小 A 的密碼。

當然這裡的例子很蠢,而且也過於理想化,但足夠說明問題。反應快的讀者可能馬上就會知道為什麼在觀察 ‘gaaaaa’ 的測量結果後小 B 就會知道小 A 首位密碼,這是因為執行校驗密碼是否正確的代碼是需要時間的,因此在理想條件下,首位錯誤和首位正確第二位錯誤的反饋結果必然是後者時間略長。

這就是一個比較典型的旁路攻擊類型,專業的名稱叫做計時攻擊(timing attack),有興趣的可以上網搜索瞭解詳情。

預執行(speculation execution)

之後再來了解預執行這個概念,電腦之所以可以執行我們所編寫的代碼,其背後是由若干硬件協同工作的結果。其中兩個比較重要的,一個是內存,一個是CPU。眾所周知,CPU執行計算的速度肯定是遠大於它讀取內存的速度的,這樣的結果就是,CPU在對內存讀取某些數據的時候,會閒置,這樣變造成了浪費。為了提高性能,現代基本大部分硬件製造商都引入了預執行這個機制來壓榨CPU的性能。大概的意思如下,比如你寫了一段代碼:

if(somethingTrueOrFalse) {
  // TODO ...
}

邏輯上,這個 if 語句內部的代碼是否執行,取決於 somethingTrueOrFalse 變量,但是注意,這是邏輯上的,CPU在運行這段代碼的時候,可不是這樣子的。它可能會直接跳過判定 somethingTrueOrFalse 是真是假的邏輯,直接執行 if 語句內部的代碼,之後反過來再根據 somethingTrueOrFalse 的取值情況作出反應,如果為真,則保留執行結果,如果為假,則撤銷執行結果。

這裡對於預執行的描述是極度簡化的,不過足夠說明概念了。如果有興趣可以上網搜索相關文章,尤其是預執行策略方面的,我看了一些,沒看完,感覺和AI有的一拼(題外話)。

幽靈和熔斷漏洞(Spectre & Meltdown)

這個漏洞是在今年 1 月份被報道出來的,是硬件系統層面的漏洞。關於這個漏洞本身,網上已經有專業的論文對其進行了詳盡的介紹,有興趣可以自行搜索閱讀,這裡就不展開說了。簡單講,就是結合上文提及的兩個概念的兩種實際攻擊方法。

這裡還需要再說一下 CPU 讀取數據的方式,CPU 除了利用預執行來提供性能,它本身在從內存讀取數據的時候,還會涉及一個緩存的概念。從緩存讀取數據的速度是大於內存的,當 CPU 發現將要讀取的一個數據在緩存中存在時,它會直接從緩存中讀取,這樣同樣可以提高性能,但是緩存很小同時也很昂貴,所以緩存的大小無法與內存相比。同時,每個程序運行時,CPU 為了防止進程間互相保持獨立,它們都擁有屬於自己的某塊內存區域,假設程序 A 存在一條想要直接越界訪問程序 B 內存的指令,這在 CPU 是屬於非法的,它不會執行這條指令,而會選擇拋出異常並終止程序,然後將其相應的內存數據清零。

之後問題就出現了,假設我們有以下代碼:

if (x < arr1.length) {
  y = arr2[arr1[x]]
}

這個例子在參考鏈接的文章中你可能會多次見到,這裡大概解釋一下:

  • arr1 假設是一個比較小的數組,x 是一個我們定義的索引值變量
  • 正常情況下,如果 x 超過 arr1 的長度,程序是要崩潰的,因為它越界了,但是在預執行的前提下,CPU 可能會忽略越界的問題而執行 if 語句內部的代碼
  • arr2 是我們提前聲明的一個用來儲存數據的數組,它儲存於內存的另一個區域,它是連續的,而且我們強制它沒有拷貝至緩存,只保存於內存(這點在視頻中有提及,我這裡強調一下)
  • 之後我們假設 arr1 中的位於 x 索引出的值是 k,那麼在預執行的前提下,y = arr2[arr1[x]]等價於y = arr2[k]
  • 然後由於我們會把 arr2[k] 這個值付給另一個變量 y,這裡其實算是一個訪問值的操作,CPU 後將 arr2[k] 位於內存地址的值轉入緩存中,而其餘元素保留在內存中(因為並未訪問)

之後,只需要遍歷 arr2 這個數組,當發現某個索引上的值的訪問速度遠快於其他索引的訪問速度時,這個索引既是我們從越界內存中“偷”到的值。至此,一次攻擊就完成了,理論上,利用這個漏洞,可以獲取緩存區所有地址的值,其中很有可能包含敏感信息,比如密碼什麼的。

CORB(Cross-Origin Read Blocking)

說了這麼多,終於可以引入正題了。它是什麼呢?引入 chromium 文檔中關於它的定義:

an algorithm by which dubious cross-origin resource loads may be identified and blocked by web browsers before they reach the web page.

瀏覽器在加載可以跨域資源時,在資源載入頁面之前,對其進行識別和攔截的算法。

這裡可能有人會問,這和上面說的一堆又有什麼關係呢?是這樣的,Chrome瀏覽器在處理不同 tab 和不同頁面時,會將為它們劃分不同的進程,而且受制於同源策略的影響,這些頁面之間本應該互不干擾。但是我們知道,同源策略雖然牛逼,但瀏覽器中仍然存在一些不受制於它約束的 api、標籤,比如常見的 img、iframe 和 script等等。諸如以下代碼,不知道看文章的諸位有沒有寫過,反正我是寫過,或者說遇見過:

<img src="https://foo/bar/baz/">

有人可能會問,一個 img 標籤你 src 屬性不填圖片的 uri,你是不是傻。其實不是這樣的,有時候對網站做一些跟蹤和分析時,確實會這麼寫,因為瀏覽器會往https://foo/bar/baz/這個地址發送一個 GET 資源的請求,在服務端我們可以利用這個請求做一些追蹤的邏輯,同理 script 也可以完成需求。但是這麼做的後果就是,雖然 img 幫我們發送了這個請求,但是它卻沒有得到所期望格式的資源,所以這裡實際可以算作一種錯誤或者異常。而一些攻擊者可以利用這一點,比如,在頁面嵌入下面的代碼:

<img src="https://example.com/secret.json">

來加載跨域私密文件,因為 img 不受同源策略的制約,這個請求是可以發出去的,服務器響應返回後,顯然 secret.json 不是一個圖片格式的資源,img 不會顯示它,但是並不代表負責渲染當前頁面的進程的內存中沒有保留關於 secret.json 的數據。因此攻擊者可以利用上文中提及的漏洞,編寫一些代碼來“偷”這些數據,從而造成安全隱患。

而 CORB 的作用就是當瀏覽器嘗試以上面代碼的方式加載跨域資源時,在資源未被加載之前進行攔截,從而提升攻擊者進行幽靈攻擊的成本,這裡之所以是說提升成本還非徹底解決是因為這個漏洞是基於硬件層面的,所以軟件層面只能做有限的修復,有的人可能馬上會說,那 CPU 直接去掉或者用戶放棄使用預處理功能不就好了嗎?理論上是這樣的,但是這將導致預處理帶來的性能紅利瞬間消失,而且 CPU 的架構設計也不是一天兩天就能改的,而且就算改了也沒辦法一下普及。

哪些內容類型受 CORB 保護

當前有三種內容類型受保護,分別是 json、html 和 xml。關於如何針對每種內容類型 CORB 如何對其進行保護,文檔中有詳細的章節進行介紹,這裡就不多說了。我瀏覽了一遍,大體的規則均是對內容格式進行一些有針對性的校驗,以確認它確實是某個內容類型。這個校驗結果最終影響 CORB 的運作方式。

CORB 如何運作

這裡我引用文檔部分章節並做翻譯,關於其中的備註可以直接瀏覽原文檔進行查看。

CORB 會根據如下步驟來確定是否對 response 進行保護(如果響應的內容格式是 json、html 或者 xml):

  • 如果 response 包含 X-Content-Type-Options: nosniff 響應頭部,那麼如果 Content-Type 是以下幾種的話, response 將受 CORB 保護:
    • html mime type
    • xml mime type(除了 image/svg+xml)
    • json mime type
    • text/plain
  • 如果 response 的狀態是 206,那麼如果 Content-Type 是以下幾種的話, response 將受 CORB 保護:
    • html mime type
    • xml mime type(除了 image/svg+xml)
    • json mime type
  • 否則,CORB 將嘗試探測 response 的 body:
    • html mime type,並且探測結果是 html 內容格式,response 受 CORB 保護
    • xml mime type(除了 image/svg+xml), 並且探測結果是 xml 內容格式,response 受 CORB 保護
    • json mime type,並且探測結果是 json 內容格式,response 受 CORB 保護
    • text/plain,並且探測結果是 json、html 或者 xml 內容格式,response 受 CORB 保護
    • 任何以 JSON security prefix 開頭的 response(除了 text/css)受 CORB 保護

這裡值得一提的是,對於探測是必須的,以防攔截了那些依賴被錯誤標記的跨源響應的頁面(比如,圖片資源但是格式卻被標記為 text/html)。如果不進行格式探測,那麼會有16倍以上的 response 被攔截。

CORB 如何攔截一個響應

當一個 response 被 CORB 保護時,它的 body 會被覆蓋為空,同時 headers 也會被移除(當前 Chrome 仍然會保留 Access-Control-* 相關的 headers)。關於 CORB 的工作方式,一定要和 CORS 做區分,因為它要防止這些被攔截的數據進入渲染當前頁面進程的內存中,所以它一定不會被加載並讀取。這不同於 CORS,因為後者會做一些過濾操作,數據雖然不可被加載,但是可能仍然保留在渲染進程的內存中。

對於其他 web 平臺特性的影響

這裡仍然是翻譯部分文檔中的內容,因為本身寫的就很細緻了。

CORB 不會影響以下技術場景:

  • XHR and fetch()

    • CORB 並不會產生顯而易見的影響,因為 XHR 和 fetch() 在響應中已經應用了同源策略(比如:CORB 應該僅會攔截那些因缺少 CORS 而發生跨域 XHR 錯誤的 response)
  • Prefetch

    • CORB 會攔截那些到達跨源渲染進程的 response body,但是不會阻止那些被瀏覽器進程緩存的 response body(然後傳遞到另一個同源渲染進程)。
  • Tracking and reporting

    • 當前存在各種各樣的技術,嘗試對記錄用戶訪問的服務器發送 web 請求,以檢查用戶是否已訪問某些內容。該請求經常使用隱藏的 img 標籤進行發送(我前文提及了),然後服務器以 204 狀態碼或者 html 文檔進行響應。除了 img,還可以使用類似 script、style 和別的可用標籤。
    • CORB 不會對這些技術場景造成影響,因為它們不會依賴於服務器返回響應的內容。這一點同樣使用與其他類似的技術場景和 web 特性,只要它們不關心響應即可,比如:beacons,ping,CSP違規報告 等。
  • Service workers

    • Service workers 可以攔截跨源 requests 並在其內部人為地構建 response(沒有跨源和安全邊界),CORB 不會攔截它們。
    • 當 Service workers 確實緩存了一些跨源的 responses 時,由於這些 responses 對於調用者來講是透明的,因此 CORB 會攔截它們,但是這並不需要對 Service Worker 作出任何改變。
  • Blob and File API

    • 即使沒有 CORB 的話,獲取跨源的 blob URLs 當前也會被攔截。
  • Content scripts and plugins

    • 它們所屬的範圍並不含在 CORB 的職責內 —— CORB 假設已經有某種合適的安全策略或安全機制存在於這些 content scripts 和 plugins 中(比如 Adobe Flash 已經實現了類似 CORB 的機制,通過 crossdomain.xml)。

總結

大概就這麼多,讀到這裡,應該對 CORB 能夠有一個初步的認識和把握了,以及它所需要解決的問題。最後我列舉了我寫這篇文章之前閱讀的文章或者視頻,有些需要自備梯子,有些不要。尤其推薦那個 B 站的視頻,算是講的最生動形象的了。另外 Chrome 的文檔也很詳細,只是有些長,需要耐心慢慢讀完。

以上,如有錯誤,還望指出,大神輕噴。

參考鏈接

相關文章

高級vue組件模式4

高級Vue組件模式3

高級vue組件模式2

高級vue組件模式1