安全圈 | 专注于最新网络信息安全讯息新闻

首页

跨域管道及其產生的安全問題

作者 recor 时间 2020-02-27
all

前言

最近在學習和挖洞的過程中碰到了一些因為涉及跨域而產生的安全問題,結合之前所學總結分享一下

同源策略

概念

同源策略是一種約定,它是瀏覽器最覈心也最基本的安全功能。以下特徵被稱之為同源

同源策略有兩種限制,第一種是限制了不同源之間的請求互動,例如在使用XMLHttpRequest或fetch函數時則會受到同源策略的約束。第二個限制是瀏覽器中不同源的框架之間是不能進行js的互動操作的。比如通過iframe和window.open產生的不同源的視窗。這兩種限制都有不同的解決方案,下麵會講解不同的解決方案和可能產生的安全問題。

注:

<a> <script> <img> <video> <link> company.com:81/index.html company.com/index.html

作用

有人可能一開始覺得同源策略多此一舉,但如果沒有同源策略會怎麼樣?如果沒有同源策略,所有頁面之間都可以相互讀取,javascript就擁有無窮的權利。

舉個例子。假設A頁面是一個很敏感的登入系統,現在受害者先訪問了我們偽造的B網頁,然後誘導其登入A網頁

為了假設沒有同源策略,我以aaa.evoa.me為頁面A和bbb.evoa.me為頁面B。現實中兩個不同子域默認不同源

aaa.evoa.me/login.php

bbb.evoa.me/evil.php

跨域資料傳輸的管道

document.domain

此方法針對的是同源策略的第二個限制,即不同視窗之間的同源限制。且此方法只能影響頂層網域名相同子功能變數名稱不同之間的同源規則。

不同子功能變數名稱之間默認不同源(如aaa.evoa.me與bbb.evoa.me),但是可以通過設定document.domain為相同的更高級功能變數名稱,來使不同子域同源。

aaa.evoa.me/1.php

bbb.evoa.me/2.php

通過修改document.domain

aaa.evoa.me/1.php

<iframe id='iframe' src="//bbb.evoa.me/2.php"></iframe> <script>document.domain = evoa.me</script>

bbb.evoa.me/2.php

注:

document.domain只可以被設定為他的當前域或其當前域的父域,比如aaa.evoa.me可以設定document.domain為aaa.evoa.me或evoa.me,但是不能設定為aaa.evoa.com或者bbb.aaa.evoa.me

document.domain的賦值操作會導致埠號被重寫為NULL,所以aaa.evoa.me僅設定document.domain為evoa.me並不能與evoa.me進行通信,evoa.me的頁面也必須賦值一次使雙方埠相同從而通過瀏覽器的同源檢測。這麼做的目的是,如果子域有XSS,那麼他的父域都存在安全隱患

設定document.domain並不會影響XMLHttpRequest或fetch的同源策略。

同一表單不同視窗之間(iframe中的或window.open打開的),是能够獲取到彼此的window對象的,如iframe.contentWindow可以獲取iframe的window對象,但是不同源的情况下這個window對象的大部分内容和方法是受限制的,如上圖alert函數一樣。下麵是火狐瀏覽器的可用window方法内容

iframe.contentWindow

如果某個子域為了和根域通信,根域設定了document.domain為根域,那麼其他子域如果有xss漏洞可以直接跨同源攻擊根域和同樣設定了document.domain的其他子域

window.name

window對象有個name内容,該内容有個特徵:即在一個視窗(window)的生命週期內,視窗載入的所有的頁面都是共亯一個window.name的,每個頁面對window.name都有讀寫的許可權,window.name是持久存在一個視窗載入過的所有頁面中的,並不會因新頁面的載入而進行重置。

舉個例子,頁面有個iframe,iframe中的頁面為A,無論iframe中的頁面A地址怎麼更改,這個iframe對象都是共亯同一個window.name,A頁面設定window.name,再將iframe的src設定為B頁面,B頁面中的JS腳本可以讀取到之前A頁面設定的window.name,簡而言之,window.name幾乎不受同源策略的影響

aaa.evoa.me/1.php

bbb.evoa.me/2.php

aaa.evoa.me/3.php

首先,我們訪問iframe中的name内容,瀏覽器返回了跨域訪問拒絕。但是我們通過設定iframe的src為3.php(3.php可以不與1.php同域),在iframe中的所有頁面共亯window.name。然後3.php中的腳本訪問到不同源的頁面2.php並獲取到了window.name

3.php

注:

所以,永遠不要把敏感數據存在window.name中,否則敏感數據可以被任何其他網頁的JS腳本獲取

location.hash

