同源策略和跨域請求研究

来源:http://www.cnblogs.com/yincheng/archive/2016/02/19/cross-domain.html
-Advertisement-
Play Games

比較深入全面地介紹了什麼是同源策略,同源策略的影響以及如何繞過同源策略進行CSRF攻擊。同時跨域請求的實際應用,通過案例分析跨域請求的幾種辦法:cors、jsonp、子域域父域、iframe跨父域、postMessage實現frame和父視窗的通信


一、同源策略

假設有一個需求,需要向另外的網站請求數據,例如抓取谷歌搜索的結果。然後寫這麼一個請求,搜索內容為hello:

var url = "https://www.google.com.hk/?gws_rd=cr,ssl#newwindow=1&safe=strict&q=hello";
$.ajax({
    url: url, 
    sucess: function(data){
        document.write(data);
    }
});

或者用原生的更直觀:

        var req = new XMLHttpRequest();
        req.open("GET", url);
        req.send();

執行後,瀏覽器會報錯:

大意是說localhost功能變數名稱無法向google.com功能變數名稱請求數據。

因為同源策略的限制,不同功能變數名稱、協議(http、https)或者埠無法直接進行ajax請求。 同源策略只針對於瀏覽器端,瀏覽器一旦檢測到請求的結果的功能變數名稱不一致後,會堵塞請求結果。這裡註意,跨域請求是可以發去的,但是請求響應response被瀏覽器堵塞了

寫了一個程式做驗證——用node開了個服務,監聽在9000埠,然後在8000埠打開一個頁面,再向9000埠的服務發請求:

        url = "http://server.com:9000";
        $.ajax({
            method: "POST",
            url: url,
            data: {
                account: "yin"
            },
            success: function(data){
                document.write(data);
            }
        });

服務將收到的請求數據列印出來:

服務收到了請求,並正常返回數據,但是返回的數據被瀏覽器幹掉了,即使是返回碼也無法得到了。所以說同源策略是限制了不同源的讀,但不限制不同源的寫。那麼我們的問題來了,為什麼不直接限制寫呢,只限制讀有什麼好處呢?在回答這個問題之前,先要瞭解同源策略的作用。

假設我打開了A網銀http://Abank.com,已經通過了登陸驗證,然後再打開了另外一個黑網站http://evil.com,這個網站剛好是抓使用Abank.com的肉雞。在evil.com的代碼里會向Abank.com發請求,例如轉賬請求,將餘額轉到自己的賬戶。但是由於同源策略的限制,使得這種做法無法成功。這個怎麼解釋呢?

因為evil.com無法獲取你在Abank.com的信息,包括驗證身份的信息——通常是按照一定規則生成的無法猜到的隨機token字元串。token可能放在cookie裡面,從evil.com向Abank發請求時,是不會帶上Abank的cookie的,同時也不會帶上evil.com的cookie,雖然cookie是和功能變數名稱綁定的。由於沒有正確的token值,導致無法通過服務的身份驗證。

為驗證沒帶cookie,在上面的例子,localhost向server.com請求數據,服務將收到的cookie列印出來是undefined:

然而localhost已經設置了cookie:

server.com也有設置cookie:

回到上面的問題,為什麼不限制寫呢?那是因為如果連請求也不出去,那在源頭上就限制死了,網站之間就無法共用資源了。另外,限制讀即瀏覽器攔截請求結果,一般情況下就夠了,一方面如果訪問的是黑網站,那麼網站無法跟據請求結果繼續下一步的操作,如不斷地猜測密碼,另一方面如果訪問的是白網站,block掉請求結果,應該是考慮到了請求結果可能會使得頁面重定向,或者是給網頁添加一個惡意的iframe之類的。

有什麼辦法可以繞過同源策略?有一個辦法就是CSRF攻擊

二、CSRF攻擊

如上面的例子,由於同源策略的限制,跨域的ajax請求不會帶cookie,然而script/iframe/img等標簽卻是支持跨域的,所以在請求的時候是會帶上cookie的。還是上面的例子,如果登陸了Abank.com,那麼cookie裡面就有了tocken,同時又打開了另外一個標簽頁訪問了evil.com,這個網頁裡面有一個iframe:

