簡單弄懂同源政策 (Same Origin Policy) 與跨網域 (CORS)

Hannah Lin
Starbugs Weekly 星巴哥技術專欄
11 min readAug 15, 2021

Understanding Frontend Security 系列文

1. 簡單弄懂同源政策 (Same Origin Policy) 與跨網域 (CORS)
2.
[XSS 1] 從攻擊自己網站學 XSS (Cross-Site Scripting)
3. [XSS 2] 如何防禦 XSS 攻擊
4. Content Security Policy (CSP) — 幫你網站列白名單吧
5. [CSRF] One click attack: 利用網站對使用者瀏覽器信任達成攻擊

我相信每一個前端在串接 API 時,多多少少一定遇過以下錯誤

雖然要解決此問題大部分還是需要後端幫忙,但前端卻需要知道為什麼會發生、大概要如何解決

🔖 文章索引1. 同源政策 (Same Origin Policy)
2. CORS (Cross-Origin Resource Sharing)

同源政策 (Same Origin Policy)

The same-origin policy is a critical security mechanism that restricts how a document or script loaded by one origin can interact with a resource from another origin.

同源政策是網站安全的基礎。 https://domain-a.com 只能存取自己網站裡的資源 (圖片、影片、程式碼等),不允許網站 https://domain-b.com 來存取。想要存取跨來源資源必須在某些特定情況下才被允許。

畫清楚界線自己網站自己管,別人讀取不到也改不了。這很合理,不然若自己網站被別人任意修改、被惡意人士讀取機密資訊那就糟糕了!若沒有這層防護,壞人就可以任意新增刪除你 Facebook 裡面留言了、也可以輕易登入你銀行帳戶 😨。

怎麼判斷同不同源?

只要 scheme、domain、port 一樣就會被視為同源 (same-origin),其他則是不同源

右: domain 不同,所以當然不同源

若以 https://domain-a.com:80/hannah-lin 做範例,我們可以因此判斷跟以下是否同源

http://domain-a.com → 不同源.scheme 不同
https://domain-a.com/mike → 同源
https://news.domain-a.com → 不同源.domain 不同
https://domain-a.com:81 → 不同源.port 不同
https://domain-b.com → 不同源.domain 不同

Note. IE 對於不同 port 會視為同源

但我網站明明就引入很多跨來源的資源啊?

網頁常見的跨來源資源真的不少,例如以下

左: 程式碼示意 / 右: 畫面

沒錯,在某些情況下跨來源是被允許的,不受同源策略限制

跨來源嵌入通常被允許 (embed)

像範例的 <script src=”…”></script><link rel=”stylesheet” href=”…”><iframe>、圖片 <img><video>、或是 @font-face <object><embed>.等等都是跨來源嵌入。

跨來源寫入通常被允許 (writes)

可以在藉由<form>domain-a.com 發 request 給 domain-b.com,當然透過連結 links 或直接 redirect 到別的網站也是被允許的。

跨來源讀取通常被禁止 (reads)

domain-a.com 不能讀取 domain-b.com 的 cookie、XMLHttpRequest ,Fetch API 也都無法被讀取,會回報錯誤

同源政策 (Same Origin Policy) 允許 HTML tag 產生的跨來源寫入 (write)/嵌入 (embed)/讀取 (read),但對於 JS 的跨來源 write/embed/read 是有限制的

那什麼是 Cookie 的同源 ?

Set-Cookie: <cookie-name>=<cookie-value>
Set-Cookie: id=1234567; domain=hello.com

前端都知道 Cookie 是什麼,簡單來說就像一張通行證,通行證裡面會存有一組 Session ID 來驗證你身份,就像你加入某某俱樂部一樣,認卡不認人,誰擁有這張通行證就可以進入俱樂部。

Cookie 的同源跟以上所說的 DOM 同源有點不同 (圖片或是程式碼等資源被載入瀏覽器最後都會變成 DOM 的元素,延伸閱讀: 從輸入網址列到渲染畫面,過程經歷了什麼事?)

只要 domain 跟 Path 與 Cookie 上的一樣就會被視為同源。若經過一些設定才會判斷 scheme 要是 http 或 https。

// 加了 Secure 會限定此 Cookie 只能以 https 傳送
Set-Cookie: id=1234567; domain=hello.com; Secure

