Web頁面子資源完整性校驗詳細指南

dreamapplehappy發表於2021-09-26

時間過得好快,距離上一篇文章動手寫一個簡單的編譯器:在JavaScript中使用Swift的尾閉包語法釋出已經過去快半年了,這半年時間也一直在想著按時更新文章;但是因為工作和生活的瑣事,沒有能夠堅持下來。有點慚愧,感覺之前年初立下的計劃快要實現不了了。希望接下來的這一段時間能夠堅持更新文章吧。

這次要跟大家分享的是關於Subresource Integrity(子資源完整性)的內容。如果平時對Web安全關注不是很多的話,可能沒怎麼聽過這個術語。不知道也沒關係,接下來我會跟大家一起來研究討論一下這個內容,相信在看完這篇文章之後,你能夠深入的理解什麼是SRI,為什麼要使用SRI,以及在有這方面需求的情況下如何在專案中實踐使用SRI

SRI是什麼,以及解決了什麼問題

SRISubresource Integrity的簡寫,表示的是子資源的完整性。比如對於我們在頁面中通過linkscript標籤引入的樣式檔案或者引入使用的第三方庫就是頁面的的子資源。比如像下面這樣:

<link rel="stylesheet" 
      href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css"  
      crossorigin="anonymous">

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js" 
        crossorigin="anonymous"></script>

一般情況下,為了提高網頁的響應速度以及效能,我們通常會把這些子資源放到CDN上。對於大的網際網路公司來說,一般會有自己的雲服務,也基本會有自己的CDN服務。但是對於小公司來說,一般會使用雲服務廠商提供的CDN功能。這裡就會有一個問題,如果我們託管在雲服務廠商的CDN上的資源萬一被篡改了,那麼就會對我們的業務產生一些影響。雖然這種事情一般情況下不會發生,但是如果我們的業務對安全要求很高的話,那麼還是要對這種情況做好防範處理。

SRI 就是應對這個問題的一個解決方案。那麼具體是通過什麼方式來解決的呢?首先對於一個檔案,我們如何知道這個檔案的內容有沒有被篡改呢?我們可以對這個檔案進行一個雜湊計算然後通過base64編碼生成一段跟檔案內容關聯的唯一的字串。如果檔案的內容發生了變化,那麼通過相同的方式生成的字串,跟原來的檔案生成的字串是不一樣的。這樣我們就知道檔案被篡改了。關於這一點如果大家對區塊鏈有了解的話,應該比較容易理解的。

對於每一個引入的第三方資源,我們只需要在對應的標籤上加上integrity屬性,integrity屬性的值是一個字串,形式如下面這樣:

<script src="https://example.com/example-framework.js"
        integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
        crossorigin="anonymous"></script>

其中sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC就是integrity的值,這個字串以sha384開頭,表示的是對應安全雜湊演算法的名稱,還有sha256sha512;接著是一個短橫線-,用來分割演算法名稱和後面通過這個演算法生成的base64編碼的值;最後的oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC表示的就是對應的檔案經過計算後產生的字串。

當瀏覽器下載了帶有integrity屬性的子資源的時候,不會立刻執行裡面的程式碼;或者應用裡面的樣式。瀏覽器會首先根據integrity屬性值中指定的相應演算法以及下載的檔案的內容計算一下這個檔案的雜湊值是否跟標籤中的那個值一樣,只有兩者一樣的情況下才會應用對應的樣式或者執行相應的程式碼。如果兩者不一樣,那麼瀏覽器就會拒絕執行對應的程式碼,以及拒絕應用對應的樣式。也會在控制檯報錯,提醒我們當前下載的子資源存在問題。

這樣我們就通過SRI這種方法保證了我們的頁面不會使用從CDN上下載的被篡改了內容的資源。保證了我們的頁面的安全。

如何使用SRI

上面簡單介紹了SRI的作用,那麼具體怎麼實踐呢?下面我們來一起實踐一下如何使用SRI

首先我們隨便建立一個index.html,然後在裡面新增如下內容:

