Subresource Integrity 介紹

肖老闆發表於2018-10-10

原文連結:https://imququ.com/post/subresource-integrity.html

這幾天,GitHub 宣佈啟用 SRI 策略,用來減少由「託管在 CDN 的資源被篡改」而引入的 XSS 等風險。很多小夥伴對此表示關注。那麼 SRI 究竟是什麼,如何使用 SRI,它的適用場景和侷限性是什麼?本文逐一解答。

SRI 是什麼?
SRI 是 Subresource Integrity 的縮寫,一般按照字面意義翻譯為:子資源完整性(草案),它也是由 Web 應用安全工作組(Web Application Security Working Group)釋出。草案地址見這裡。

Web 效能優化中很重要的一點是讓請求提前結束,讓可快取的資源走 CDN 是最通用的做法。CDN 服務提供商通過分佈在各地的節點,讓使用者從最近的節點載入內容,大幅提升速度。但是 CDN 的安全性一直是一個風險點:對於網站主來說,讓請求從第三方伺服器經過,由第三方響應,安全方面肯定不如自己伺服器可控。

我們知道 CSP(Content Security Policy) 的外鏈白名單機制可以在現代瀏覽器下減小 XSS 風險。但針對 CDN 內容被篡改而導致的 XSS,CSP 並不能防範,因為網站所使用的 CDN 域名,肯定在 CSP 白名單之中。這時候,SRI 就應運而生了。它通過對資源進行摘要簽名機制,來保證外鏈資源的完整性(不被篡改)。

目前支援 SRI 的瀏覽器有 Chrome 45+ 和 FireFox 43+。IE Edge 表示考慮中,將其列入了需求池,接受使用者投票。CanIUse 網站目前尚未提供 SRI 支援度資料,但是已經有人提了 Issue,後續應該會加上。

SRI 怎麼使用?
首先,我們通過 GitHub 的頁面原始碼看一下 SRI 如何使用:

<link crossorigin="anonymous" href="https://assets-cdn.github.com/assets/github-aef3088517c60128e10c5cce8d392985504018745a58a13691f1a278951852bb.css" integrity="sha256-rvMIhRfGASjhDFzOjTkphVBAGHRaWKE2kfGieJUYUrs=" media="all" rel="stylesheet" />

<script crossorigin="anonymous" integrity="sha256-+Ec97OckLaaiDVIxNjSIGzl1xSzrqh5sOBV8DyYYVpE=" src="https://assets-cdn.github.com/assets/frameworks-f8473dece7242da6a20d52313634881b3975c52cebaa1e6c38157c0f26185691.js"></script>

啟用 SRI 策略後,瀏覽器會對資源進行 CORS 校驗,這就要求被請求的資源必須同域,或者配置了 Access-Control-Allow-Origin 響應頭。這個細節需要大家注意。

要使用 SRI,只需要在原有的標籤裡增加 integrity 屬性即可,這個屬性的簽名演算法支援 sha256、sha384 和 sha512,簽名演算法和摘要簽名內容用 - 分隔。例如,要引入以下這個資源,並啟用 SRI 策略:

https://example.com/static/js/other/zepto.js
可以使用 sha256 演算法生成摘要簽名,並進行 Base64 編碼:

curl https://example.com/static/js/other/zepto.js | openssl dgst -sha256 -binary | openssl enc -base64 -A

b/TAR5GfYbbQ3gWQCA3fxESsvgU4AbP4rZ+qu1d9CuQ=

最終的程式碼如下:

<script crossorigin="anonymous" integrity="sha256-b/TAR5GfYbbQ3gWQCA3fxESsvgU4AbP4rZ+qu1d9CuQ=" src="https://example.com/static/js/other/zepto.js"></script>

瀏覽器拿到資源內容之後,會使用 integrity 所指定的簽名演算法計算結果,並與 integrity 提供的摘要簽名比對,如果二者不一致,就不會執行這個資源。

動態載入的資源使用 SRI 也是類似的,需要指定 crossOrigin(注意大小寫)和 integrity 屬性。例如:

var s = document.createElement('script');
s.crossOrigin = 'anonymous';
s.integrity = 'sha256-b/TAR5GfYbbQ3gWQCA3fxESsvgU4AbP4rZ+qu1d9CuQ=';
s.src = 'https://example.com/static/js/other/zepto.js';
document.head.appendChild(s);

在載入 CSS 資源啟用 SRI,使用 Fetch Api 時啟用 SRI,都是類似的,這裡略過。

用途和侷限性
可以看到,SRI 的作用是保證頁面引入第三方資源的完整性。在第三方 CDN 服務被入侵或回源被運營商劫持、檔案內容被加入惡意程式碼時,網站如果啟用了 SRI 策略,那麼在支援 SRI 的瀏覽器下,被篡改的檔案無法執行。

我們知道,HTTPS 也可以確保傳輸過程中的資料完整性,但是對於 CDN 伺服器被入侵或 HTTP 回源被劫持造成的檔案篡改,HTTPS 無濟於事,這時 SRI 就可以派上用場,作為補充。

但是,如果網站以及 CDN 都沒有使用 HTTPS,運營商可以將外鏈資源及 HTML 頁面本身一起劫持,並將資源內容和頁面中的摘要簽名同步修改,讓 SRI 徹底失效。

大部分運營商劫持,都是為了插入廣告程式碼。如果網站啟用了 SRI,會導致篡改後的整個檔案無法執行,這很可能讓頁面變得完全不可用。所以 SRI 給我的感覺是:寧為玉碎不為瓦全。

當然,為了提高可用性,也可以增加 fallback 處理。例如,在 CDN 資源被篡改而無法載入時,轉為使用本站資源:

<script crossorigin="anonymous" integrity="sha256-xxxx" src="http://cdn.example.com/js/jquery.js"></script>
<script>
if(!window.jQuery) {
    document.write('<scr' + 'ipt src="/js/jquery.js"></scr' + 'ipt>');
}
</script>

最後,現在廣泛被大家使用的「將 JS 程式碼快取在本地 localStorage」方案也有很大的安全隱患。網站出現任何 XSS,都有可能被用來篡改快取在 localStorage 的程式碼。之後即使 XSS 被修復,localStorage 中的程式碼依然是被篡改過的,持續發揮作用。這個問題,以後再專門討論。

擴充套件閱讀:

Mozilla Blog:Do not let your CDN betray you: Use Subresource Integrity
Github Blog:Subresource Integrity
MDN:Subresource Integrity