<iframe src="http://Abank.com/app/transferFunds?amount=1500&destinationAccount=... >

這個iframe的src是一個Abank.com的轉賬的請求,如果Abank.com的轉賬請求沒有第二重加密措施的話,那麼請求轉賬就成功了!

第二個例子是路由器的配置,假設我在網上找到了一個路由器配置教程的網站。這個網站裡面偷偷地加一個img標簽:

<img src=”http://192.168.1.1/admin/config/outsideInterface?nexthop=123.45.67.89” alt=”pwned” height=”1” width=”1”/>

其中192.168.1.1是很多路由器的配置地址。這個1像素的圖片沒載入出來被忽略了,但是它的請求卻發出去了。這個請求給路由器添加了一個vpn代理,指向黑客的代理伺服器。如果路由器也是把登陸驗證放在cookie裡面,那麼這個設置vpn的請求很可能就成功了,以後的連接路由器的每個請求都會先經過黑客的服務。

到這裡,很明顯一個防CSRF攻擊的策略就是將token添加到請求的參數裡面,也就是說每個需要驗證身份的請求都要顯式地帶上token值。詳見:Cross-Site Request Forgery Guide: Learn All About CSRF Attacks and CSRF Protection

 

用script引用的外域的資源一方面可以像上面一樣當作一個跨域的請求,另外一方面雖然資源是不可見的,但是script裡面定義的全局對象是可用的,如引用jQuery的CDN,定義的一個全局對象jQuery。所以根據這個特性,在某些條件下可以獲得到script返回的需要登陸才能得到的數據,有興趣的可參見:Plain text considered harmful: A cross-domain exploit

跨域攻擊可以採取一些措施進行規避,但是跨域更多的還是一些實際的正常應用。

三、跨域請求

有時候在自己的網站需要一些去別人的網站請求數據,這個時候就需要跨域正常請求。方法有很多:

1. 跨域資源共用(CORS)

很多天氣、IP地址查詢的網站就採用了這樣的方法,允許其它網站對其請求數據,例如IP location,可以在自己網站的js裡面向它發一個get請求:

        var url = "https://ipinfo.io/54.169.237.109/json?token=iplocation.net";
        document.cookie = "version=1;";
        $.ajax({ url: url })

它就會返回ip地址信息,同時不會被瀏覽器攔截:

觀察response的頭部,可以發現添加了一個欄位:

Access-Control-Allow-Origin就是所謂的資源共用了,它的值*表示允許任意網站向這個介面請求數據,也可以設置成指定的功能變數名稱,如:

response.writeHead(200, { "Access-Control-Allow-Origin": "http://yoursite.com"});

在node.js服務裡面添加這個頭,那麼只有http://yoursite.com能夠正常的進行跨域請求。更多地,還可以指定請求的方式、時間等,詳見:HTTP訪問控制(CORS)

2. JSONP

另外一個常用的辦法是使用jsonp,這個方法的原理是客戶端告訴服務一個回調函數的名稱,服務在返回的scritp裡面調用這個回調函數,同時傳進客戶端需要的數據,這樣返回的代碼就在瀏覽器執行了。

例如8000埠要向9000端品請求數據,在8000埠的頁面文件定義一個回調函數writeDate,將writeDate寫在script的src的參數里,這個script標簽向9000埠發出請求:

    <script>
        function writeDate(_date){
            document.write(_date);
        }
    </script>
    <script src="http://192.168.0.103:9000/getDate?callback=writeDate"></script>

服務端返回一個腳本,在這個腳本裡面執行writeDate函數:

function getDate(response, callback){
    response.writeHead(200, {"Content-Type": "text/javascript"});
    var data = "2016-2-19";
    response.end(callback + "('" + data + "')");
}

瀏覽器就執行了這個script片段:

這樣就實現了跨域的效果。jQuery的ajax里的jsonp的類型,就是用了這樣的辦法,只是jQuery將它封裝好了,使用起來形式跟普通的get/post一樣,但是原理是不一樣的。

JSONP和CORS相比較,缺點是只支持get類型,無法支持post等其它類型,必須完全信任提供服務的第三方,優點是相容性較好。

3. 子域跨父域

