通過幾行 JS 就可以讀取電腦上的所有數(shù)據(jù)?
Spectre 漏洞究竟有啥魔力,讓瀏覽器頻繁的為它更新策略呢,今天我就來給大家講解一下。
Spectre
如果一個漏洞很難構(gòu)造,就算他能夠造成再大的危害,可能也不會引起瀏覽器這么大的重視,那么我們今天的主角 Spectre ,是又容易構(gòu)造,而且造成的危害也很大的,利用 Spectre ,你可以:
通過幾行 JavaScript ,就可以讀取到電腦/手機上的所有數(shù)據(jù),瀏覽器中的網(wǎng)頁可以讀取你所有的密碼,知道其他程序在干什么,這甚至不需要你寫出來的程序是有漏洞的,因為這是一個計算機硬件層面上的漏洞。
想要理解 Spectre ,我們需要下面三個方面的知識:
理解什么是旁路攻擊 理解內(nèi)存的工作方式 理解計算機的預(yù)測執(zhí)行
其實都是一些非常基礎(chǔ)的計算機知識,大家可能學(xué)校里都學(xué)到過的,那么 Spectre 則巧妙利用了上面三個原理,下面我們來挨個看一下。
大家可以完全不用擔(dān)心,我會用最簡單的方式給大家講明白,先把這些知識拆解一下,最后組合起來其實是很容易理解的。
內(nèi)存的工作方式
首先,我們的電腦是由很多零部件構(gòu)成的:
存儲:內(nèi)存、硬盤等等 CPU 輸入輸出設(shè)備:鍵盤鼠標(biāo)等

我們的計算機運行的時候呢,從存儲設(shè)備加載程序進入 CPU,CPU 負(fù)責(zé)處理進行大量運算,這些運算需要內(nèi)存的數(shù)據(jù)進行多次讀取。然后把結(jié)果輸出到我們的顯示器等輸出設(shè)備里面,這大概是是一個計算機簡單的工作原理。

下面我們把關(guān)注點放到 CPU 和內(nèi)存上面,內(nèi)存里存放著你正在運行的很多程序,包括系統(tǒng)、用戶數(shù)據(jù)等等、同時也存儲了 CPU 運算的中間結(jié)果。

要存儲這么多信息,需要一個規(guī)范化的存儲方式,我們可以把內(nèi)存想像成一堆排列好的小的內(nèi)存塊,每個內(nèi)存塊里保存著一位信息。

另外,內(nèi)存是有很多層的,CPU 去里面讀一個數(shù)據(jù)是很慢的,所以我們又在 CPU 和 內(nèi)存中建立了幾級緩存,當(dāng)我們?nèi)〉揭粋€被緩存過的數(shù)據(jù)時,速度會快一點。那當(dāng)訪問一個沒被緩存過的數(shù)據(jù)時,數(shù)據(jù)會在緩存內(nèi)存里創(chuàng)建一個副本,下次再訪問到它就會很快。

這就是內(nèi)存大概的工作原理,當(dāng)然這個過程簡化了很多,我們在這里只需要簡單理解即可。
旁路攻擊
那么啥是旁路(side-channel)呢?
我們可以簡單這樣理解:假如在你的程序正常的通訊通道之外,產(chǎn)生了一種其他的特征,這些特征反映了你不想產(chǎn)生的信息,這個信息被人拿到了,你就泄密了。這個邊緣特征產(chǎn)生的信息通道,就叫旁路。
比如你的內(nèi)存在運算的時候,產(chǎn)生了一個電波,這個電波反映了內(nèi)存中的內(nèi)容的,有人用特定的手段收集到這個電波,這就產(chǎn)生了一個旁路了。基于旁路的攻擊,就稱為旁路攻擊。
常見的旁路還有:時延,異常,能耗,電磁,噪聲,可見光,錯誤消息,頻率,等等,反正你運行總是有邊緣特征的,一不小心這個邊緣特征就成了泄密的機會。
我們來舉個基于時延來進行旁路攻擊的例子:
假設(shè)我們想讓電腦驗證一下密碼,比如我們的密碼是 ConardLi。
下面我們從攻擊者的角度來猜一下,密碼是啥,我們從一個字母開始猜:
密碼是 A,計算機1ms后告訴我:不對!密碼是 B,計算機1ms后告訴我:不對!