location.hash其實就是URL的錨部分(從#號開始的部分)

具體原理是改變hash並不會導致頁面刷新,所以可以利用hash值來進行數據傳遞。不同域下location.hash也是不能相互讀取的

具體做法是,A域的頁面a加載一個iframe,設定iframe的src為B域的b頁面+#傳輸給b的數據,此時b頁面的js腳本可以通過讀取location.hash獲得頁面a傳過來的數據,然後在b頁面再生成一個iframe,src指向A域的頁面c+#傳輸給a的數據,由於頁面c與頁面a同域同源,所以頁面c的腳本可以修改a的locaition.hash

B域的b页面+#传输给b的数据 A域的页面c+#传输给a的数据

由於此跨域方法比較麻煩且無比較直接的安全問題,此處不細講

PostMessage

window.postMessage()方法可以安全地實現跨源通信,被調用時,會在所有頁面腳本執行完畢之後向目標視窗派發一個MessageEvent消息。該函數的第一個參數為發送的消息,第二個參數是匹配發送給的視窗的url地址(可以使用*,代表無限制通配),若目標url和此參數不匹配,消息就不會被發送。

MessageEvent *

被接受視窗則可以通過監聽message事件來獲取接受資訊

例:子視窗向父視窗傳遞數據

aaa.evoa.me/1.php

bbb.evoa.me/2.php

如果事件監聽沒有判斷事件的來源,則會有很大的安全隱患,以下麵為例

evoa.me/1.php

<?php setcookie("flag","flag{this_is_flag}"); ?> <iframe id='iframe' src="//evoa.me/2.php"></iframe> <h1 id="name"></h1> <script> window.addEventListener('message',function(e){ document.getElementById('name').innerHTML = e.data; }) </script>

本來1.php應該接受來自2.php傳過來的數據,但由於監聽事件並沒有任何判斷,所以我們可以構造惡意網頁,構造iframe src指向evoa.me/1.php往裡面傳數據造成xss

evoa.me/1.php

evil.com/evil.php

如果正則設置不當,依舊可能造成安全隱患

evoa.me/1.php

<?php setcookie("flag","flag{this_is_flag}"); ?> <iframe id='iframe' src="//bbb.evoa.me/2.php"></iframe> <h1 id="name"></h1> <script> window.addEventListener('message',function(e){ if(/^http:\/\/.*evoa\.me$/.test(e.origin)) document.getElementById('name').innerHTML = e.data; }) </script>

正則設定有誤,我們可以購買功能變數名稱aaaevoa.me進行繞過

aaaevoa.me/evil.php

JSONP

上面講過<script>標籤可以跨域加載資源,但是返回內容如果不符合JS語法同樣無法獲取數據,JSONP則是通過返回符合JS語法的數據內容使資源能够跨域加載

<script>

aaa.evoa.me/1.php網站

bbb.evoa.me/2.php網站

<?php header('Content-type: application/javascript'); $func = $_REQUEST['func'] ?? "func"; $data = '["aaa","bbb","ccc","ddd"]'; echo $func . "(" . $data . ")"; ?>

即1.php頁面先設定好輸出數據的函數,通過<script>標籤請求2.php並帶有函數名參數,2.php把數據當函數參數傳入並根據函數名輸出對應函數調用語句,1.php獲得響應後自動調用函數即可獲取數據

1.php <script> 2.php 2.php 1.php

本來一個極其巧妙的資料傳輸管道,但如果配寘有問題,則可能產生安全隱患,假如一個沒有任何驗證的JSONP介面,用來傳輸用戶的敏感數據

evoa.me/2.php

evil.com/evil.php

如果未設定Content-type,會發生什麼?

evoa.me/2.php

未設定Conten-type可以導致反射性XSS

但是就算設定好了Conten-type也可能會有安全隱患,比如IE可以在Conten-type為application/json或application/javascript的情况下XSS,具體可以看下麵這篇文章

在application/json,application/javascript等Response下進行XSS

而且這種用戶完全可控點可以結合很多其他缺陷產生漏洞,所以這種介面還應該過濾非法字元

防禦方法:

CORS

上面說過瀏覽器的同源策略有兩種限制,CORS頭就是為了突破不同源之間的請求互動這一限制而產生的,

只需要HTTP返回

Access-Control-Allow-Origin: http://evil.com

Access-Control-Allow-Origin: http://evil.com

evil.com的跨域請求(XMLHttpRequest或fetch)的響應會被瀏覽器正確的返回

CORS的詳細內容可以看https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS

evoa.me/1.php

如果設定Access-Control-Allow-Origin: *

Access-Control-Allow-Origin: *

則所有的跨域訪問響應都會被允許。

如果請求需要帶上Cookie,則需要服務器設定Access-Control-Allow-Credentials: true

Access-Control-Allow-Credentials: true

否則瀏覽器將不會把響應內容返回給請求的發送者。

注:

如果設定Access-Control-Allow-Origin: *,則不管設沒設定Access-Control-Allow-Credentials: true,帶Cookie的請求都會失敗,這是瀏覽器的規定,若請求需帶上Cookie,Access-Control-Allow-Origin:不能使用*

Access-Control-Allow-Origin: * Access-Control-Allow-Credentials: true Access-Control-Allow-Origin:

如果輸出Access-Control-Allow-Origin採用正則的管道,正則編寫失誤的話很可能產生安全性漏洞

Access-Control-Allow-Origin

如下

evoa.me/1.php

由於正則沒有以$結尾,我們可以構造evoa.me.evil.com進行惡意訪問

一般來說只要正確配寘Access-Control-Allow-Origin就可以避免這些隱患,特別是在用規則運算式進行匹配的時候需尤為謹慎

Access-Control-Allow-Origin

“21442;”32771;

http://www.jianshu.com/p/7d23b48ff8b8

https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin策略

https://developer.mozilla.org/zh-CN/docs/Web/API/Document/domain

https://developer.mozilla.org/zh-CN/docs/Web/API/Window/PostMessage

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS控制訪問

http://www.secpulse.com/archives/56637.html

http://www.anquanke.com/post/id/97671