今天了解了一些有關(guān)瀏覽器同源策略以及CSRF攻擊之后潮太,覺(jué)得挺有意思的,所以特此總結(jié)一波虾攻,給自己囤一點(diǎn)干貨
這篇文章主要包含三個(gè)大的方面:
1.同源策略是什么及其作用
2.如何繞過(guò)同源策略
3.CSEF攻擊及防御
1.同源策略(same-origin policy):
1.1 什么叫做同源:
同源是針對(duì)域名來(lái)說(shuō)的铡买,對(duì)于任意的域名,只要他們的協(xié)議(HTTP,HTTPS)一致霎箍,主機(jī)一致奇钞,端口號(hào)一致,就可以將其認(rèn)為是同源的漂坏。
1.2 那么同源策略的作用是什么景埃?
** 說(shuō)到作用,那么就不可避免地要提及一下為什么會(huì)有這個(gè)策略的產(chǎn)生吧**
引用一段阮一峰大神的描述:
同源政策的目的顶别,是為了保證用戶信息的安全谷徙,防止惡意的網(wǎng)站竊取數(shù)據(jù)。
設(shè)想這樣一種情況:A網(wǎng)站是一家銀行筋夏,用戶登錄以后蒂胞,又去瀏覽其他網(wǎng)站。如果其他網(wǎng)站可以讀取A網(wǎng)站的 Cookie条篷,會(huì)發(fā)生什么骗随?
很顯然蛤织,如果 Cookie 包含隱私(比如存款總額),這些信息就會(huì)泄漏鸿染。更可怕的是指蚜,Cookie 往往用來(lái)保存用戶的登錄狀態(tài),如果用戶沒(méi)有退出登錄涨椒,其他網(wǎng)站就可以冒充用戶摊鸡,為所欲為。因?yàn)闉g覽器同時(shí)還規(guī)定蚕冬,提交表單不受同源政策的限制免猾。
由此可見,"同源政策"是必需的囤热,否則 Cookie 可以共享猎提,互聯(lián)網(wǎng)就毫無(wú)安全可言了。
再來(lái)一段Wikipedia上的描述:
In computing, the same-origin policy is an important concept in the web application security model. Under the policy, a web browser permits scripts contained in a first web page to access data in a second web page, but only if both web pages have the same origin. An origin is defined as a combination of URI scheme, hostname, and port number.This policy prevents a malicious script on one page from obtaining access to sensitive data on another web page through that page's Document Object Model.
This mechanism bears a particular significance for modern web applications that extensively depend on HTTP cookies to maintain authenticated user sessions, as servers act based on the HTTP cookie information to reveal sensitive information or take state-changing actions. A strict separation between content provided by unrelated sites must be maintained on the client-side to prevent the loss of data confidentiality or integrity.
看了這么多的描述之后旁蔼,我也來(lái)一個(gè)自己總結(jié)的吧锨苏。
我認(rèn)為同源策略之所以出現(xiàn),就是為了提高在瀏覽器上網(wǎng)的安全性棺聊。這里的 安全性
主要是針對(duì) cookie
來(lái)說(shuō)的伞租。因?yàn)?cookie
保存著我們每個(gè)人的上網(wǎng)信息,甚至包含一些涉及個(gè)人重要隱私的信息限佩,而沒(méi)有同源策略之前葵诈,我們的這些信息,也即 cookie
很容易被他人利用祟同,去做一些羞羞的事情驯击,所以為了保證我們每個(gè)網(wǎng)民能夠安全的上網(wǎng),這個(gè)策略就應(yīng)用而生啦耐亏。(在這里,我要對(duì)NetScape說(shuō)一聲感謝)
** 那么沪斟,回歸正題广辰,這個(gè)同源策略的具體作用都有哪些呢? **
一旦我們進(jìn)行非同源的跨站訪問(wèn)主之,那么為了安全起見择吊,在這個(gè)過(guò)程中,有一些數(shù)據(jù)的傳輸會(huì)受到限制槽奕。比如:
- Cookie几睛、LocalStorage 和 IndexDB 無(wú)法讀取。
- DOM無(wú)法獲得
- AJAX請(qǐng)求不能發(fā)送
這樣一來(lái)粤攒,雖然看起來(lái)我們的安全性被牢牢地掌握在自己手中所森,但是自己的手腳也多多少少受到了一些限制囱持,什么生意呢?如果在我們自己搞的東西中焕济,真的需要進(jìn)行跨域訪問(wèn)呢纷妆?難道當(dāng)初NetScape在設(shè)計(jì)的時(shí)候就沒(méi)考慮到這一點(diǎn)嗎?
放心晴弃,其實(shí)是有辦法的掩幢。
2.那么都有哪些辦法能夠繞過(guò)同源策略來(lái)實(shí)現(xiàn)相應(yīng)的功能呢?
對(duì)于Cookie的傳輸:
1.1 如果我們現(xiàn)在有兩個(gè)網(wǎng)頁(yè)上鞠,它們一級(jí)域名一致际邻,只是二級(jí)域名不同,那么如果要做到使這兩個(gè)域名能夠?qū)崿F(xiàn)cookie的共享芍阎,只需要將這兩個(gè)頁(yè)面的document.domain
設(shè)置為一級(jí)域名即可世曾。
比如 頁(yè)面一http://xiao.haha.com/a.html
頁(yè)面二http://qian.haha.com/b.html
那么只需要將document.domain = haha.com
即可
1.2 對(duì)于兩個(gè)毫無(wú)關(guān)系的頁(yè)面,如果要進(jìn)行cookie的傳輸能曾,那么需要借助標(biāo)簽<img>
<script>
<iframe>
即可(這里就是CSRF攻擊的起源之地)對(duì)于DOM的傳輸:
2.1 片段識(shí)別符(fragment identifier)
2.2 window.name
2.3 跨文檔通信API(Cross-document messaging)
3.對(duì)于AJAX請(qǐng)求:
3.1 利用JSONP通信標(biāo)準(zhǔn)
其基本思想是既然AJAX已經(jīng)被廢掉了度硝,那么就換一種還可以繼續(xù)請(qǐng)求的辦法。于是就想到了可以利用 <script>
標(biāo)簽來(lái)向跨源地址發(fā)起請(qǐng)求寿冕。
示例代碼
function addScriptTag(src) {
var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = src;
document.body.appendChild(script);
}
window.onload = function () {
addScriptTag('http://example.com/ip?callback=foo');
}
function foo(data) {
console.log('Your public IP address is: ' + data.ip);
};
上面代碼通過(guò)動(dòng)態(tài)添加 <script>
元素蕊程,向服務(wù)器 example.com
發(fā)出請(qǐng)求。注意驼唱,該請(qǐng)求的查詢字符串有一個(gè) callback
參數(shù)藻茂,用來(lái)指定回調(diào)函數(shù)的名字,這對(duì)于JSONP是必需的玫恳。
服務(wù)器收到這個(gè)請(qǐng)求以后辨赐,會(huì)將數(shù)據(jù)放在回調(diào)函數(shù)的參數(shù)位置返回。
foo({
"ip": "8.8.8.8"
});
由于 <script>
元素請(qǐng)求的腳本京办,直接作為代碼運(yùn)行掀序。這時(shí),只要瀏覽器定義了foo函數(shù)惭婿,該函數(shù)就會(huì)立即調(diào)用不恭。作為參數(shù)的JSON數(shù)據(jù)被視為JavaScript對(duì)象,而不是字符串财饥,因此避免了使用 JSON.parse
的步驟换吧。
3.2 利用WebSocket協(xié)議
WebSocket是一種通信協(xié)議,使用ws://(非加密)和wss://(加密)作為協(xié)議前綴钥星。該協(xié)議不實(shí)行同源政策沾瓦,只要服務(wù)器支持,就可以通過(guò)它進(jìn)行跨源通信。
示例如下:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Origin: http://example.com/
在上邊的示例中贯莺,最后一個(gè)屬性 origin
是重點(diǎn)风喇。該字段表示請(qǐng)求的請(qǐng)求源,然后瀏覽器根據(jù)該地址乖篷,判斷是否處在白名單中响驴,如果處在,則可以進(jìn)行本次通信撕蔼,反之不可豁鲤。
3.3 利用CORS(跨源資源分享)標(biāo)準(zhǔn)
CORS是一個(gè)W3C標(biāo)準(zhǔn),全稱是"跨域資源共享"(Cross-origin resource sharing)鲸沮。
它允許瀏覽器向跨源服務(wù)器琳骡,發(fā)出XMLHttpRequest請(qǐng)求,從而克服了AJAX只能同源使用的限制讼溺。
瀏覽器將CORS請(qǐng)求分成兩類:簡(jiǎn)單請(qǐng)求(simple request)和非簡(jiǎn)單請(qǐng)求(not-so-simple request)楣号。
只要同時(shí)滿足以下兩大條件,就屬于簡(jiǎn)單請(qǐng)求怒坯。
- 請(qǐng)求方法是以下三種方法之一:
1.HEAD
2.GET
3.POST - HTTP的頭信息不超出以下幾種字段:
1.Accept
2.Accept-Language
3.Content-Language
4.Last-Event-ID
5.Content-Type:只限于三個(gè)值application/x-www-form-urlencoded炫狱、multipart/form-data、text/plain
** 對(duì)于簡(jiǎn)單請(qǐng)求: **
瀏覽器直接發(fā)出CORS請(qǐng)求剔猿。具體來(lái)說(shuō)视译,就是在頭信息之中,增加一個(gè)Origin字段归敬。
如果Origin指定的源酷含,不在許可范圍內(nèi),服務(wù)器會(huì)返回一個(gè)正常的HTTP回應(yīng)汪茧。瀏覽器發(fā)現(xiàn)椅亚,這個(gè)回應(yīng)的頭信息沒(méi)有包含Access-Control-Allow-Origin字段(詳見下文),就知道出錯(cuò)了舱污,從而拋出一個(gè)錯(cuò)誤呀舔,被XMLHttpRequest的onerror回調(diào)函數(shù)捕獲。注意扩灯,這種錯(cuò)誤無(wú)法通過(guò)狀態(tài)碼識(shí)別别威,因?yàn)镠TTP回應(yīng)的狀態(tài)碼有可能是200。
** 對(duì)于非簡(jiǎn)單請(qǐng)求: **
非簡(jiǎn)單請(qǐng)求是那種對(duì)服務(wù)器有特殊要求的請(qǐng)求驴剔,比如請(qǐng)求方法是PUT或DELETE,或者Content-Type字段的類型是application/json粥庄。
非簡(jiǎn)單請(qǐng)求的CORS請(qǐng)求丧失,會(huì)在正式通信之前,增加一次HTTP查詢請(qǐng)求惜互,稱為"預(yù)檢"請(qǐng)求(preflight)布讹。
瀏覽器先詢問(wèn)服務(wù)器琳拭,當(dāng)前網(wǎng)頁(yè)所在的域名是否在服務(wù)器的許可名單之中,以及可以使用哪些HTTP動(dòng)詞和頭信息字段描验。只有得到肯定答復(fù)白嘁,瀏覽器才會(huì)發(fā)出正式的XMLHttpRequest請(qǐng)求,否則就報(bào)錯(cuò)膘流。
** 注: CORS協(xié)議支持所有的HTTP請(qǐng)求絮缅,而JSONP只支持GET請(qǐng)求,所以在這一點(diǎn)上呼股,CORS協(xié)議要強(qiáng)大的多耕魄。 **
3.CSRF攻擊又是啥子武器呢?
3.1 什么是CSRF攻擊彭谁?
CSRF(Cross-site request forgery)吸奴,中文名稱:跨站請(qǐng)求偽造,也被稱為:one click attack/session riding缠局,縮寫為:CSRF/XSRF则奥。
3.2 CSRF攻擊的原理?
利用cookie狭园,盜用身份從而進(jìn)行邪惡活動(dòng)读处。
具體的作案手段都有哪些呢?
示例1:
銀行網(wǎng)站A妙啃,它以GET請(qǐng)求來(lái)完成銀行轉(zhuǎn)賬的操作档泽,如:http://www.mybank.com/Transfer.php?toBankId=11&money=1000
危險(xiǎn)網(wǎng)站B,它里面有一段HTML的代碼如下,便會(huì)成功揖赴。
<img src=http://www.mybank.com/Transfer.php?toBankId=11&money=1000>
** 由于同源策略對(duì)img標(biāo)簽沒(méi)有限制馆匿,所以向在img標(biāo)簽中的src地址發(fā)起請(qǐng)求時(shí),會(huì)直接將用戶的cookie傳遞過(guò)去燥滑,從而盜取用戶身份進(jìn)行非法操作渐北。 **
示例2:
為了杜絕上面的問(wèn)題,銀行決定改用POST請(qǐng)求完成轉(zhuǎn)賬操作铭拧。
銀行網(wǎng)站A的WEB表單如下:
<form action="Transfer.php" method="POST">
<p>ToBankId: <input type="text" name="toBankId" /></p>
<p>Money: <input type="text" name="money" /></p>
<p><input type="submit" value="Transfer" /></p>
</form>
后臺(tái)處理頁(yè)面Transfer.php如下:
<?php
session_start();
if (isset($_REQUEST['toBankId'] && isset($_REQUEST['money']))
{
buy_stocks($_REQUEST['toBankId'], $_REQUEST['money']);
}
?>
危險(xiǎn)網(wǎng)站B赃蛛,仍然只是包含那句HTML代碼,同樣也會(huì)成功搀菩。
<img src=http://www.mybank.com/Transfer.php?toBankId=11&money=1000>
** 為什么這次還可以呢呕臂,這個(gè)鍋就得PHP背了,PHP在后端用的是$_REQUEST來(lái)獲取參數(shù)肪跋,那么它既可獲取GET參數(shù)歧蒋,也可以得到POST。所以在這里,依然用的是GET請(qǐng)求方式了谜洽, 同樣地在JAVA中萝映, request對(duì)象也是如此,不能區(qū)分GET和POST**
示例3:
經(jīng)過(guò)前面2個(gè)慘痛的教訓(xùn)阐虚,銀行決定把獲取請(qǐng)求數(shù)據(jù)的方法也改了序臂,改用$_POST,只獲取POST請(qǐng)求的數(shù)據(jù)实束,后臺(tái)處理頁(yè)面Transfer.php代碼如下:
<?php
session_start();
if (isset($_POST['toBankId'] && isset($_POST['money']))
{
buy_stocks($_POST['toBankId'], $_POST['money']);
}
?>
然而奥秆,危險(xiǎn)網(wǎng)站B與時(shí)俱進(jìn),它改了一下代碼磕洪,照樣得手:
<html>
<head>
<script type="text/javascript">
function steal()
{
iframe = document.frames["steal"];
iframe.document.Submit("transfer");
}
</script>
</head>
<body onload="steal()">
<iframe name="steal" display="none">
<form method="POST" name="transfer" action="http://www.myBank.com/Transfer.php">
<input type="hidden" name="toBankId" value="11">
<input type="hidden" name="money" value="1000">
</form>
</iframe>
</body>
</html>
其實(shí)在網(wǎng)站B的代碼中吭练,就是通過(guò)利用同源策略對(duì)iframe標(biāo)簽的不限制,從而在iframe標(biāo)簽中又重新構(gòu)造了一個(gè)post方式提交的表單析显,同樣奏效了鲫咽。
3.3 那么如何防御呢?
- 在服務(wù)器端進(jìn)行防御 :Cookie Hashing(所有表單都包含同一個(gè)偽隨機(jī)值):
在表單里增加Hash值谷异,以認(rèn)證這確實(shí)是用戶發(fā)送的請(qǐng)求分尸。
<?php
$hash = md5($_COOKIE['cookie']);
?>
<form method=”POST” action=”transfer.php”>
<input type=”text” name=”toBankId”>
<input type=”text” name=”money”>
<input type=”hidden” name=”hash” value=”<?=$hash;?>”>
<input type=”submit” name=”submit” value=”Submit”>
</form>
然后在服務(wù)器端進(jìn)行Hash值驗(yàn)證
<?php
if(isset($_POST['check'])) {
$hash = md5($_COOKIE['cookie']);
if($_POST['check'] == $hash) {
doJob();
} else {
//...
}
} else {
//...
}
?>
利用此種hash方法,即使他人拿到cookie歹嘹,也無(wú)法再次獲取到原來(lái)的那個(gè)值箩绍,也就不能再盜取身份了。所以這種防御幾乎可以杜絕很大一部分的攻擊了尺上,但是還有一小部分的不能防御到材蛛,是因?yàn)橐坏┯龅絏SS攻擊,讓他人拿到原始的cookie數(shù)據(jù)怎抛,那同樣GG卑吭。
每次提交表單都輸入驗(yàn)證碼。此種方法太過(guò)繁瑣马绝,不符合實(shí)際使用情況豆赏。
One-Time Tokens(不同的表單包含一個(gè)不同的偽隨機(jī)值)
4.小結(jié):
這篇文章是對(duì)同源策略及其安全性方面進(jìn)行的了解和小結(jié)。其中還有一些細(xì)節(jié)性的問(wèn)題沒(méi)有理解到位富稻,這次總結(jié)的目的并不會(huì)糾結(jié)于細(xì)節(jié)之處掷邦,而是從一個(gè)整體的角度來(lái)了解、學(xué)習(xí)一下這方面的東西椭赋,讓自己有一個(gè)大致的印象即可抚岗。
5.參考文章:
瀏覽器同源政策及其規(guī)避方法-阮一峰
跨域資源共享 CORS 詳解-阮一峰
淺談CSRF攻擊方式
同源策略和跨域請(qǐng)求研究