大家好,我是 ConardLi,我在之前曾經(jīng)為大家分析過瀏覽器的策略:
《HTTP 緩存別再亂用了!推薦一個緩存設置的最佳姿勢!》
好多同學看了這篇文章后表示看的云里霧里的,其實這些策略里面都提到了一個漏洞:Spectre 漏洞,這個漏洞究竟有啥魔力,讓瀏覽器頻繁的為它更新策略呢,今天我就來給大家講解一下。
Spectre
如果一個漏洞很難構造,就算他能夠造成再大的危害,可能也不會引起瀏覽器這么大的重視,那么我們今天的主角 Spectre ,是又容易構造,而且造成的危害也很大的,利用 Spectre ,你可以:
通過幾行 JavaScript ,就可以讀取到電腦/手機上的所有數(shù)據(jù),瀏覽器中的網(wǎng)頁可以讀取你所有的密碼,知道其他程序在干什么,這甚至不需要你寫出來的程序是有漏洞的,因為這是一個計算機硬件層面上的漏洞。
想要理解 Spectre ,我們需要下面三個方面的知識:
- 理解什么是旁路攻擊
- 理解內存的工作方式
- 理解計算機的預測執(zhí)行
其實都是一些非常基礎的計算機知識,大家可能學校里都學到過的,那么 Spectre 則巧妙利用了上面三個原理,下面我們來挨個看一下。
大家可以完全不用擔心,我會用最簡單的方式給大家講明白,先把這些知識拆解一下,最后組合起來其實是很容易理解的。
內存的工作方式
首先,我們的電腦是由很多零部件構成的:
- 存儲:內存、硬盤等等
- CPU
- 輸入輸出設備:鍵盤鼠標等
我們的計算機運行的時候呢,從存儲設備加載程序進入 CPU,CPU 負責處理進行大量運算,這些運算需要內存的數(shù)據(jù)進行多次讀取。然后把結果輸出到我們的顯示器等輸出設備里面,這大概是是一個計算機簡單的工作原理。
下面我們把關注點放到 CPU 和內存上面,內存里存放著你正在運行的很多程序,包括系統(tǒng)、用戶數(shù)據(jù)等等、同時也存儲了 CPU 運算的中間結果。
要存儲這么多信息,需要一個規(guī)范化的存儲方式,我們可以把內存想像成一堆排列好的小的內存塊,每個內存塊里保存著一位信息。
另外,內存是有很多層的,CPU 去里面讀一個數(shù)據(jù)是很慢的,所以我們又在 CPU 和 內存中建立了幾級緩存,當我們取到一個被緩存過的數(shù)據(jù)時,速度會快一點。那當訪問一個沒被緩存過的數(shù)據(jù)時,數(shù)據(jù)會在緩存內存里創(chuàng)建一個副本,下次再訪問到它就會很快。
這就是內存大概的工作原理,當然這個過程簡化了很多,我們在這里只需要簡單理解即可。
旁路攻擊
那么啥是旁路(side-channel)呢?
我們可以簡單這樣理解:假如在你的程序正常的通訊通道之外,產生了一種其他的特征,這些特征反映了你不想產生的信息,這個信息被人拿到了,你就泄密了。這個邊緣特征產生的信息通道,就叫旁路。
比如你的內存在運算的時候,產生了一個電波,這個電波反映了內存中的內容的,有人用特定的手段收集到這個電波,這就產生了一個旁路了。基于旁路的攻擊,就稱為旁路攻擊。
常見的旁路還有:時延,異常,能耗,電磁,噪聲,可見光,錯誤消息,頻率,等等,反正你運行總是有邊緣特征的,一不小心這個邊緣特征就成了泄密的機會。
我們來舉個基于時延來進行旁路攻擊的例子:
假設我們想讓電腦驗證一下密碼,比如我們的密碼是 ConardLi。
下面我們從攻擊者的角度來猜一下,密碼是啥,我們從一個字母開始猜:
- 密碼是 A,計算機 1ms 后告訴我:不對!
- 密碼是 B,計算機 1ms 后告訴我:不對!
- 密碼是 C,計算機 1.1ms 后告訴我:不對!
有沒有發(fā)現(xiàn)啥問題?我們第一個字母猜對了,但是計算機告訴我們密碼錯誤的時間增加了 0.1ms?
因為這次,計算機發(fā)現(xiàn)第一位匹配后,需要驗證第二位是否匹配,所以會多花費一些時間。是不是很巧妙!
我們可以以同樣的方式,再繼續(xù)驗證 Ca、Cb、... Co,最終猜測出我們的密碼。
這時我們的猜測時間和密碼長度是線性關系,我們可以再 O(n) 的時間復雜度內猜出密碼。如果直接爆破,我們至少需要進行 52 的 8 次方次計算!
這就是旁路攻擊,這該死的魅力!
CPU的預測執(zhí)行
上面我們提到,當CPU運行的時候,會頻繁的從內存中調取信息。但是讀取內存很慢,CPU 為此要花費很長的時間空閑,只為了等待內存的數(shù)據(jù)。這顯然不是個很好的方案。
所以,人們想,是不是 CPU 可以推測一下需要執(zhí)行的命令呢?
假設我們有下面這樣的代碼,根據(jù)內存中的某個數(shù)據(jù)判斷執(zhí)行不同的語句:
if(Menory === 0){ // 進行第一步計算 // 進行第二步計算 // 進行第三步計算 }
這里有兩種可能,Menory 是 0 或者不是 0 。
這時 CPU 等待內存數(shù)據(jù)時就會預測,假設讀取內存返回 0,CPU 可以不等待內存返回,直接搶跑:跳過 if 判斷直接執(zhí)行里面的計算命令。
那么如果內存真的返回 0 ,CPU 已經(jīng)成功超前運行,CPU 可以繼續(xù)執(zhí)行后面的命令。但是假如內存沒有返回 0 ,CPU 就會回滾之前執(zhí)行的結果。
所以,CPU 執(zhí)行需要非常小心,不能直接覆蓋寄存器的值,從而真的改變程序的狀態(tài),一旦發(fā)現(xiàn)預測失敗就立刻回滾改動。
攻擊的原理
前面,我們已經(jīng)掌握了這個漏洞利用到的所有因素,下面我們來看看它具體是咋回事。
假設下面是我們的緩存,讀取它很慢。系統(tǒng)內核將它進行分塊,分配給不同的程序,如果考慮云計算的話,可能分配給不同的虛擬機。
不同程序可能分配到的內存塊是相鄰的,我們繼續(xù)用之前的文章 《HTTP 緩存別再亂用了!推薦一個緩存設置的最佳姿勢!》中的例子:
紅色的內存塊中存儲著我們受害者的數(shù)據(jù),比如受害者的某個密碼:
操作系統(tǒng)會試圖確保一個程序無法訪問屬于其他程序的內存塊,不同程序的內存塊會被隔離開。
所以其他程序無法直接讀取 “受害者”(紅色區(qū)域)的數(shù)據(jù):
加入我們試圖直接訪問紅色區(qū)域肯定是讀不到的 ,但是緩存中可能已經(jīng)存在一些數(shù)據(jù),下面我們可以試著用高速緩存來搞點事情。
我們在紫色的內存塊放一個數(shù)組 A,這塊內存屬于我們的程序,可以合法訪問,但是它很小,只有兩位。
但是我們不滿足于讀取數(shù)組 A 中的兩個元素,我們試圖超出 A 的范圍(下標越界),訪問 A 數(shù)組的第 X 位。但是 X 可能遠遠超出 A 數(shù)組的長度。
通常情況下, CPU 會阻止這一操作,拋出一個錯誤:“非法操作”,然后操作會被強制結束 ,然而我們可以再試圖觀測這個過程,我們看看是怎么做到的。
我們在我們允許訪問的內存范圍內再次新建一個區(qū)域,可以叫工具箱。
我們特別要求 CPU 對這段數(shù)據(jù)不要拷貝到緩存,只保留于內存,這是一段連續(xù)的內存區(qū)域。
假設我們執(zhí)行的指令長這樣,首先有個 if 判斷語句:
if(name === 'code秘密花園'){ // ... }
一般來講,CPU 執(zhí)行會先無視這個判斷,因為它需要等待內存返回 name 的值是不是等于 code秘密花園,因為有預測執(zhí)行這樣的技術,if 語句中的東西會被預先執(zhí)行。
if(name === 'code秘密花園'){ access Tools[A[x]] }
我們嘗試讀取 Tools 的第(A的第X元素)個元素。假如我們讀到的這個受害者內存中包含 3:
這是我們不應該讀取到的,但是我們可以通過預測執(zhí)行做下面的事情:
CPU 執(zhí)行了這個不應該被執(zhí)行的命令后,CPU 認為它需要看一下 A[X] 的值是什么,這時 CPU 并未檢查 A[X] 是否已經(jīng)下標越界,因為 CPU 認為之后內核總會驗證下標是否越界,如果越界就強制結束程序。
于是,預測執(zhí)行就直接查詢了 A[X] 的值,然后發(fā)現(xiàn) A[X] = 3,也就是:
Tools[A[x]] = Tools[3]
也就是我們實際內存中 Tools 存儲的第四個元素 a,下面重點來了:
CPU 訪問到 a 后,將 a(即Tools[3]) 放入了高速緩存!
最后一步,就是遍歷 Tool 中的每一個元素,我們發(fā)現(xiàn)訪問前幾個元素都有點慢,直到訪問到第 3 個突然很快!因為第 3 個元素 a 在緩存中存儲了一份!
當預測執(zhí)行發(fā)現(xiàn)錯誤的時候,它就會回滾寄存器的變化,但是不會回滾高速緩存!
信息就這樣的被泄漏了,因為訪問第 3 個元素所需時間比其他要短!這也就是基于時間的旁路。
于是,我們知道 “受害者” 在內存的這個位置有個 3。
后面,我們可以把 Tools 這篇區(qū)域搞得更大,你就可以猜出其他更多的數(shù)據(jù)!當然,這就是實際去攻擊需要考慮的失去了~
給Web帶來的影響
上面的原理我們已經(jīng)分析清楚了,實際上使用 JavaScript 實現(xiàn)這個攻擊非常容易,在 JavaScript 里幾乎所有的邊界檢查都可以被繞過,從而實現(xiàn)任意內存邊界讀取。我們可以看看下面這段代碼:
if(index < array.length){ index = array[index | 0]; index = (((index * TABLE_STRIDE) | 0) & (TABLE_BYTES - 1)) | 0; localJunk ^= probeTable[index | 0] | 0; }
來自不同站點的多個頁面最終可能會在瀏覽器中共享一個進程。當一個人使用 window.open、 或 或 iframe 打開另一個頁面時,可能會發(fā)生問題,如果一個網(wǎng)站包含特定用戶的敏感數(shù)據(jù),則另一個網(wǎng)站可能會利用這樣的漏洞來讀取該用戶的數(shù)據(jù)。
上面只是舉了一個簡單的例子,其實實際的攻擊面要比這個廣泛的多,為此瀏覽器出了很多的安全策略來解決這個問題,下面我們來看看:
瀏覽器策略
緩存推薦設置
上一篇文章講的就是這個 HTTP 緩存別再亂用了!推薦一個緩存設置的最佳姿勢!
- 為了防止中間層緩存,建議設置:Cache-Control: private
- 建議設置適當?shù)亩壘彺?key:如果我們請求的響應是跟請求的 Cookie 相關的,建議設置:Vary: Cookie
這下應該更明白為要這倆緩存配置了吧,瀏覽器沒有權利把緩存干掉,它只能做到最大程度的收緊緩存的寬松程度,增加攻擊的難度。
禁用高分辨率計時器
要利用 Spectre,攻擊者需要精確測量從內存中讀取某個值所需的時間。所以需要一個可靠且準確的計時器。
瀏覽器提供的一個 performance.now() API ,時間精度可以精確到 5 微秒。作為一種緩解措施,所有主要瀏覽器都降低了 performance.now() 的分辨率,這可以提高攻擊的難度。
獲得高分辨率計時器的另一種方法是使用 SharedArrayBuffer。web worker 使用 Buffer 來增加計數(shù)器。主線程可以使用這個計數(shù)器來實現(xiàn)計時器。瀏覽器就是因為這個原因禁用了 SharedArrayBuffer。
rel="noopener"
瀏覽器 Context Group 是一組共享相同上下文的 tab、window 或 iframe 。例如,如果網(wǎng)站(https://a.example)打開彈出窗口(https://b.example),則打開器窗口和彈出窗口共享相同的瀏覽上下文,并且它們可以通過 DOM API 相互訪問,例如 window.opener。
所以瀏覽器推薦大家在打開不信任的外部頁面時指定 rel="noopener" 。
跨源開放者策略(COOP)
利用 Spectre ,攻擊者可以讀取到在統(tǒng)一瀏覽器下任意 Context Group 下的資源。
COOP:跨源開放者政策,對應的 HTTP Header 是 Cross-Origin-Opener-Policy。
通過將 COOP 設置為 Cross-Origin-Opener-Policy: same-origin,可以把從該網(wǎng)站打開的其他不同源的窗口隔離在不同的瀏覽器 Context Group,這樣就創(chuàng)建的資源的隔離環(huán)境。
跨源嵌入程序政策(COEP)
COEP:跨源嵌入程序政策,對應的 HTTP Header 是 Cross-Origin-Embedder-Policy。
啟用 Cross-Origin-Embedder-Policy: require-corp,你可以讓你的站點僅加載明確標記為可共享的跨域資源,或者是同域資源。
跨域讀取阻止(CORB)
即使所有不同源的頁面都處于自己單獨的進程中,頁面仍然可以合法的請求一些跨站的資源,例如圖片和 JavaScript 腳本,有些惡意網(wǎng)頁可能通過元素來加載包含敏感數(shù)據(jù)的 JSON 文件。
如果沒有 站點隔離 ,則 JSON 文件的內容會保存到渲染器進程的內存中,此時,渲染器會注意到它不是有效的圖像格式,并且不會渲染圖像。但是,攻擊者隨后可以利用 Spectre 之類的漏洞來潛在地讀取該內存塊。
跨域讀取阻止(CORB)可以根據(jù)其 MIME 類型防止 balance 內容進入渲染器進程內存中。
最后
瀏覽器做了這么多的策略,其實只能說可以在一定程度上緩解這個漏洞,實際上并不能從根源上消除,因為本質上 Spectre 還是一個硬件層面上的漏洞、提升漏洞的攻擊成本。
這個漏洞本身也很難解,無論是預測執(zhí)行還是緩存,做了限制就代表性能會大大降低,所以硬件層面上也一直沒有解決這個問題。
原文地址:https://mp.weixin.qq.com/s?__biz=Mzk0MDMwMzQyOA==&mid=2247492933&idx=1&sn=fc5af2da8cd38c8864991758d7b5fc54&chksm=c2e1106ef59699783d56b329d81c86a53bf66a37a3618132d2865a7bf0643d31256b7a9a298d&mpshare=1&scene=23&srcid=0301uINOUW8vt4BV1Uw11tje&sharer_sharetime=1646113228965&sharer_shareid=9603544ecd5d7f3dc66603ae089636f4#rd