1.7 xss之同源策略與跨域訪問


同源策略:

  • 同源策略

在web應用的安全模型中是一個重要概念。在這個策略下,web瀏覽器允許第一個頁面的腳本訪問第二個頁面里的數據,但是也只有在兩個頁面有相同的源時。源是由URI,主機名,端口號組合而成的。這個策略可以阻止一個頁面上的惡意腳本通過頁面的DOM對象獲得訪問另一個頁面上敏感信息的權限。

對於普遍依賴於cookie維護授權用戶session的現代瀏覽器來說,這種機制有特殊意義。客戶端必須在不同站點提供的內容之間維持一個嚴格限制,以防丟失數據機密或者完整性。

  • 源決定規則

RFC6454中有定義URI源的算法定義。對於絕對的URIs,源就是{協議,主機,端口}定義的。只有這些值完全一樣才認為兩個資源是同源的。

為了舉例,下面的表格給出了與URL" http://www.example.com/dir/page.html " 的對比。

對比URL 結果 結果
http://www.example.com/dir/page2.html 同源 相同的協議,主機,端口
http://www.example.com/dir2/other.html 同源 相同的協議,主機,端口
http://username:password@www.example.com/dir2/other.html 同源 相同的協議,主機,端口
http://www.example.com:81/dir/other.html 不同源 相同的協議,主機,端口不同
https://www.example.com/dir/other.html 不同源 協議不同
http://en.example.com/dir/other.html 不同源 不同主機
http://example.com/dir/other.html 不同源 不同主機(需要精確匹配)
http://v2.www.example.com/dir/other.html 不同源 不同主機(需要精確匹配)
http://www.example.com:80/dir/other.html 看情況 端口明確,依賴瀏覽器實現

不像其他瀏覽器,IE在計算源的時候沒有包括端口。

  • 安全考量
有這種限制的主要原因就是如果沒有同源策略將導致安全風險。假設用戶在訪問銀行網站,並且沒有登出。然后他又去了任意的其他網站,剛好這個網站有惡意的js代碼,在后台請求銀行網站的信息。因為用戶目前仍然是銀行站點的登陸狀態,那么惡意代碼就可以在銀行站點做任意事情。例如,獲取你的最近交易記錄,創建一個新的交易等等。因為瀏覽器可以發送接收銀行站點的session cookies,在銀行站點域上。訪問惡意站點的用戶希望他訪問的站點沒有權限訪問銀行站點的cookie。當然確實是這樣的,js不能直接獲取銀行站點的session cookie,但是他仍然可以向銀行站點發送接收附帶銀行站點session cookie的請求,本質上就像一個正常用戶訪問銀行站點一樣。關於發送的新交易,甚至銀行站點的CSRF(跨站請求偽造)防護都無能無力,因為腳本可以輕易的實現正常用戶一樣的行為。所以如果你需要session或者需要登陸時,所有網站都面臨這個問題。如果上例中的銀行站點只提供公開數據,你就不能觸發任意東西,這樣的就不會有危險了,這些就是同源策略防護的。當然,如果兩個站點是同一個組織的或者彼此互相信任,那么就沒有這種危險了。
  • 規避同源策略
在某些情況下同源策略太嚴格了,給擁有多個子域的大型網站帶來問題。下面就是解決這種問題的技術:
document.domain屬性

如果兩個window或者frames包含的腳本可以把domain設置成一樣的值,那么就可以規避同源策略,每個window之間可以互相溝通。例如,orders.example.com下頁面的腳本和 catalog.example.com下頁面的腳本可以設置他們的 document.domain 屬性為 example.com,從而讓這兩個站點下面的文檔看起來像在同源下,然后就可以讓每個文檔讀取另一個文檔的屬性。這種方式也不是一直都有用,因為端口號是在內部保存的,有可能被保存成null。換句話說,example.com 的端口號80,在我們更新 document.domain 屬性的時候可能會變成null。為null的端口可能不被認為是80,這主要依賴瀏覽器實現。

跨域資源共享

這種方式使用了一個新的Origin請求頭和一個新的Access-Control-Allow-Origin響應頭擴展了HTTP。允許服務端設置Access-Control-Allow-Origin頭標識哪些站點可以請求文件,或者設置Access-Control-Allow-Origin頭為"*",允許任意站點訪問文件。瀏覽器,例如Firefox3.5,Safari4,IE10使用這個頭允許跨域HTTP請求。