子域跨父域是支持的,但是需要顯式將子域的功能變數名稱改成父域的,例如mail.mysite.com要請求mysite.com的數據,那麼在mail.mysite.com腳本里需要執行:

document.domain = "mysite.com";

4. iframe跨父視窗

如果iframe與父視窗也有同源策略的限制,父域無法直接讀取不同源的iframe的DOM內容以及監聽事件,但是iframe可以調用父視窗提供的api。iframe通過window.parent得到父視窗的window對象,然後父視窗定義一個全局對象供iframe調用。

例如在頁面通過iframe的方式嵌入一個youtobe的視頻,如果需要手動播放視頻、監聽iframe的播放事件,頁面需要引入youtobe的視頻播放控制api,在這個js文件裡面定義了一個全局對象YT:

if (!window['YT']) {var YT = {loading: 0,loaded: 0};}

而在視頻iframe的腳本里通過window.parent獲取得到父視窗即自己網站的頁面:

sr = new Cq(window.parent, d, b)

自已網站的頁面也是在這個YT對象自定義一些東西,如添加播放事件監聽:

new YT.Player('video', { events:{ 'onStateChange': function(data){//do sth. } } });

5. window.postMessage

在上面第(4)點,父視窗無法向不同源的iframe傳遞東西,通過window.postMessage可以做到,父視窗向iframe傳遞一個消息,而iframe監聽消息事件。

例如在8000埠的頁面嵌入了一個9000埠的iframe:

<iframe src="http://server.com:9000"></iframe>

然後9000埠post一個message:

window.onload = function(){
            window.frames[0].postMessage("hello, this is from http://localhost:8000/", "http://server.com:9000/");
        }

postMessage執行的上下文必須是接收信息的window,傳遞兩個參數,第一個是數據,第二個是目標視窗。

同時,iframe即9000埠的頁面監聽message事件:

    window.addEventListener("message", receiveMessage);

    function receiveMessage(event){
        var origin = event.origin || event.originalEvent.origin; 
        //身份驗證
        if (origin !== "http://localhost:8000"){
              return;
        }
        console.log("receiveMessage: " + event.data); 
    }

這樣子iframe就可收到父視窗的信息了:

同理iframe也可以向父視窗發送消息:

window.parent.postMessage("hello, this is from http://server.com:9000", "http://localhost:8000");

父視窗收到:

window.postMessage也適用於通過window.open打開的子視窗,方法類似。

補充一點,如果iframe與父視窗是同源的,則父視窗可以直接獲取到iframe的內容,這個方法常用於無刷新上傳文件

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • jquery定時器不僅僅是定時器,定時器會在事件隊列穩定時開始運行。 所以我們可以在某些很快發生的事件裡加入定時器setTimeout來穩定我們的事件
  • 下麵是相關的應用與知識點: window.location.href window.top.location.replace("http://www.jb51.net") top.location.href("http://www.jb51.net") window.navigate ("http:
  • 如果系統的時間是2016年2月20日,分析下列JavaScript代碼,運行後在網頁上顯示() var now = new Date();var year = now.getFullYear();var month = now.getMonth();var date = now.getDate();
  • [1]寬高 [2]內邊距 [3]外邊距 [4]邊框
  • 使用jQuery選擇器不僅比使用傳統的getElementById()和getElementsByTagName()函數簡潔得多,而且還能避免某些錯誤。請看下麵例子: 1 <script> 2 document.getElementById("div").style.color ="red"; 3
  • position:屬性規定元素的定位類型。 取值:static | absolute | fixed | relative static : 預設值。無特殊定位 。對象遵循HTML定位規則。 absolute(絕對定位) : 將對象從文檔流中拖出(可層疊)。用left, right, top, bo
  • [1]line-height [2]font-size [3]vertical-align
  • Web 項目或多或少都會有涉及到什麼人員職稱樹,菜單樹,組織機構樹...... 歷手三四個項目有大有小,採用的樹前端都是 Ztree。 有些優秀的J2EE 框架將這些常用的組件都封裝起來,作為模塊化的組件提供給前端,這樣固然是好,開發效率會提升幾倍。 客戶需求是什麼,組件化往上一套,幾分鐘就能搭建起
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...