【Web】徹底理解同源政策(Same Origin Policy)

對於網頁學習者來說,同源政策(Same Origin Policy)可能是一個很重要的困惑點。

當我們終於搞懂了client與server;理解了HTTP request/response;摸熟了AJAX;想出了一個絕佳的專案點子⋯⋯但不知為何,API的資料就是讀不出來。

於是我們開始研究關鍵字「同源政策」並持續Google,每次都好像又多懂了一點,但又不是那麼肯定——特別是在部署至production環境時,只要網域轉換,似乎就會發生不可預期的效果。

讓我們試著一次把它搞懂吧。

Meme of AJAX 圖片:Ajax迷因。Same Origin Policy是件麻煩的事(出處:reddit)

瀏覽器/使用者代理人/安全性

要理解同源政策,最核心的重點就是理解:

同源政策是由「誰」來執行?以及「為什麼」?

是HTTP(S)的嗎?還是JavaScript?是瀏覽器嗎?是網站框架嗎?還是TCP/IP?

如果能將以下重點銘記在心,我想在閱讀文件時就會感到豁然開朗:

同源政策,Same Origin Policy,是「瀏覽器」作為使用者代理人,為了「安全性考量」,所設定的規範。

什麼是使用者代理人?

請試著想像如下情境:我們拿著雙證件到銀行開了戶,也存了錢進去。行員告知:如果需要匯款或是其他操作,可以透過帳號+密碼登入銀行網站,在瀏覽器中進行——這樣就不用每次都跑銀行囉。

在現實世界裡,與我們互動的是銀行行員,我們可以透過證件與存摺存取帳戶;但在網路銀行中,與我們互動的對象成了Web server,而驗證身份的方式則變成帳號+密碼。

當我們請銀行行員協助時,使用的是人類的自然語言。但在網路銀行中,我們與web server是透過使用者介面/通訊協定/網路訊號來進行溝通的。

但作為一個人類,我們無法用大腦解析這一切。

於是我們需要一個代理人(user agent)——也就是瀏覽器,協助我們與web server進行溝通。這包含了渲染UI、設定Cookie、執行TCP三向交握、通訊加密、並將我們的表單解析成Http Request後送出⋯⋯等。

使用者的「安全性」是瀏覽器的職責

在操作的過程裡,瀏覽器有義務保護使用者的資料——尤其是涉及身份驗證的部分。

我們會透過瀏覽器登入各種帳號(例如網路銀行、臉書、Gmail、以及各式各樣來路不明的網站)。當成功使用帳號密碼登入時,這些服務的Web server會透過HTTP header, 提供一段auth token並要求瀏覽器存在cookie裡。在auth token的有效期間內,我們的http request都會自動帶上這段cookie,以此為憑,我們因此可以對服務進行各種操作,例如匯款、貼文、按讚、回覆email⋯⋯等。

但問題來了,如果有一個惡意的盜版漫畫網站evil.com,當我們在訪問時,透過頁面中JS以及AJAX,偷偷在瀏覽器中執行這樣的行為呢:

「請把銀行bank.com的cookie資料(包含auth token)讀取出來;透過AJAX+POST;回傳到https://evil.com/sniff

當然,作為一個使用者代理人,瀏覽器會拒絕如是請求。

拒絕的原則是什麼呢?那就是同源政策最重要的一環:「非同源的資料讀取是不被允許的」。既然evil.com不能讀取bank.com設定的cookie,那當然也無法將之回傳。

火狐姬

同源(Same-origin)的定義

如果能充分了解以上原則。剩下的,就只是技術細節的問題。

我們可以透過火狐家的MDN文件理解同源政策的規範。一言以敝之,我們可以把origin視做一個three-tuple(scheme, host, port),也就是(通訊協定, 網域, 埠號)的組合。兩個document的origin只要任一欄位不相同,就會被瀏覽器視作不同源[1]。

舉例如下:

此外,同源政策是以document為單位的。舉例來說:假設store.mystie.com的document透過<script>標籤取得了來自cdn.google.com的JS腳本,則該腳本的origin仍是由blog.mystie.com來定義。

在實務中,我們最常遇到的origin變數往往是網域。為了方便起見,以下討論會將網域等同於origin使用,但請注意origin包含的不只有網域而已。

SOP show domain 圖片:我們可以透過document.domain來取得文件所屬的網域。

跨源存取細節