密碼是 C,計算機1.1ms后告訴我:不對!
有沒有發(fā)現(xiàn)啥問題?我們第一個字母猜對了,但是計算機告訴我們密碼錯誤的時間增加了 0.1ms?

因為這次,計算機發(fā)現(xiàn)第一位匹配后,需要驗證第二位是否匹配,所以會多花費一些時間。是不是很巧妙!

我們可以以同樣的方式,再繼續(xù)驗證 Ca、Cb、... Co,最終猜測出我們的密碼。
這時我們的猜測時間和密碼長度是線性關(guān)系,我們可以再 O(n) 的時間復(fù)雜度內(nèi)猜出密碼。如果直接爆破,我們至少需要進行 52 的 8 次方次計算!
這就是旁路攻擊,這該死的魅力!

CPU的預(yù)測執(zhí)行
上面我們提到,當(dāng)CPU運行的時候,會頻繁的從內(nèi)存中調(diào)取信息。但是讀取內(nèi)存很慢,CPU 為此要花費很長的時間空閑,只為了等待內(nèi)存的數(shù)據(jù)。這顯然不是個很好的方案。
所以,人們想,是不是 CPU 可以推測一下需要執(zhí)行的命令呢?
假設(shè)我們有下面這樣的代碼,根據(jù)內(nèi)存中的某個數(shù)據(jù)判斷執(zhí)行不同的語句:
if(Menory === 0){
// 進行第一步計算
// 進行第二步計算
// 進行第三步計算
}
這里有兩種可能,Menory 是 0 或者不是 0 。

這時 CPU 等待內(nèi)存數(shù)據(jù)時就會預(yù)測,假設(shè)讀取內(nèi)存返回 0,CPU 可以不等待內(nèi)存返回,直接搶跑:跳過 if 判斷直接執(zhí)行里面的計算命令。
那么如果內(nèi)存真的返回 0 ,CPU 已經(jīng)成功超前運行,CPU 可以繼續(xù)執(zhí)行后面的命令。但是假如內(nèi)存沒有返回 0 ,CPU 就會回滾之前執(zhí)行的結(jié)果。
所以,CPU 執(zhí)行需要非常小心,不能直接覆蓋寄存器的值,從而真的改變程序的狀態(tài),一旦發(fā)現(xiàn)預(yù)測失敗就立刻回滾改動。
攻擊的原理
前面,我們已經(jīng)掌握了這個漏洞利用到的所有因素,下面我們來看看它具體是咋回事。
假設(shè)下面是我們的緩存,讀取它很慢。系統(tǒng)內(nèi)核將它進行分塊,分配給不同的程序,如果考慮云計算的話,可能分配給不同的虛擬機。

不同程序可能分配到的內(nèi)存塊是相鄰的,我們繼續(xù)用之前的文章 HTTP 緩存別再亂用了!推薦一個緩存設(shè)置的最佳姿勢! 中的例子:
紅色的內(nèi)存塊中存儲著我們受害者的數(shù)據(jù),比如受害者的某個密碼:

操作系統(tǒng)會試圖確保一個程序無法訪問屬于其他程序的內(nèi)存塊,不同程序的內(nèi)存塊會被隔離開。
所以其他程序無法直接讀取 “受害者”(紅色區(qū)域)的數(shù)據(jù):

加入我們試圖直接訪問紅色區(qū)域肯定是讀不到的 ,但是緩存中可能已經(jīng)存在一些數(shù)據(jù),下面我們可以試著用高速緩存來搞點事情。

我們在紫色的內(nèi)存塊放一個數(shù)組 A,這塊內(nèi)存屬于我們的程序,可以合法訪問,但是它很小,只有兩位。

