我知道的跨域與安全

關於跨域,有兩個誤區javascript

1. ✕ 動態請求就會有跨域的問題html

✔ 跨域只存在於瀏覽器端,不存在於安卓/ios/Node.js/python/ java等其它環境java

2. ✕ 跨域就是請求發不出去了python

✔ 跨域請求能發出去,服務端能收到請求並正常返回結果,只是結果被瀏覽器攔截了jquery

之因此會跨域,是由於受到了同源策略的限制,同源策略要求源相同才能正常進行通訊,即協議、域名、端口號都徹底一致。ios

以下圖所示:nginx

這三個源分別因爲域名、協議和端口號不一致,致使會受到同源策略的限制。web

同源策略具體限制些什麼呢?ajax

1. 不能向工做在不一樣源的的服務請求數據(client to server)canvas

這裏有個問題以前也困擾了我好久,就是爲何home.com加載的cdn.home.com/index.js能夠向home.com發請求而不會跨域呢?其實home.com加載的JS是工做在home.com的,它的源不是提供JS的cdn,因此這個時候是沒有跨域的問題的,而且script標籤可以加載非同源的資源,不受同源策略的影響。

2. 沒法獲取不一樣源的document/cookie等BOM和DOM,能夠說任何有關另一個源的信息都沒法獲得 (client to client)


爲何會有同源策略呢?

1. 爲何要限制不一樣源發請求?

假設用戶登錄了bank.com,同時打開了evil.com,若是沒有任何限制,evil.com能夠向bank.com請求到任何信息,進而就能夠在evil.com向bank.com發轉帳請求等。

若是這樣,爲何不直接限制寫,只限制讀?

由於若是連請求都發不出去了,那就不能作跨域資源共享了,沒法讀取返回結果,evil.com就沒法繼續下一步的操做,如獲取轉帳請求的一些必要的驗證信息。

2. 爲何限制跨域的DOM讀取?

若是不限制的話,那麼很容易就能夠假裝其它的網站,如套一個iframe或者經過window.open的方法,從而獲得用戶的操做和輸入,如帳戶、密碼。

另外,添加這個http頭能夠限制別人把你的網站套成它的iframe:

X-Frame-Options: SAMEORIGIN


同源策略提供了安全的同時也形成了不方便,由於有時候咱們須要跨域請求,如獲取第三方提供的服務信息,因爲第三方的源和本網站的源不同,因此這個時候就受到跨域的限制。

跨域最經常使用的方法,應當屬CORS,以下圖所示:

只要瀏覽器檢測到響應頭帶上了CORS,而且容許的源包括了本網站,那麼就不會攔截請求響應。

CORS把請求分爲兩種,一種是簡單請求,另外一種是須要觸發預檢請求,這二者是相對的,怎樣纔算「不簡單」?只要屬於下面的其中一種就不是簡單請求:

(1)使用了除GET/POST/HEAD以外的請求方式,如PUT/DELETE

(2)使用了除Content-Type/Accept等幾個經常使用的http頭

這個時候就認爲須要先發個預檢請求,預檢請求使用OPTIONS方式去檢查當前請求是否安全,以下圖所示:

代碼裏面只發了一個請求,但在控制檯看到了兩個請求,第一個是OPTIONS,服務端返回:

返回頭裏麪包含了容許的請求頭、請求方式、源,以及預檢請求的有效期,上圖是設置了20天,在這個有效期內就不用再發一個options的請求,實際上瀏覽器有一個最長時間,如Chrome是5分鐘。若是在預檢請求檢測到當前請求不符合服務端設定的要求,則不會發出去了直接拋異常,這個時候就不用去發「複雜」的請求了。

如本源不在容許的源範圍內,則會拋異常,沒法獲取返回結果:

爲了支持CORS,nginx能夠這麼配:

location / {
     if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Max-Age' 1728000;
        add_header 'Content-Type' 'text/plain; charset=utf-8';
        add_header 'Content-Length' 0;
        return 204;
     }
     add_header 'Access-Control-Allow-Origin' '*';
     add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
     add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
     add_header 'Access-Control-Expose-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
}複製代碼


第二種經常使用的跨域的方法是JSONP,JSONP是利用了script標籤可以跨域,以下代碼所示:

function updateList (data) {
    console.log(data);
}

