同源策略(Same Origin policy,SOP),也稱為單源策略(Single Origin policy),它是一種用于Web瀏覽器編程語言(如JavaScript和Ajax)的安全措施,以保護信息的保密性和完整性。
同源策略能阻止網站腳本訪問其他站點使用的腳本,同時也阻止它與其他站點腳本交互。
| 原始資源 | 要訪問的資源 | 非IE瀏覽器 | IE瀏覽器 |
|---|---|---|---|
| http://example.com/a/ | http://example.com/b/ | 可以訪問 | 可以訪問 |
| http://example.com/ | http://www.example.com/ | 主機不匹配 | 主機不匹配 |
| http://example.com/a/ | https://example.com/a/ | 協議不匹配 | 協議不匹配 |
| http://example.com:81/ | http://example.com/ | 端口不匹配 | 可以訪問 |
同源策略一開始是為了管理DOM之間的訪問,后來逐漸擴展到Javascript對象,但并非是全部。
例如非同源的腳本之間可以調用location.assign()和location.replace()。
同源策略在提高了安全性,但同時也降低了靈活性。
例如很難將login.example.com與payments.example.com兩個域之間的數據可以方便的傳送。
介紹兩種解決方式:document.domain和postMessage()。
javascript允許子域之間使用頂級域名。
例如login.example.com和payments.example.com都可以進行如下設置:
document.domain="example.com"
設置這個屬性之后,子域之間可以方便的通信,需注意的是協議和端口號必須相同。
| 原始資源 | 訪問的資源 | 結果 | ||
| URL | document.domain | URL | document.domain | |
| http://www.example.com/ | example.com | http://payments.example.com/ | example.com | 可以訪問 |
| http://www.example.com/ | example.com | https://payments.example.com/ | example.com | 協議不匹配 |
| http://payments.example.com | example.com | http://example.com/ | (不設置) | 拒絕訪問 |
| http://www.example.com/ | (不設置) | http://www.example.com | example.com | 拒絕訪問 |
postMessage()是HTML5的一個API接口,由于比較新,所以在IE6和IE7中不支持。 1 向另外一個iframe發送消息:
var message = 'Hello' + (new Date().getTime());
window.parent.frames[1].postMessage(message, '*');
iframe1.html需要向iframe2.html發送消息,也就是第二個iframe,所以是window.parent.frames[1]。
如果是向父頁面發送消息就是window.parent。
postMessage這個函數接收二個參數,缺一不可,第一個參數即你要發送的數據。
第二個參數是非常重要,主要是出于安全的考慮,一般填寫允許通信的域名。
這里為了簡化,所以使用’*',即不對訪問的域進行判斷。
2 另外一個iframe監聽消息事件:
iframe2.html中寫個監聽message事件,當有消息傳到iframe2.html時就會觸發這個事件。
var onmessage = function(e) {
var data = e.data,p = document.createElement('p');
p.innerHTML = data;
document.getElementById('display').appendChild(p);
};
//監聽postMessage消息事件
if (typeof window.addEventListener != 'undefined') {
window.addEventListener('message', onmessage, false);
} else if (typeof window.attachEvent != 'undefined') {
window.attachEvent('onmessage', onmessage);
}
如果你有加域名限,比如下面的代碼:
window.parent.frames[1].postMessage(message, 'http://www.test.com');
就要在onmessage中追加個判斷:
if(event.origin !== 'http://www.test.com') return;
一個簡單的同步XMLHttpRequest請求:
var x = new XMLHttpRequest();
x.open("POST", "/some_script.cgi", false);
x.setRequestHeader("X-Random-Header", "Hi mom!");
x.send("...POST payload here...");
alert(x.responseText);
XMLHttpRequest請求嚴格遵守同源策略,非同源不可以請求。
這個API也做過很多測試與改進,下面列出之前的測試方法:
var x = new XMLHttpRequest();
x.open("POST", "http://www.example.com/", false);
// 定義發送內容長度為7
x.setRequestHeader("Content-Length", "7");
// 構造的http請求。
x.send(
"Gotcha!\n" +
"GET /evil_response.html HTTP/1.1\n" +
"Host: www.bunnyoutlet.com\n\n"
);
現在的瀏覽器都不存在上面的隱患,包括基本都禁用了TRACE方法,防止httponly的cookie泄漏問題等。
Web Storage是由Mozilla的工程師在Firefox1.5中加入的,并且加入了HTML5中,現在的瀏覽器都支持,除了IE6與IE7。
JavaScript可以通過localStorage與sessionStorage對Web Storage進行創建,檢索和刪除:
localStorage.setItem("message", "Hi mom!");
alert(localStorage.getItem("message"));
localstorage.removeItem("message");
localStorage對象可以長時間保存,并且遵守同源策略。
但是在IE8中localStorage會把域名相同但是協議分別為HTTP和HTTPS的內容放在一起,IE9中已修改。
在Firefox中,localStorage沒有問題,但是sessionStorage也是會把域名相同的HTTP與HTTPS放在一起。
設置Cookie總結
|
在foo.example.com設置cookie,domain設置為: |
最終cookie的范圍 |
|
|
非IE瀏覽器 |
IE瀏覽器 |
|
|
設置為空 |
foo.example.com(一個域) |
*.foo.example.com |
|
bar.foo.example.com |
cookie設置失敗,設置的域是當前域的一個子域 |
|
|
foo.example.com |
*.foo.example.com |
|
|
baz.example.com |
cookie設置失敗,域名不匹配 |
|
|
example.com |
*.example.com |
|
|
ample.com |
cookie設置失敗,域名不匹配 |
|
|
.com |
設置失敗,域名太廣,存在安全風險。 |
|
Cookie中的path參數可以設定指定目錄的cookie。
例如設定domain為example.com,path為/some/path/ 在訪問下面url的時候會帶上設定的cookie:
http://foo.example.com/some/path/subdirectory/hello_world.txt
存在一定的安全風險,因為path的設定沒有考慮到同源策略。
httponly屬性可以防止通過document.cookie的API訪問設定的cookie。
secure屬性設定后只有在通過https傳輸時才會帶上設定的cookie,可以防止中間人攻擊。
AllowScriptAccess參數:用來控制flash通過ExternallInterface.call()函數調用javascript的時的限制。
有三個值:always,never和sameorigin,最后一個值只允許同域的JavaScript操作(08年之前默認為always,現在默認為sameorigin)。
AllowNetworking參數:用來控制flash與外部的網絡通訊。
可選的值為:all(允許使用所有的網絡通訊,默認值),internal(flash不能與瀏覽器通訊如navigateToURL,但是可以調用其他的API),none(禁止任何的網絡通訊)
由于本地文件都是通過file:協議進行訪問的,由于不存在host,所以無法遵循同源策略。
所以本地保存的一個HTML文件,在瀏覽器中通過file:協議訪問后,可以通過XMLHttpRequest或DOM對本地其他文件進行操作。
與此同時,也可以對互聯網的其他資源做同樣的操作。各瀏覽器廠商意識到這個問題,并努力做了修改:
測試代碼:
1.html(1.txt隨機寫一些字符串即可)
<script>
function createXHR(){
return window.XMLHttpRequest?
new XMLHttpRequest():
new ActiveXObject("Microsoft.XMLHTTP");
}
function getlocal(url){
xmlHttp = createXHR();
xmlHttp.open("GET",url,false);
xmlHttp.send();
result = xmlHttp.responseText;
return result;
}
function main(){
url = "file://路徑/1.txt";
alert(url);
result = getlocal(url);
alert(result);
}
main();
</script>
結論:
1 Chrome瀏覽器(使用WebKit內核的瀏覽器)
完全禁止跨文檔的XMLHttpRequest和DOM操作,并禁止了document.cookie和<meta http-equiv="Set-Cookie" ...>的操作。
2 Firefox
允許訪問同目錄與子目錄里的文件。也可通過document.cookie與<meta http- equiv="Set-Cookie" ...>設定cookie,file:協議下cookie共享,storage也是。
3 IE7及以上
允許本地文件之間的訪問,但是在執行JavaScript之前會有一個提示,用戶點擊通過之后可以執行,cookie域Firefox類似,但是file:協議下不支持storage。
4 IE6
允許本地文件的訪問,同時也允許對http協議的訪問,cookie也是一樣。
一些web應用用到了偽URL例如about:,javascript:,和data:來創建HTML文檔。
這種方法是為了不需要再與服務器通信,可以節約時間更快的響應,但是也帶進了很多安全隱患。
about:blank
about協議在現在的瀏覽器中有很多用途,但是其中大部分不是為了獲取正常的頁面。
about:blank這個URL可以用來被創建DOM對象,例如:
<iframe src="about:blank" name="test"></iframe>
<script>
frames["test"].document.body.innerHTML = "<h1>Hi!</h1>";
</script>
在瀏覽器中,創建一個about:blank頁面,它繼承的域為創建它的頁面的域。
例如,點擊一個鏈接,提交一個表單,創建一個新窗口,但是當用戶手動輸入about:或者書簽中打開的話,他的域是一個特殊的域,任何其他的頁面都不可以訪問。
data:協議
data:協議是設計用來放置小數據的,例如圖標之類的,可以減少http請求數量,例如:
<img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEBLAEsAAD...">
用以下代碼研究域的問題:
<iframe src="data:text/html;charset=utf-8,<script>alert(document.domain)</script>" >
在Chrome與Safari中,所有的data:都會賦予一個單獨的,不可獲取的域,而不是從父域中繼承的。
Firefox與Opera中,域是繼承于當前頁面。
IE8之前的版本不支持data:協議。
javascript:和vbscript:
javascript:協議允許后面執行javascript代碼,并且繼承了調用的當前域。
有些情況會對后面的內容處理兩次,如果代碼正確的話,會把后面的代碼當成html解析,覆蓋掉原來的html代碼:
<iframe src='javascript:"<b>2 + 2 = " + (2+2) + "</b>"'>
</iframe>