但是我們不滿足于讀取數(shù)組 A 中的兩個元素,我們試圖超出 A 的范圍(下標(biāo)越界),訪問 A 數(shù)組的第 X 位。但是 X 可能遠(yuǎn)遠(yuǎn)超出 A 數(shù)組的長度。

通常情況下, CPU 會阻止這一操作,拋出一個錯誤:“非法操作”,然后操作會被強制結(jié)束 ,然而我們可以再試圖觀測這個過程,我們看看是怎么做到的。
我們在我們允許訪問的內(nèi)存范圍內(nèi)再次新建一個區(qū)域,可以叫工具箱。

我們特別要求 CPU 對這段數(shù)據(jù)不要拷貝到緩存,只保留于內(nèi)存,這是一段連續(xù)的內(nèi)存區(qū)域。

假設(shè)我們執(zhí)行的指令長這樣,首先有個 if 判斷語句:
if(name === 'code秘密花園'){
// ...
}
一般來講,CPU 執(zhí)行會先無視這個判斷,因為它需要等待內(nèi)存返回 name 的值是不是等于 code秘密花園,因為有預(yù)測執(zhí)行這樣的技術(shù),if 語句中的東西會被預(yù)先執(zhí)行。
if(name === 'code秘密花園'){
access Tools[A[x]]
}
我們嘗試讀取 Tools 的第(A的第X元素)個元素。假如我們讀到的這個受害者內(nèi)存中包含 3:

這是我們不應(yīng)該讀取到的,但是我們可以通過預(yù)測執(zhí)行做下面的事情:
CPU 執(zhí)行了這個不應(yīng)該被執(zhí)行的命令后,CPU 認(rèn)為它需要看一下 A[X] 的值是什么,這時 CPU 并未檢查 A[X] 是否已經(jīng)下標(biāo)越界,因為 CPU 認(rèn)為之后內(nèi)核總會驗證下標(biāo)是否越界,如果越界就強制結(jié)束程序。
于是,預(yù)測執(zhí)行就直接查詢了 A[X] 的值,然后發(fā)現(xiàn) A[X] = 3,也就是:
Tools[A[x]] = Tools[3]
也就是我們實際內(nèi)存中 Tools 存儲的第四個元素 a,下面重點來了:
CPU 訪問到 a 后,將 a(即Tools[3]) 放入了高速緩存!

最后一步,就是遍歷 Tool 中的每一個元素,我們發(fā)現(xiàn)訪問前幾個元素都有點慢,直到訪問到第 3 個突然很快!因為第 3 個元素 a 在緩存中存儲了一份!
當(dāng)預(yù)測執(zhí)行發(fā)現(xiàn)錯誤的時候,它就會回滾寄存器的變化,但是不會回滾高速緩存!

信息就這樣的被泄漏了,因為訪問第 3 個元素所需時間比其他要短!這也就是基于時間的旁路。
于是,我們知道 “受害者” 在內(nèi)存的這個位置有個 3。
后面,我們可以把 Tools 這篇區(qū)域搞得更大,你就可以猜出其他更多的數(shù)據(jù)!當(dāng)然,這就是實際去攻擊需要考慮的失去了~
給Web帶來的影響
上面的原理我們已經(jīng)分析清楚了,實際上使用 JavaScript 實現(xiàn)這個攻擊非常容易,在 JavaScript 里幾乎所有的邊界檢查都可以被繞過,從而實現(xiàn)任意內(nèi)存邊界讀取。我們可以看看下面這段代碼:
if(index < array.length){
index = array[index | 0];
index = (((index * TABLE_STRIDE) | 0) & (TABLE_BYTES - 1)) | 0;
localJunk ^= probeTable[index | 0] | 0;
}
來自不同站點的多個頁面最終可能會在瀏覽器中共享一個進程。當(dāng)一個人使用 window.open、 或 <a href="..." target="_blank"> 或 iframe 打開另一個頁面時,可能會發(fā)生問題,如果一個網(wǎng)站包含特定用戶的敏感數(shù)據(jù),則另一個網(wǎng)站可能會利用這樣的漏洞來讀取該用戶的數(shù)據(jù)。
上面只是舉了一個簡單的例子,其實實際的攻擊面要比這個廣泛的多,為此瀏覽器出了很多的安全策略來解決這個問題,下面我們來看看:
瀏覽器策略
緩存推薦設(shè)置
上一篇文章講的就是這個 HTTP 緩存別再亂用了!推薦一個緩存設(shè)置的最佳姿勢!
為了防止中間層緩存,建議設(shè)置: Cache-Control: private建議設(shè)置適當(dāng)?shù)亩壘彺? key:如果我們請求的響應(yīng)是跟請求的Cookie相關(guān)的,建議設(shè)置:Vary: Cookie
這下應(yīng)該更明白為要這倆緩存配置了吧,瀏覽器沒有權(quán)利把緩存干掉,它只能做到最大程度的收緊緩存的寬松程度,增加攻擊的難度。
禁用高分辨率計時器
要利用 Spectre,攻擊者需要精確測量從內(nèi)存中讀取某個值所需的時間。所以需要一個可靠且準(zhǔn)確的計時器。
瀏覽器提供的一個 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:跨源開放者政策,對應(yīng)的 HTTP Header 是 Cross-Origin-Opener-Policy。
通過將 COOP 設(shè)置為 Cross-Origin-Opener-Policy: same-origin,可以把從該網(wǎng)站打開的其他不同源的窗口隔離在不同的瀏覽器 Context Group,這樣就創(chuàng)建的資源的隔離環(huán)境。
詳細(xì)的可以看我這篇文章:新的跨域策略:使用COOP、COEP為瀏覽器創(chuàng)建更安全的環(huán)境
跨源嵌入程序政策(COEP)
COEP:跨源嵌入程序政策,對應(yīng)的 HTTP Header 是 Cross-Origin-Embedder-Policy。

啟用 Cross-Origin-Embedder-Policy: require-corp,你可以讓你的站點僅加載明確標(biāo)記為可共享的跨域資源,或者是同域資源。
詳細(xì)的也不多介紹了,其實都在這篇文章里講過了:新的跨域策略:使用COOP、COEP為瀏覽器創(chuàng)建更安全的環(huán)境
跨域讀取阻止(CORB)
即使所有不同源的頁面都處于自己單獨的進程中,頁面仍然可以合法的請求一些跨站的資源,例如圖片和 JavaScript 腳本,有些惡意網(wǎng)頁可能通過 <img> 元素來加載包含敏感數(shù)據(jù)的 JSON 文件。
如果沒有 站點隔離 ,則 JSON 文件的內(nèi)容會保存到渲染器進程的內(nèi)存中,此時,渲染器會注意到它不是有效的圖像格式,并且不會渲染圖像。但是,攻擊者隨后可以利用 Spectre 之類的漏洞來潛在地讀取該內(nèi)存塊。
跨域讀取阻止(CORB)可以根據(jù)其 MIME 類型防止 balance 內(nèi)容進入渲染器進程內(nèi)存中。
詳細(xì)的原理,可以看這篇文章:跨域,不止CORS
參考
https://www.bilibili.com/video/av18144159/ https://zhuanlan.zhihu.com/p/32784852
最后
瀏覽器做了這么多的策略,其實只能說可以在一定程度上緩解這個漏洞,實際上并不能從根源上消除,因為本質(zhì)上 Spectre 還是一個硬件層面上的漏洞、提升漏洞的攻擊成本。
這個漏洞本身也很難解,無論是預(yù)測執(zhí)行還是緩存,做了限制就代表性能會大大降低,所以硬件層面上也一直沒有解決這個問題。
后續(xù)瀏覽器可能還會出更多的策略或者推薦配置,大家可以持續(xù)關(guān)注~
點贊和在看點這里??