<script src="http://localhost:3000/test.js"
        integrity="sha384-yGduQba2SOt4PhcoqN6zsgbwhbpK8ZBguLWCSdnSRc6zY/MmfJEmBguDBXJpvXFg"
        crossorigin="anonymous"></script>

然後建立一個test.js檔案,裡面內容如下:

document.write("Hello World!");

然後在本地使用Node.jsexpress框架或者其他的工具,讓test.js能夠在本地通過http://localhost:3000/test.js訪問。

對於上面script標籤的integrity屬性,我們可以通過如下的命令,通過openssl工具獲取對應的sha384演算法生成的字串:

cat test.js | openssl dgst -sha384 -binary | openssl base64 -A

如果是Windows環境的話,需要使用另外的方式獲取對應的字串。

然後在瀏覽器中開啟index.html,你會看到頁面上展示:Hello World!。如果我們這個時候把test.js的內容更改一下,在原來的基礎上,把Hello World!後面的感嘆號去掉,如下所示:

document.write("Hello World");

那麼這個時候頁面就是空白的,不再展示Hello World。對應的控制檯也會報錯,不過不同的瀏覽器報錯資訊不一樣:

  • Chrome報錯如下:

    Chrome瀏覽器

  • Firefox報錯如下:

    Firefox瀏覽器

  • Safari報錯如下:

    Safari瀏覽器

總之都會提醒你,當前下載的子資源通過計算後的雜湊字串,跟標籤上的不一致,瀏覽器拒絕執行對應的程式碼

這裡還有一些需要注意的地方,如果我們的test.js資源跟我們的index.html是不同的源,那麼還需要在標籤上新增crossorigin="anonymous",表明這個資源的請求是需要跨源資源共享的。不然瀏覽器會報錯如下

不支援跨域資源共享的錯誤展示

如果對跨源資源共享還不是很明白的同學可以參考Cross-Origin Resource Sharing

當然對應的服務端也需要設定對應的響應頭部:Access-Control-Allow-Origin: *,如果是使用express的話,可以使用cors來簡單的設定一下。具體如下所示:

// ...
app.use(
    cors({
        origin: "*",
    })
);
// ...

如何在框架中使用SRI

  • 對於Vue專案來說,通過使用Vue CLI我們可以很簡單的就使用這個功能。通過在vue.config.js中增加一個配置:integrity: true,我們就可以在構建的時候後看到,打包後的index.html中引入的資源都是帶有integrity屬性的,如下面所示:
<!-- ... -->
<link href="/css/app.fb0c6e1c.css" rel="stylesheet"
          integrity="sha384-1Ekc46o2fTK9DVGas4xXelFNSBIzgXeLlQlipQEqYUDHkR32K9dbpIkPwq+JK6cl">
<!-- ... -->
<script src="/js/chunk-vendors.0691b6c2.js"
        integrity="sha384-j7EDAmdSMZbkzJnbdSJdteOHi77fyFw7j6JeGYAf4O20/zAyQq1nJ91iweLs6NDd"></script>
<script src="/js/app.290d19ae.js"
        integrity="sha384-S3skbo1aIjA4WCmQH6ltlpwMgTXWrakI5+aloQEnNKpEKRfbNyy1eq6SrV88LGOh"></script>
<!-- ... -->
  • 對於其他框架來說,如果打包工具使用的是Webpack的話,可以直接使用對應的外掛webpack-subresource-integrity,相關的安裝和使用說明可以參考這裡

關於Integrity的一些細節

