同源策略在web應用的安全模型中是一個重要概念。在這個策略下,web瀏覽器允許第一個頁面的腳本訪問第二個頁面里的數據,但是也只有在兩個頁面有相同的源時。源是由URI,主機名,端口號組合而成的。這個策略可以阻止一個頁面上的惡意腳本通過頁面的DOM對象獲得訪問另一個頁面上敏感信息的權限。
對于普遍依賴于cookie維護授權用戶session的現代瀏覽器來說,這種機制有特殊意義。客戶端必須在不同站點提供的內容之間維持一個嚴格限制,以防丟失數據機密或者完整性。
歷史
同源策略的概念要追溯到1995年的網景瀏覽器。同源策略作為一個重要的安全基石,所有的現代瀏覽器都在一定程度上實現了同源策略。同源策略雖然不是一個明確規范,但是經常為某些web技術(例如Microsoft Silverlight,Adobe Flash,Adobe Acrobat)或者某些機制(例如XMLHttpRequest)擴展定義大致兼容的安全邊界。
源決定規則
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服務器必須根據允許接受請求的白名單中的源列表比較頭數據。
個例以及異常
在一些個例中,例如哪些沒有明確定義主機名或者端口的協議(file:,data:,等),同源檢查以及相關機制如何運作沒有很好的定義。這在歷史上導致了很多安全問題,例如任意本地存儲的HTML文件不能訪問磁盤上的其他文件,也不能與任何網絡上的站點通信。
另外,很多遺留的跨域操作,早期是不受同源策略限制的,例如<script>
的跨域請求以及表單POST提交。
最后,某些類型的攻擊,例如DNS重新綁定,服務端代理,可以破壞主機名檢查,讓流氓頁面可以直接通過地址與站點通信,盡管地址不是同源的。這種攻擊的影響僅限于某些特殊情況下,例如,如果瀏覽器仍然相信正在通信的攻擊者的站點,然后沒有公開第三方cookie或者其他敏感信息給攻擊者。
變通方法
為了讓開發者可以在可控情況下繞過同源策略,一些"hacks"方法可以被用來在不同域的文檔之間傳輸數據,例如fragment identifier ,window.name
屬性。根據HTML5標準,一個postMessage
接口可以實現這樣的功能,但是只有最新的瀏覽器才支持。JSONP也可以用來保證通過類似Ajax的方式訪問跨域資源。
做好前端開發必須對HTTP的相關知識有所了解,所以我創建了一個專題前端必備HTTP技能專門收集前端相關的HTTP知識,歡迎關注,投稿。
PS:本文翻譯自維基百科,原文地址https://en.wikipedia.org/wiki/Same-origin_policy