另外要注意的是子網域的 cookie 是可以存取到母網域 cookie 的 (api.domain-a.comdomain-a.com cookie 可以被共用)

對了,同源政策是瀏覽器專屬,所以才會發生用 postman 可以拿到 API 回應但放到網站上就是會失敗的狀況。

CORS (Cross-Origin Resource Sharing)

CORS 翻成中文就是跨網域資源共享,也就是你可以用我資源我也可以用你的

為什麼會有 CORS

Same Origin Policy 雖然不錯,因為他防止了一些惡意的 script 攻擊,但總不會每一個跨網域都是惡意的; 也不可能一間公司擁有所有的資源,有時還是必須串接第三方資源,例如 Facebook API、Google Map、政府釋出的公開 API 等。

怎麼實作

實作 CORS 相當容易,它其實只是 HTTP-Header 而已,這些設定基本上都是在後端,所以前端只需知道概念跟怎麼看 Responce Header 即可。

當前端用 fetch 或 XMLHttpRequest 要存取資源時,在 request 之前都會先發送 preflight request 確定 server 端有設定正確的相關 Http-Header,若檢查通過,才會實際發出 requeest。例如

fetch('https://xxx.com/data/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
})

那 request header 會大概長這樣

POST /data/
Host: xxx.com
Origin: https://myweb.com
Content-Type: application/json

preflight reques 會大概是這樣

OPTIONS /data/
Host: xxx.com
Origin: https://myweb.com/
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type

你可以在 developer tools 中的 Network 看到相關的 Responce Header

當然後端可以把權限開到最大讓任何人都可以讀取,不受同源政策的限制

Access-Control-Allow-Origin: *

但跨來源還是有許多風險的,所以建議還是不要直接 * 開好開滿。

// 還是會針對特定網站開權限
Access-Control-Allow-Origin: https://foo.example
// 可以設定允許哪些 method,defult 是全部方法
Access-Control-Request-Method: POST, GET
// 允许 client side 帶 cookie 等驗證,defult 是 false
Access-Control-Allow-Credentials: true

另外如果你需要在發送 request 的時候帶上 cookie,那必須滿足三個條件:

  1. 後端 Response header 有 Access-Control-Allow-Credentials: true
  2. 後端 Response header 的 Access-Control-Allow-Origin 不能是 *,要明確指定
  3. 前端 fetch 加上 credentials: 'include'
fetch('http://localhost:3000/form', {
method: 'POST',
credentials: 'include', // 前端新增這個允許 cookie
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
}).then(res => res.json())
.then(res => console.log(res))

想看更多可以直接閱讀 MDN 的 CORS

simple request?

滿足以下全部條件就是 simple request

  • HTTP methods: GET、POST、HEAD
  • 不帶自訂的 header
  • Content-Type:application/x-www-form-urlencodedmultipart/form-data 或是 text/plain

Access-Control-Allow-Origin: *基本上就可以滿足 simple request 的 CORS 問題,但若不是 simple request 後端就必須再加上 HTTP methods 跟 Content-Type ,例如若 Content-Type 為 application/json

// BE 要加上 Access-Control-Allow-Headers 明確表示願意接受這個 header
app.options('/form', (req, res) => {
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-Headers', 'content-type')
res.end()
})

preflight request?

不是 simple request 都會先發一個 preflight request 確定 server 端有設定正確的相關 Http-Header,也就是會先問 server 是否允許這個 request,允許的話才會正式 request。像 HTTP PUT/DELETE method,或 Content-Type: application/json 都會先發 preflight request。

有同源政策的保護,我的網站就是安全的嗎 ?

答案是: 不對。

Same Origin Policy 只是最基本的保護而已,實際上 attackers 還是可以聰明地找到漏洞攻擊你的網站。例如 Cross-site scripting (XSS) 可以欺騙網站來繞過同源政策的保護,這是一個很大的問題,因為同源政策下瀏覽器信任底下所有資料存取都是安全的,但這樣被惡意注入利用很有可能就讓你網站掛掉,更嚴重是用戶敏感資料被洩漏,應該被保密的資料被壞人利用。

HTML5 Security Cheatsheet 這個網站蠻酷的,提供各種 XSS 變形手法程式以及範例可以上去玩玩看

要確保自己網站的安全,還是需要下功夫的,接下來也會介紹一些簡單的方法來保護你的網站!

--

--