初探同源策略及其安全
同源策略及其安全.png今天了解了一些有关浏览器同源策略以及CSRF攻击之后,觉得挺有意思的,所以特此总结一波,给自己囤一点干货
这篇文章主要包含三个大的方面:
1.同源策略是什么及其作用
2.如何绕过同源策略
3.CSEF攻击及防御
1.同源策略(same-origin policy):
1.1 什么叫做同源:
同源是针对域名来说的,对于任意的域名,只要他们的协议(HTTP,HTTPS)一致,主机一致,端口号一致,就可以将其认为是同源的。
1.2 那么同源策略的作用是什么?
** 说到作用,那么就不可避免地要提及一下为什么会有这个策略的产生吧**
引用一段阮一峰大神的描述:
同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。
设想这样一种情况:A网站是一家银行,用户登录以后,又去浏览其他网站。如果其他网站可以读取A网站的 Cookie,会发生什么?
很显然,如果 Cookie 包含隐私(比如存款总额),这些信息就会泄漏。更可怕的是,Cookie 往往用来保存用户的登录状态,如果用户没有退出登录,其他网站就可以冒充用户,为所欲为。因为浏览器同时还规定,提交表单不受同源政策的限制。
由此可见,"同源政策"是必需的,否则 Cookie 可以共享,互联网就毫无安全可言了。
再来一段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.
看了这么多的描述之后,我也来一个自己总结的吧。
我认为同源策略之所以出现,就是为了提高在浏览器上网的安全性。这里的 安全性
主要是针对 cookie
来说的。因为 cookie
保存着我们每个人的上网信息,甚至包含一些涉及个人重要隐私的信息,而没有同源策略之前,我们的这些信息,也即 cookie
很容易被他人利用,去做一些羞羞的事情,所以为了保证我们每个网民能够安全的上网,这个策略就应用而生啦。(在这里,我要对NetScape说一声感谢)
** 那么,回归正题,这个同源策略的具体作用都有哪些呢? **
一旦我们进行非同源的跨站访问,那么为了安全起见,在这个过程中,有一些数据的传输会受到限制。比如:
- Cookie、LocalStorage 和 IndexDB 无法读取。
- DOM无法获得
- AJAX请求不能发送
这样一来,虽然看起来我们的安全性被牢牢地掌握在自己手中,但是自己的手脚也多多少少受到了一些限制,什么生意呢?如果在我们自己搞的东西中,真的需要进行跨域访问呢?难道当初NetScape在设计的时候就没考虑到这一点吗?
放心,其实是有办法的。
2.那么都有哪些办法能够绕过同源策略来实现相应的功能呢?
-
对于Cookie的传输:
1.1 如果我们现在有两个网页,它们一级域名一致,只是二级域名不同,那么如果要做到使这两个域名能够实现cookie的共享,只需要将这两个页面的document.domain
设置为一级域名即可。
比如 页面一http://xiao.haha.com/a.html
页面二http://qian.haha.com/b.html
那么只需要将document.domain = haha.com
即可
1.2 对于两个毫无关系的页面,如果要进行cookie的传输,那么需要借助标签<img>
<script>
<iframe>
即可(这里就是CSRF攻击的起源之地) -
对于DOM的传输:
2.1 片段识别符(fragment identifier)
2.2 window.name
2.3 跨文档通信API(Cross-document messaging)
3.对于AJAX请求:
3.1 利用JSONP通信标准
其基本思想是既然AJAX已经被废掉了,那么就换一种还可以继续请求的办法。于是就想到了可以利用 <script>
标签来向跨源地址发起请求。
示例代码
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);
};
上面代码通过动态添加 <script>
元素,向服务器 example.com
发出请求。注意,该请求的查询字符串有一个 callback
参数,用来指定回调函数的名字,这对于JSONP是必需的。
服务器收到这个请求以后,会将数据放在回调函数的参数位置返回。
foo({
"ip": "8.8.8.8"
});
由于 <script>
元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了foo函数,该函数就会立即调用。作为参数的JSON数据被视为JavaScript对象,而不是字符串,因此避免了使用 JSON.parse
的步骤。
3.2 利用WebSocket协议
WebSocket是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。
示例如下:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Origin: http://example.com/
在上边的示例中,最后一个属性 origin
是重点。该字段表示请求的请求源,然后浏览器根据该地址,判断是否处在白名单中,如果处在,则可以进行本次通信,反之不可。
3.3 利用CORS(跨源资源分享)标准
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。
它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
只要同时满足以下两大条件,就属于简单请求。
- 请求方法是以下三种方法之一:
1.HEAD
2.GET
3.POST - HTTP的头信息不超出以下几种字段:
1.Accept
2.Accept-Language
3.Content-Language
4.Last-Event-ID
5.Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
** 对于简单请求: **
浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。
如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段(详见下文),就知道出错了,从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。
** 对于非简单请求: **
非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
** 注: CORS协议支持所有的HTTP请求,而JSONP只支持GET请求,所以在这一点上,CORS协议要强大的多。 **
3.CSRF攻击又是啥子武器呢?
3.1 什么是CSRF攻击?
CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。
3.2 CSRF攻击的原理?
利用cookie,盗用身份从而进行邪恶活动。
具体的作案手段都有哪些呢?
示例1:
银行网站A,它以GET请求来完成银行转账的操作,如:http://www.mybank.com/Transfer.php?toBankId=11&money=1000
危险网站B,它里面有一段HTML的代码如下,便会成功。
<img src=http://www.mybank.com/Transfer.php?toBankId=11&money=1000>
** 由于同源策略对img标签没有限制,所以向在img标签中的src地址发起请求时,会直接将用户的cookie传递过去,从而盗取用户身份进行非法操作。 **
示例2:
为了杜绝上面的问题,银行决定改用POST请求完成转账操作。
银行网站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>
后台处理页面Transfer.php如下:
<?php
session_start();
if (isset($_REQUEST['toBankId'] && isset($_REQUEST['money']))
{
buy_stocks($_REQUEST['toBankId'], $_REQUEST['money']);
}
?>
危险网站B,仍然只是包含那句HTML代码,同样也会成功。
<img src=http://www.mybank.com/Transfer.php?toBankId=11&money=1000>
** 为什么这次还可以呢,这个锅就得PHP背了,PHP在后端用的是$_REQUEST来获取参数,那么它既可获取GET参数,也可以得到POST。所以在这里,依然用的是GET请求方式了, 同样地在JAVA中, request对象也是如此,不能区分GET和POST**
示例3:
经过前面2个惨痛的教训,银行决定把获取请求数据的方法也改了,改用$_POST,只获取POST请求的数据,后台处理页面Transfer.php代码如下:
<?php
session_start();
if (isset($_POST['toBankId'] && isset($_POST['money']))
{
buy_stocks($_POST['toBankId'], $_POST['money']);
}
?>
然而,危险网站B与时俱进,它改了一下代码,照样得手:
<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>
其实在网站B的代码中,就是通过利用同源策略对iframe标签的不限制,从而在iframe标签中又重新构造了一个post方式提交的表单,同样奏效了。
3.3 那么如何防御呢?
- 在服务器端进行防御 :Cookie Hashing(所有表单都包含同一个伪随机值):
在表单里增加Hash值,以认证这确实是用户发送的请求。
<?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>
然后在服务器端进行Hash值验证
<?php
if(isset($_POST['check'])) {
$hash = md5($_COOKIE['cookie']);
if($_POST['check'] == $hash) {
doJob();
} else {
//...
}
} else {
//...
}
?>
利用此种hash方法,即使他人拿到cookie,也无法再次获取到原来的那个值,也就不能再盗取身份了。所以这种防御几乎可以杜绝很大一部分的攻击了,但是还有一小部分的不能防御到,是因为一旦遇到XSS攻击,让他人拿到原始的cookie数据,那同样GG。
-
每次提交表单都输入验证码。此种方法太过繁琐,不符合实际使用情况。
-
One-Time Tokens(不同的表单包含一个不同的伪随机值)
4.小结:
这篇文章是对同源策略及其安全性方面进行的了解和小结。其中还有一些细节性的问题没有理解到位,这次总结的目的并不会纠结于细节之处,而是从一个整体的角度来了解、学习一下这方面的东西,让自己有一个大致的印象即可。