Wiki: Cross-origin resource sharing (CORS) is a mechanism that allows restricted resources on a web page to be requested from another domain outside the domain from which the first resource was served.
白話: CORS是一個(防禦)機制,允許瀏覽器向不同網域的server發送請求。CORS是一個瀏覽器技術規範,判斷要阻擋或是允許不同網域的資源存取。
基於安全性的考量,瀏覽器預設會限制網頁做跨網域的連線。如果像是透過node.js送出請求,就不會受到限制!
Same-origin policy-同源政策
雙方必須具備相同的協定&port (if 有指定)&主機位置
A- http://www.example.com/foo
B- http://www.example.com/bar
C- https://www.example.com/foo
A v.s. B >>OK!
A v.s. C >>NO!
但但…不是全面性的限制不同來源的資源存取。
基本上: 同源的資源一定可以互相存取;例外: 跨來源的資源,在特定情況下才允許存取
- cross-origin-writes 允許跨來源的寫入
- 連結, 表單送出…
- cross-origin embedding 允許跨來源的嵌入
<script src="xxxx"></script>
<link rel="stylesheet" href="xxxx">
<img>
<video>
<iframe>
小結: 圖片、影片、script…等資源,會被載入瀏覽器中,成為DOM的元素的,來源不管;頁面跳轉,來源不管
- cross-origin read 不允許跨來源的讀取
- EX: 透過fetch or XHR發出的請求
- 但….可以使用cross-origin embedding偷吃步
加映Cookies也有同源政策
Cookies是網站在使用者瀏覽網頁時,儲存在使用者端的資料
當下一次對此網站送出Request時,會一併帶上cookies
常見在紀錄購物網站記錄使用者的資訊/購物車資訊/Ad- Retargeting
在DevTools檢視cookies
cookies也有同源政策,A網站存在client端的cookie沒有辦法被B網站讀取! (localstrage & indexDB也是)
瀏覽器訊息
當網頁向外部網站要資料時,瀏覽器將Request送出,拿到Response,但瀏覽器會基於安全性考量,不讓程式(javascript)拿到Response。
範例: 利用XHR送出Request
// 透過XMLHttpRequest物件-->開啟URL-->發出請求
// Transaction完成後,XMLHttpRequest物件會包含Response和HTTP狀態等資訊
<script>
function httpGet(theUrl) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function () {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
console.log('success');
}
xmlHttp.open("GET", theUrl, true); // 請求初始化 (true for asynchronous)
xmlHttp.send(null); //送出請求
xmlHttp.onerror = function () {
console.log('error!!!!');
}
}
</script>
會看到錯誤訊息:
Q:如何突破安全限制,取得跨網域的資料內容??
script可以在同網域之間互相傳送資料,但禁止不同網域之間取用方法/屬性
解法1: CORS跨源設定
由Server端做調整設定,於Response Header加上Access-Control-Allow-Origin
Access-Control-Allow-Origin: * # 允許所有網站發送的請求
Access-Control-Allow-Origin: http://foo.example # 只允許 http://foo.example 的請求
瀏覽器收到Server Response,檢查Access-Control-Allow-Origin
的內容,若Origin符合Access-Control-Allow-Origin
,程式才能收到Response的內容
利用express提供的cors套件https://expressjs.com/en/resources/middleware/cors.html
CORS將請求分兩種: Simple Request& Preflight Request
-
Simple Request
一般請求
-
Preflight Request (預檢請求)
有些情況下
Access-Control-Allow-Origin
檢查還不夠,像是Delete
/Put
這類的請求,需要取得Server的允許後,再送出真正的Request先送一次檢查,確認是不是能送出真正的請求
ex: xxxx/api/posts/1 刪除文章
小結CORS:
-
發出一個CORS Request,Request header中帶有Origin資源
-
Browser檢查是simple or not-simple request
-
simple-直接送實際的Request,browser依Response header
Access-Control-Allow-Origin
決定要不要給not-simple-通常是GET以外的Request,browser會先送一個OPTIONS Preflight Request,Browser確認Server Allow的項目,符合後才送實際的Request
還有其他相關的Header:
Access-Control-Allow-Headers:哪些 HTTP Header Server支持。
Access-Control-Allow-Methods:存取資源所允許的方法,用來回應預檢請求。
Access-Control-Expose-Headers:瀏覽器能夠存取伺服器回應當中哪些標頭。
Access-Control-Max-Age:預檢請求之結果可以被快取的秒數。
Access-Control-Allow-Credentials:用於驗證請求中。
實作
Server:
const express = require('express');
const cors = require('cors'); //express出的套件cors
const PORT = process.env.PORT || 3000;
const app = express();
app.use(cors());
app.get('/hello/:id', function (req, res, next) {
res.json({
msg: 'Hello world, we are CORS-enabled!'
});
});
app.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`);
});
CORS預設為全開放:
{
"origin": "*",
"methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
"preflightContinue": false,
"optionsSuccessStatus": 204
}
自訂:
const corsOptions = {
origin: [
'http://www.example.com',
'http://localhost:8080',
],
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
allowedHeaders: ['Content-Type', 'Authorization'],
};
app.use(cors(corsOptions));
p.s. Chrome 79+ no longer shows preflight CORS requests
下載 HTTP Toolkit
解法2: JSONP
JSON with Padding,有些tag src不受同源的限制,載入帶參數(querystring)的js,發送request
(type=”text/javascript”,資料藏在回傳的內容中)
當想要向http://localhost:3000/user?id=xxx取得資料,透過瀏覽器送出http://localhost:3000/user?id=1
Server根據id=1,回傳json
{
"Name":"Pon",
"id":"1",
"age":"xx"
}
或是取得資料的同時就執行script
// Client
<script>
function jsonpCallback(res){
// bla bla bla
}
</script>
<script type="text/javascript" src="/app/jsonp"></script> // JSOP
// Server
app.get('/app/jsonp', (req, res) => {
const responseData = { data: [{ id: 1, name: 'Brian' }, { id: 2, name: 'Peter' }] }
const responseDataText = JSON.stringify(responseData)
res.set('Content-Type', 'application/text')
res.send(`jsonpCallback(${responseDataText})`)
})
Client定義jsonpCallback function要實作的內容/邏輯,Server遇到/app/jsonp的請求,回傳jsonpCallback(responseDataText)
通常Server會提供一個callback,讓client可以傳入,/app/jsonp?id={id}&jsonpcallback={callback}
解法3: Proxy
-
Proxy : 在client之前,攔截/收集Request,向web server溝通,取得的資源返回給client端,Server只知道Proxy的IP位址,不知道Client的IP
(代理client)
-
Reverse Proxy: 在server之前,攔截/收集request,不公開真正的Server IP位址,所有請求會先給Reverse Proxy,由Reverse Proxy來分配給Server
(代理server)
Server之間不會有跨域問題,建立Proxy發送請求!
範例-Angular proxy setting
// proxy.conf.json
{
"/api/*": {
"target": "http://localhost:3000", // Request target
"secure": false,
"logLevel": "debug",
"changeOrigin": true
}
}
// 在app中所有/api/...開頭的request會被置換成http://localhost:3000/api/....送到proxy
// proxy再把請求送給後端
參考: https://juristr.com/blog/2016/11/configure-proxy-api-angular-cli/
加映-沒有同源保護下會….
Cross site request forgery
CSRF,跨站請求偽造
在不同的domain下,偽造出使用者發出的Request,因為瀏覽器機制是發出Request給某網域時,一併帶上所屬cookie。
範例取自讓我們來談談 CSRF
// 在網站中製作一個惡意連結
<a href='https://books.com/delete?id=3'>影片播放</a>
// 送出Request給books網站,cookies一併被送出
Server端收到請求,也驗證了session,於是執行了刪除
// 不知不覺
<img src='https://books.com/delete?id=3' width='0' height='0' />
所以…..使用者可以養成隨手登出好習慣
Server端可以檢查referer、圖形驗證、簡訊驗證碼
瀏覽器也可以來做防禦,如chrome實作SameSite
,阻止跨網站發送cookies,可防止CSRF攻擊
// Server
// Set-Cookie: key=value SameSite有三個值 None, Strict & Lax
Set-Cookie: cat_image_loaded=1, SameSite=None 不限制同源
Set-Cookie: cat_image_loaded=1, SameSite=Strict 限同源網站,完全禁止第三方cookies
Set-Cookie: cat_image_loaded=1, SameSite=Lax 部分request允許 (<a>, <link> )
clickjacking
例如:iframe visibility調整將真實網頁隱藏,假的網頁誘使User點擊(ex:輸入帳號密碼)
另外也可以控制滑鼠點擊的任何連結,甚至控制Camera或麥克風
取自:https://www.netsparker.com/blog/web-security/clickjacking-attacks/
解法: 如果希望整個網站都不准其他網頁內嵌,可以在web server的header中加入X-Frame-options (DENY/ SAMEORIGIN/ ALLOW-FROM uri),瀏覽器根據同源政策下,符合的網頁才能使用iframe,需要瀏覽器支援這個Header才有效果。
延伸閱讀&參考圖文
Day8-什麼是CROS (Cross-Origin Resource Sharing)
Same Origin Policy 同源政策 ! 一切安全的基礎
CORS, Same-origin policy 和 iFrame
Using a simple proxy to add CORS support to Ex Libris APIs