$body.append(‘<script src=「http://otherdomain.com/request?callback=updateList"></script>');複製代碼

代碼先定義一個全局函數,而後把這個函數名經過callback參數添加到script標籤的src,script的src就是須要跨域的請求,而後這個請求返回可執行的JS文本:

// script響應返回的js內容爲
updateList([{
    name: 'hello'
}]);複製代碼

因爲它是一個js,而且已經定義了upldateList函數,因此能正常執行,而且跨域的數據經過傳參獲得。這就是JSONP的原理。


因此因爲script/iframe/img等標籤的請求默認是能帶上cookie(cookie裏面帶上了登錄驗證的票token),用這些標籤發請求是可以繞過同源策略的,所以就能夠利用這些標籤作跨站請求僞造(CSRF),以下面代碼所示:

// 轉帳請求 <iframe src="http://Abank.com/app/transferFunds?amount=1500&destinationAccount=..."></iframe> // 配置路由器添加代理 <img src="http://192.168.1.1/admin/config/outsideInterface?nexthop=123.45.67.89" style="display:none">複製代碼

若是相應的網站支持GET請求,或者沒有作進一步的防禦措施,那麼若是用戶在另一個頁面登錄過了,再打開一個「有毒」的網站就中招了。

而動態ajax請求默認是不帶cookie的,若是你要帶cookie,能夠設置ajax的一個屬性withCredentials,以下代碼所示:

// 原生請求
let xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open("GET", "http://otherdomain.com/list");
xhr.send();

// jquery請求
$.ajax({
   url: "http://otherdomain.com/list",
   xhrFields: {
      withCredentials: true
   }
});

複製代碼

這個時候就和img/script標籤同樣,能帶上cookie,而且還支持除GET以外的其它方式。因此這種方式也是能實現CSRF的,以下圖所示:

因此若是轉帳請求只是不支持GET,沒作其它的防禦措施,仍然有CSRF攻擊的風險。那怎麼辦呢?

方法一是每次請求都要在參數裏面顯示地帶上token即登錄的票,雖然跨域請求能帶上cookie,可是經過document.cookie仍然是獲取不到其它源的cookie的,因此攻擊者沒法在代碼裏面拿到cookie裏面的token,因此就沒辦法了。方法一的缺點是會暴露token,因此須要帶token的最好不能是GET,由於GET會把參數拼在url裏面,用戶可能會無心把連接發給別人,但不知道這個連接帶上了本身的登錄信息。

方法二是每次轉帳請求前都先請求一個隨機串,這個串只能用一次轉帳或者支付請求,用完就廢棄,只有這個串對得上才能請求成功,攻擊者是沒法拿到這個串的,由於若是跨域請求帶cookie,瀏覽器要求Access-Control-Allow-Origin不能爲通配符,只能爲指定的源,如:

Access-Control-Allow-Origin: http://renren.com
因爲攻擊者所在的域名不在這個源裏面,因此它是沒法獲得請求結果,因此請求不到隨機串。所以這種方式也是能夠避免CSRF攻擊。
假設Allow-Origin爲*,ajax設置withCredentials爲true時,瀏覽器會拋異常,沒法獲得返回結果:
另外服務還須要指定Allow-Credentials的頭部,以下代碼所示:
add_header "Access-Control-Allow-Origin" "http://fedren.com";
add_header "Access-Control-Allow-Credentials" "true";複製代碼

關於cookie還有兩個地方值得注意,以下圖所示:


討論完了client to server,咱們再討論client to client,即 如何和一個frame通訊,包括iframe或者使用window.open打開的頁面。

iframe訪問父頁面可經過window.parent獲得父窗口的window對象,經過open打開的能夠用window.opener,進而獲得父窗口的任何東西;父窗口若是和iframe同源的,那麼可經過iframe.contentWindow獲得iframe的window對象,若是和iframe不一樣源,則存在跨域的問題,這個時候可經過postMessage進行通信。

使用postMessage的基本原理以下圖所示:

// main frame
let iframeWin = document.querySelector("#my-iframe")
                .contentWindow;
iframeWin.postMessage({age: 18}, "http://parent.com");
iframeWin.onmessage = function(event) {
    console.log("recv from iframe ", event.data);
};

// iframe
window.onmessage = function(event) {
    // test event.origin
    if (event.origin !== expectOrigin) {
        return;
    }
    console.log("recv from main frame ", event.data);
};

window.parent.postMessage("hello, this is from iframe ", "http://child.com");
複製代碼

以頁面嵌入youtobe視頻爲例,經過如下代碼能夠在頁面嵌入一個youtobe視頻,嵌入的是一個跨域的iframe,因此就涉及到如何和iframe進行通訊的問題。如怎麼知道iframe的狀態,觸發父頁面定義的事件onPlayerReady,這個是iframe通知父頁面,而父頁面能夠調player.stopVideo控制iframe的行爲,這個是父頁面通知iframe。

iframe通知父頁面是經過window.parent.postMessage,同時監聽message事件:

經檢查上面代碼4304行的c就是window.parent,這個embed-player.js是iframe的js,iframe的js經過postMessage發送了一個消息,如上圖右邊的窗口所示,而後在父窗口的widgetapi.js就收到了這個消息。

一樣地,父窗口的JS也是使用postMessage向iframe發送消息,以下圖所示:

固然postMessage不限於跨域,同域的也可使用,只是同域的話能夠經過window對象互相操做,你可能須要額外定義一些全局變量或者函數供其它frame使用,或者是定義一套事件機制(能夠藉助原生事件/jQuery/Vue事件等)。

這裏有一個特例,就是子域如mail.hello.com要跨hello.com的時候,能夠顯式地設置子域的document.domain值爲父域的domain:

document.domain = "hello.com";複製代碼
就不會有跨域的問題了。

補充一點,若是須要和同源的不一樣標籤頁進行通訊可使用localStorage,即一個頁面設置localStorage,其它頁面就會觸發storage事件:
window.addEventListener('storage', function(e) {
    e.key;
    e.oldValue;
    e.newValue;
    e.url;
    e.storageArea;
});
複製代碼

這個我沒試過,讀者能夠試一下。

再補充一點,websocket是不受同源策略限制的,沒有跨域的問題。CSS的字體文件是會有跨域問題,指定CORS就能加載其它源的字體文件(一般是放在cdn上的)。而canvas動態加載的外部image,也是須要指定CORS頭才能進行圖片處理,不然只能畫不能讀取。


最後,跨域分爲兩種,一種是跨域請求,另外一種訪問跨域的頁面,跨域請求能夠經過CORS/JSONP等方法進行訪問,跨域的頁面主要經過postMesssage的方式。因爲跨域請求不但能發出去還能帶上cookie,因此要規避跨站請求僞造攻擊的風險,特別是涉及到錢的那種請求。

相關文章
相關標籤/搜索