討論到這裡,可能我們已經想到一堆例外了⋯⋯

  • 我的網站會透過CDN導入jQuery函式庫和Bootstrap的CSS,為什麼這不受限制?
  • 我的網站會使用<img>讀取外站圖片,為什麼這不受限制?

事實上,同源政策其實有許多細節,甚至各家瀏覽器的實作都會有些微不同。請注意,當我們在討論這個問題時,我們的角色已經轉變成服務的提供者與Web Server了。在研究同源政策時,釐清主詞(使用者/瀏覽器/網站後端)以及行為(request/response)是最重要的起手。

我們可以透過MDN的文件理解不同的跨源行為

跨源寫入(write)原則上允許

可以在A網域的document中提交請求給B網站;也可以透過超連結、轉址等方式連結至B網站。

跨源嵌入(embed)原則上允許

可以使用<img> <video> <iframe>標籤嵌入來自其他網域的資源。

可以使用<link rel="stylesheet" href="…">套用來自其他網域的CSS。

可以使用<scrip src="">標籤嵌入來自其他網域的腳本[3]

跨源讀取(read)原則上禁止

A網域的document不能讀取B網域的本地端儲存資料(例如Cookie)與回應。

值得注意的是:我們可以在A網域的document中,使用JS發送AJAX Request給B(跨源寫入允許);但就算B有回應;文件也無法讀取response(跨源讀取禁止)。例如這樣的jQuery操作$.get("https://www.google.com ")在所有非Google的document中,都會回報disallows reading的錯誤。

那第三方的Client端API是怎麼實作的?

如果跨源讀取是被禁止的,作為一個開發者(假設是交友網站),如果我們希望有一個第三方API服務(例如臉書),可以讓我們透過瀏覽器+AJAX取得資料,這其中的實作原理是什麼呢?

實情是:臉書作為第三方服務的提供者(務必注意主詞!),在回應請求時,是可以透過HTTP header中的Access-Control-Allow-Origin欄位,請瀏覽器特別通融。

舉例來說,當我們的交友網站向臉書申請API帳號時,臉書會提供一組token並詢問我方網域(假設是https://friend.com)。當我們使用這組token請求資料時,臉書就會在response中設定Access-Control-Allow-Origin=https://friend.com,翻成白話就是:

雖然 friend.com 與我不同源,但請准許他讀取這則HTTP response。

當收到這樣的回應時,瀏覽器就會允許friend.com之下的document讀取來自facebook API的回應囉[4]。

如果API server將Access-Control-Allow-Origin設定為*時,代表任何origin都可以讀取回覆,不受同源政策限制。

當然,回到一開始銀行的例子。基於安全性考量(只賦予必要的最小權限),當然就沒有必要設定這個欄位了。

client_app 圖片:使用client端API時,設定網域通常是必要的

那麼⋯⋯我們就這樣高枕無憂了嗎?

與同源政策常常一起討論,且觀念容易糾纏在一起的另一個主題,是所謂的跨域請求偽造攻擊(Cross Site Request Forgery)。這兩者彼此概念交錯,但其實同源政策並不能完全防止跨域請求偽造攻擊——不過這也是另一個主題了,我們就有空再說吧。

目前為止,希望我們已經清楚解釋了同源政策的大概念。


註解

[1] 在RFC-6454原文中的host對應的中文應該是「主機名稱」。但本篇文章主要聚焦於瀏覽器的一般用例(而非各種形式的user agent),因此使用「網域」一詞雖然不完全精準,但為了可讀性仍然選用。

[2] cookie的讀取權限可以下放給子網域。細節請見RFC-6256中的Domain Matching章節。

[3] 請注意這包含了「伺服器端後果自負」的前提。作為後端服務的開發者,在document中嵌入來自第三方(諸如Google CDN)的JavaScript意即「允許第三方腳本以我的origin身份進行操作」——這是另一個網頁安全性的大主題,有興趣的讀者可以搜尋Cross Site Scripting(XSS)。

[4] 請注意這只是允許讀取response,並不包含允許friend.com讀取facebook.com的cookie的意味。

[ref] IETF | RFC-6265 HTTP State Management Mechanism

[ref] IETF | RFC-6454 The Web Origin Concept

[ref] LiveOverflow | CSRF Introduction and what is the Same-Origin Policy? Concept

[ref] MDN | Same Origin Policy

[ref] stackoverflow | Does including all these 3rd party javascript files impose a security risk?

Leave a Reply