script 標籤的安全性

September 9, 2022

有些安全觀念真的就是基本觀念,只是開發者毫無自覺罷了,目前你已經知道了 JavaScript 執行於瀏覽器方式,那就來略為探討吧!

原始碼中的敏感資訊

瀏覽器會解析 HTML 檔案來呈現畫面,有些資料可以藏在 HTML 中,便於後續應用程式執行某些任務。例如,設計多頁問卷時,可以將 inputtype 設為 hidden,用來記錄上一頁問卷的答案,下一次的表單發送,會一併發送隱藏欄位 的值,而使用者不會在瀏覽器畫面中看到該欄位,不會對操作造成妨礙。

使用者不會在瀏覽器畫面看到 JavaScript 程式碼,在一些應用中,後端可以動態地產生 JavaScript 程式碼,其中包含前端瀏覽器需要的資料,例如選單後續的可選項目,如此就不用每次都透過網路跟後端請求,節省網路方面的開銷。

只要不涉及敏感資訊,這類隱藏在 HTML 或 JavaScript 原始碼中的資料,是合理應用;然而有些開發者卻誤以為,只要瀏覽器不在畫面上呈現給使用者觀看的資料,就可以放到 HTML 或 JavaScript 原始碼中,甚至將密碼都放進去了;這跟技術沒有直接的關係,純綷就是觀念問題,敏感資訊永遠都不該放到 HTML 或 JavaScript 原始碼,畢竟只要檢視網頁原始碼就看光光了。

密碼會被放到前端往往也意謂著,密碼是以明碼儲存,這也是不對的,這類與缺少基本安全觀念相關的實例不少,有興趣可以看看〈網站系統安全及資料保護設計認知

同源策略

當 JavaScript 原始碼來自網路時,與其直接相關的安全議題之一是同源策略(Same-origin policy),同源指的是請求的資源與目前文件來源,具有相同的協定、埠號以及位置,例如若目前文件來源的協定、埠號以及位置為 http://caterpillar.onlyfun.net/,那麼底下協定、埠號以及位置的資源就不是同源:

  • https://caterpillar.onlyfun.net:http 與https 視為不同的協定。
  • http://openhome.cc:位置名稱不同,視為不同位置(就算實體機器是同一台)。
  • http://192.168.0.1:位置名稱不同,視為不同位置(就算實體機器是同一台)。
  • http://caterpillar.onlyfun.net:8080:在沒有指定埠號下,預設埠號是 80,這邊卻指定 8080,因此是非同源。

如果有份 HTML 網頁來自 http://caterpillar.onlyfun.net/,其中的 JavaScript 程式碼無法取得 src 為非同源的 iframe 相關資訊,預設也無法以 XMLHttpRequest 或 Fetch API 等,請求非同源的資源,瀏覽器會禁止 JavaScript 取得結果。

子網域的資源,預設視為非同源,不過 JavaScript 在瀏覽器中,可以透過 document.domain 設定子網域為同源,document.domain 預設為文件來源。

以方才的 http://caterpillar.onlyfun.net/ 來源的 HTML 網頁為例,若其中使用 JavaScript 程式碼取得 document.domain,值就是 'caterpillar.onlyfun.net',這時無法使使用 XMLHttpRequest 或 Fetch API 等請求 sub1.onlyfun.netsub2.onlyfun.net 等子網域下的資源。

然而,可以將 document.domain 設定為 'onlyfun.net' ,這時來自 sub1.onlyfun.netsub2.onlyfun.net 的資源就會視為同源,若要讓子網域可以請求頂層網域,需要同時改變子、頂層網域的 document.domain 為相同值;document.domain 可以設為頂層網域,然而不能設其他網域。

script 標籤的 src 可以引用外部 .js 檔案,如果 type"text/javascript" 的話,不限於同源的 .js,非同源的 .js 也可以引用,然而不同來源的 .js 檔案在 HTML 頁面中執行時,是以該 HTML 頁面來源作為同源依據。

例如 http://caterpillar.onlyfun.net/index.html 中的 script 標籤,若 src 引用了 http://openhome.cc/foo.js,程式碼運行時,同源依據是 index.html 的來源 http://caterpillar.onlyfun.net/,而不是 http://openhome.cc/

CORS

如果你經常使用一些 Web 網路服務,看到這邊應該會疑惑,因為有些網路服務確實請求了來自不同網站的資源啊?處理這類跨域請求的方式之一,就是實作 CORS(Cross-Origin Resource Sharing) ,這是基於 HTTP 標頭的跨來源資源共享協議。

若要遵照 CORS,以最簡單的情境來說,瀏覽器發出非同源的跨域請求時,會附上 HTTP 標頭 Origin,告知伺服端目前文件來源,伺服端若同意跨域請求,回應中要包含 Access-Control-Allow-Origin 標頭,值可以是 *(表示不管哪個來源都接受)或某一網域名稱。瀏覽器收到回應時,若回應中沒有 Access-Control-Allow-Origin 標頭,或者它的值既非*也不符合目前文件來源,瀏覽器就不會交出收到的資源。

請求還是會發出,視伺服端設計而定,伺服端或許也會回應資源,只不過若 Access-Control-Allow-Origin 標頭值對不上的話,瀏覽器會讓你拿不到資源。

XMLHttpRequest 或 Fetch API 支援 CORS,只要非同源的伺服端允許,XMLHttpRequest 或 Fetch API 可以依以上的原理取得非同源的資源。

模組的匯入必須遵守 CORS,script 標籤的 type 設定為 "module" 時,在 src 引用的若是非同源的 .js 檔案,或者是在模組原始碼中 import 非同源的模組,請求 .js 檔案時會附上 Origin 標頭,若伺服端沒有實作CORS回應,瀏覽器就不會解析、執行模組。

script 標籤的 type"text/javascript" 的話,src 引用的 .js 檔案,請求、回應並不遵照 CORS;然而,HTML5 提供了 crossorigin 屬性,script 標籤若設定 crossorigin 屬性,對 .js 的請求就要遵照 CORS,伺服端必須回應 Access-Control-Allow-Origin 標頭與合法的值,否則就無法引用 .js 檔案。

若伺服端支援 CORS,在 script 標籤的 type"text/javascript" 的情況下,設定 crossorigin 屬性的好處是,若引用的非同源 .js 發生錯誤,可以取得更詳細的錯誤訊息;現在有些程式庫,若要非同源地引用官方提供的 .js 檔案,官方會建議在 script 標籤加註 crossorigin,以便程式庫提供詳細的錯誤訊息,原因就在於此,這也表示官方提供 .js 的伺服端,也實作了 CORS 的支援。

分享到 LinkedIn 分享到 Facebook 分享到 Twitter