跨文檔通信

這種方式允許一個頁面的腳本發送文本信息到另一個頁面的腳本中,不管腳本是否跨域。在一個window對象上調用 postMessage()會異步的觸發window上的onmessage事件,然后觸發定義好的事件處理方法。一個頁面上的腳本仍然不能直接訪問另外一個頁面上的方法或者變量,但是他們可以安全的通過消息傳遞技術交流。

JSONP

JOSNP允許頁面接受另一個域的JSON數據,通過在頁面增加一個可以從其它域加載帶有回調的JSON響應的<script>標簽。

WebSocket

現代瀏覽器允許腳本直連一個WebSocket地址而不管同源策略。然而,使用WebSocket URI的時候,在請求中插入Origin頭就可以標識腳本請求的源。為了確保跨站安全,WebSocket服務器必須根據允許接受請求的白名單中的源列表比較頭數據。

 

  •  不受同源策略限制的:

1、頁面中的鏈接,重定向以及表單提交是不會受到同源策略限制的。
2、跨域資源的引入是可以的。但是js不能讀寫加載的內容。如嵌入到頁面中的<script src="..."></script>,<img>,<link>,<iframe>等。

 

跨域:

  • 什么是跨域:

瀏覽器從一個域名的網頁去請求另一個域名的資源時,域名、端口、協議任一不同,都是跨域

  1. 域名:

主域名不同  http://www.baidu.com/index.html    –>     http://www.sina.com/test.js
子域名不同  http://www.666.baidu.com/index.html    –>    http://www.555.baidu.com/test.js
域名和域名ip http://www.baidu.com/index.html  –>   http://180.149.132.47/test.js

  1. 端口: 

http://www.baidu.com:8080/index.html    –>   http://www.baidu.com:8081/test.js

  1. 協議:

http://www.baidu.com:8080/index.html   –>   https://www.baidu.com:8080/test.js

  1. 備注:

1、端口和協議的不同,只能通過后台來解決
2、localhost和127.0.0.1雖然都指向本機,但也屬於跨域

  •  如何解決跨域問題:
  1.  通過jsonp跨域

JSONP(JSON with Padding:填充式JSON),應用JSON的一種新方法,
JSON、JSONP的區別:
1、JSON返回的是一串數據、JSONP返回的是腳本代碼(包含一個函數調用)
2、JSONP 只支持get請求、不支持post請求
(類似往頁面添加一個script標簽,通過src屬性去觸發對指定地址的請求,故只能是Get請求)

 

  1. 代理:

  www.baidu.com/index.html需要調用www.sina.com/server.php,可以寫一個接口www.baidu.com/server.php,由這個接口在后端去調用www.sina.com/server.php並拿到返回值,然后再返回給index.html

  1. PHP端修改header

  header(‘Access-Control-Allow-Origin:*’);//允許所有來源訪問
  header(‘Access-Control-Allow-Method:POST,GET’);//允許訪問的方式

  1. document.domain

跨域分為兩種,一種xhr不能訪問不同源的文檔,另一種是不同window之間不能進行交互操作;
  document.domain主要是解決第二種情況,且只能適用於主域相同子域不同的情況;
  document.domain的設置是有限制的,我們只能把document.domain設置成自身或更高一級的父域,且主域必須相同。例如:a.b.example.com中某個文檔的document.domain可以設成a.b.example.com、b.example.com 、example.com中的任意一個,但是不可以設成c.a.b.example.com,因為這是當前域的子域,也不可以設成baidu.com,因為主域已經不相同了。
兼容性:所有瀏覽器都支持;
優點:
 可以實現不同window之間的相互訪問和操作;
缺點:
 只適用於父子window之間的通信,不能用於xhr;
 只能在主域相同且子域不同的情況下使用;
使用方式:
 不同的框架之間是可以獲取window對象的,但卻無法獲取相應的屬性和方法。比如,有一個頁面,它的地址是http://www.example.com/a.html , 在這個頁面里面有一個iframe,它的src是http://example.com/b.html, 很顯然,這個頁面與它里面的iframe框架是不同域的,所以我們是無法通過在頁面中書寫js代碼來獲取iframe中的東西的:

 