在實際的使用過程中,還有很多細節需要注意的,下面給大家再深入的介紹一下。

  • 目前使用計算資原始檔雜湊值的演算法有sha256sha384sha512,這些都是屬於SHA-2的安全雜湊演算法
  • 目前已經不推薦使用MD5SHA-1的計算雜湊值的演算法
  • 首先Integrity的值可以存在多個,每個值之間使用空格分隔開

    • 如果多個值分別使用的是不同的安全雜湊演算法,比如如下所示:
    <script src="http://localhost:3000/test.js"
            crossorigin="anonymous"
            integrity="
            sha256-LsK9lSOT7mZ9iEbLTm9cwaKTfuBdypNn2ID1Z9g7ZPM=
            sha384-yGduQba2SOt4PhcoqN6zsgbwhbpK8ZBguLWCSdnSRc6zY/MmfJEmBguDBXJpvXFg
            sha512-2qg2xR+0XgpsowJp3VCqWFgQalU9xPbqNTV0fdM9sV9ltHSSAcHni2Oo0Woo6aj860KvFu8S1Rdwb8oxJlMJ2Q==
    "></script>

    那麼這個時候瀏覽器是根據那個安全雜湊演算法來進行處理的呢?還是說只要有一個匹配就可以了呢?

    答案是:瀏覽器首先會選擇安全性最高的那個計算方式,如果是上面這個例子的話,瀏覽器會選擇sha512這種計算雜湊值的演算法。因為sha512的安全性大於sha384sha384的安全性大於sha256,然後會忽略掉其餘通過其他方式計算出的雜湊值。這個時候需要注意的是,如果瀏覽器根據sha512計算出來的雜湊字串跟提供的不一樣的話,那麼不管sha384或者sha256提供的雜湊值是否正確,瀏覽器都會認為這個資源計算出來的雜湊值跟提供的雜湊值不一樣。所以不會執行對應的程式碼。

    • 如果多個值分別使用的是相同的安全雜湊演算法,比如如下所示:
    <script src="http://localhost:3000/test.js"
            crossorigin="anonymous"
            integrity="
            sha384-yGduQba2SOt4PhcoqN6zsgbwhbpK8ZBguLWCSdnSRc6zY/MmfJEmBguDBXJpvXFg
            sha384-c+xXeW2CdZ1OuDKSrMpABg4MrVFWi3N5VKDC6CTgSRRnPr0dgprowjuFPomHgXlI
            sha384-E6ULLMoeKAMASZMjQ00AvU+3GzK8HPRhL/bM+P4JdcHLbNqGzU14K9ufSPJCnuex
    "></script>

    那麼這個時候只要有一個值跟瀏覽器計算的結果是一樣的,那麼這個資源就可以被認為是沒有被篡改的;資源的內容是可以被執行的。

  • Integrity屬性暫時只支援linkscript標籤,以後會支援更多的關於子資源的標籤,比如:audioembediframeimg

總結

關於Web頁面子資源完整性校驗的分享到這裡也就算結束了,相信如果大家仔細看過一遍的話應該都會有一些收穫的。如果大家看完後有什麼建議和反饋都可以在這裡留言,或者在文章底部留言。

如果大家想自己快速實踐一下的話,可以參考sri-demo這個專案,文章中的部分例子可以在這個專案中進行實踐。當然你也可以自己寫一些例子去實踐。畢竟自己親身實踐一下會把相應的知識記得更牢固。

也歡迎大家關注我的公眾號關山不難越,如果你覺得這篇文章寫的不錯,或者對你有所幫助,那就點贊分享一下吧~

招賢納士

大搜車,成立於2012年,正在為車商的日常經營提供資料分析、營銷管理、金融及交易服務。我們作為國內先進的汽車新零售平臺,從誕生初期就期待用網際網路+智慧化思維重新定義這個行業,通過連結、賦能和引領產業鏈上下游,打造汽車全流通生態,推進汽車產業網際網路發展。

我們是屬於大搜車公司下面的金融服務事業部,因部門業務的快速發展;需要優秀前端工程師加入我們,期待優秀的你能夠和我們一起做一些推動汽車行業發展的有意義的事情。大家相互成就,一起為美好的明天奮鬥。

更多關於公司的資訊可以瀏覽大搜車,關於職位薪資福利相關的可以參考職位資訊,如果你很感興趣的話,可以把你的簡歷傳送到dreamapplehappy@gmail.com,或者加我的微信,備註搜車內推,如果簡歷可以的話,我會幫你直接內推給相關的負責人。並且能夠及時幫你瞭解內推的進度。

參考:

相關文章