<script type="text/javascript">
    function test(){
        var iframe = document.getElementById('ifame');
        var win = document.contentWindow;//可以獲取到iframe里的window對象,但該window對象的屬性和方法幾乎是不可用的
        var doc = win.document;//這里獲取不到iframe里的document對象
        var name = win.name;//這里同樣獲取不到window對象的name屬性
    }
</script>
<iframe id = "iframe" src="http://example.com/b.html" onload = "test()"></iframe>

 

這個時候,document.domain就可以派上用場了,我們只要把http://www.example.com/a.html http://example.com/b.html這兩個頁面的document.domain都設成相同的域名就可以了。
1.在頁面 http://www.example.com/a.html 中設置document.domain:

 

<iframe id = "iframe" src="http://example.com/b.html" onload = "test()"></iframe>
<script type="text/javascript">
    document.domain = 'example.com';//設置成主域
    function test(){
        alert(document.getElementById('iframe').contentWindow);//contentWindow 可取得子窗口的 window 對象
    }
</script>

 

2.在頁面 http://example.com/b.html 中也設置document.domain:

 

<script type="text/javascript">
    document.domain = 'example.com';//在iframe載入這個頁面也設置document.domain,使之與主頁面的document.domain相同
</script>

 

  1. window.name

關鍵點:window.name在頁面的生命周期里共享一個window.name;
兼容性:所有瀏覽器都支持;
優點:
 最簡單的利用了瀏覽器的特性來做到不同域之間的數據傳遞;
 不需要前端和后端的特殊配制;
缺點:
 大小限制:window.name最大size是2M左右,不同瀏覽器中會有不同約定;
 安全性:當前頁面所有window都可以修改,很不安全;
 數據類型:傳遞數據只能限於字符串,如果是對象或者其他會自動被轉化為字符串,如下;
這里寫圖片描述
使用方式:修改window.name的值即可;

  1. postMessage

關鍵點:
 postMessage是h5引入的一個新概念,現在也在進一步的推廣和發展中,他進行了一系列的封裝,我們可以通過window.postMessage的方式進行使用,並可以監聽其發送的消息;
兼容性:移動端可以放心用,但是pc端需要做降級處理
優點
 不需要后端介入就可以做到跨域,一個函數外加兩個參數(請求url,發送數據)就可以搞定;
 移動端兼容性好;
缺點
 無法做到一對一的傳遞方式:監聽中需要做很多消息的識別,由於postMessage發出的消息對於同一個頁面的不同功能相當於一個廣播的過程,該頁面的所有onmessage都會收到,所以需要做消息的判斷;
安全性問題:三方可以通過截獲,注入html或者腳本的形式監聽到消息,從而能夠做到篡改的效果,所以在postMessage和onmessage中一定要做好這方面的限制;
 發送的數據會通過結構化克隆算法進行序列化,所以只有滿足該算法要求的參數才能夠被解析,否則會報錯,如function就不能當作參數進行傳遞;
使用方式:通信的函數,sendMessage負責發送消息,bindEvent負責消息的監聽並處理,可以通過代碼來做一個大致了解;

 

Storage.prototype.sendMessage_ = function(type, params, fn) {
    if (this.topWindow) {
        this.handleCookie_(type, params, fn);
        return;
    }
    var eventId = this.addToQueue_(fn, type);
    var storageIframe = document.getElementById('mip-storage-iframe');
    var element = document.createElement("a");
    element.href = this.origin;
    var origin = element.href.slice(0, element.href.indexOf(element.pathname) + 1);
    storageIframe.contentWindow.postMessage({
        type: type,
        params: params,
        eventId: eventId
    }, origin);
}
Storage.prototype.bindEvent_ = function() {
    window.onmessage = function (res) {
        // 判斷消息來源
        if (window == res.source.window.parent &&
            res.data.type === this.messageType.RES &&
            window.location.href.match(res.origin.host).length > 0) {
            var fn = this.eventQueue[res.data.eventId];
            fn && fn();
            delete this.eventQueue[res.data.eventId];
            // reset id
            var isEmpty = true;
            for (var t in this.eventQueue) {
                isEmpty = false;
            }
            if (isEmpty) {
                this.id = 0;
            }
        }
    }.bind(this);
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM