Cookie机制:一般来说,同域内浏览器中发出的任何一个请求都会带上Cookie,无论请求什么资源,请求时,Cookie出现在请求头的Cookie字段中。服务端响应头的Set-Cookie字段可以添加、修改和删除Cookie,客户端通过javascript也可以添加、修改和删除Cookie。另外,Cookie是无法跨浏览器存在的。
利用Cookie机制,我们可以存储用户的会话信息,比如,用户登认证后的Session,之后同域内发出的请求都会带上认证后的会话信息,很方便。也因此,攻击者特别喜欢盗取Cookie,这相当于盗取了目标网站上的用户权限。
当然,有时对静态组件的Cookie读取是一种浪费,我们可以用另一个无Cookie的域名来存放这些静态组件。
Cookie的重要字段如下:
[name] [value] [domain] [path] [expries] [max-age] [httponly] [secure]
其含义依次为:名称、值、所属域名、所属相对根路径、过期时间、是否有HttpOnly标志、是否有Secure标志。
下面分别对domain、path 、httponly和secure字段进行说明:
1、子域Cookie机制
设置Cookie时,如果不指定domain的值,默认就是本域。
例如:a.foo.com域通过javascript设置一个Cookie,语句如下:
document.cookie="test=1";
那么,domain值默认为a.foo.com。如果指定domain为父级域,比如:
document.cookie="test=1;domain=foo.com";
此时,domain变为foo.com,这样带来一个好处就是可以在不同的子域共享Cookie,坏处也很明显,就攻击者控制的其他子域也能读到这个Cookie。注意:这个机制不允许设置Cookie的domain为下一级子域或其他外域。
2、路径Cookie机制
设置Cookie时,如果不指定path的值,默认就是目标页面的路径。
例如:a.foo.com/admin/index.php页面通过javascript来设置一个Cookie,语句如下:
document.cookie="test=1";
那么,path值默认为/admin/。通过指定path字段,javascript可以设置任意Cookie到任意路径下,但是只有目标路径下的页面javascript才能读取到该Cookie。
但是,通过设置path不能防止重要的Cookie被盗取。比如,/evil/路径想读取/admin/路径的Cookie。通过跨iframe进行DOM操作即可做到,/evil/路径下页面的代码如下:
xc=function(src){
var o=document.createElement('iframe');//iframe进入同域的目标页面
o.src=src;
document.getElementsByTagName('body')[0].appendChild(o);
o.onload=function(){//iframe加载完成后
d=o.contentDocument||o.contentWindow.document;//获取document对象
alert(d.cookie);//获取cookie
};
}('http://a.foo.com/admin/index.php');
3、HttpOnly Cookie机制
HttpOnly是指仅在HTTP层面上传输的Cookie,当设置了HttpOnly标志后,客户端脚本就无法读写该Cookie,能有效地防御XSS攻击获取Cookie。以PHP setcookie为例,httponly.php文件代码如下:
<?php
setcookie("test",1,time()+3600,"","",0,0); //设置普通cookie
setcookie("test_http",1,time()+3600,"","",0,1);//第7个参数(这里的最后一个)是HttpOnly标志,0为关闭,1为开启,默认为0
?>
请求这个文件后,设置了个两个Cookie,如下图所示:
其中,test_http是HttpOnly Cookie。如果服务端响应的页面有Cookie调试信息,很可能会导致HttpOnly Cookie的泄漏。比如以下信息。
Apache HTTP Server2.2.x多个版本没有严格限制HTTP请求头信息,HTTP请求头超过LimitRequestFieldSize长度时,服务器返回400(Bad Request)错误并在返回信息中将出错的请求头内容输出(包含请求头里的HttpOnly Cookie),攻击者可以利用这个缺陷获取HttpOnly Cookie。
大多数浏览器限制Cookies(每个域50个Cookie)最大约为4kB,我们设置为更大且为本地Cookie,让请求头长度超过Apache的LimitRequestFieldSize,从而引起400错误。这样受攻击者只能通过清除Cookie才能正常访问目标网站。
4、Secure Cookie机制
Secure Cookie机制指的是设置了Secure标志的Cookie仅在HTTPS层面上安全传输,如果请求是HTTP的就不会带上这个Cookie,这样能降低重要的Cookie被中间人截获的风险。
但是,Secure Cookie对于客户端脚本来说是可读写的,也就意味着,它能够被盗取和篡改。如下的javascript脚本代码可对已知的Secure Cookie进行篡改:
//path与domain必须一致,否则会被认为是不同的Cookie
document.cookie="test_secure=1;path=/;secure;"
本地Cookie与内存Cookie
如果没设置过期时间,就是内存Cookie,这样的Cookie会随着浏览器的关闭而从内存中消失;如果设置了过期时间是未来的某个时间点,那么就是本地Cookie,这样的Cookie就会以文本形式保存在操作系统本地,待过期时间到了才会消失。如:
document.cookie="test_expires=1;expires=Mon,01 Jan 2112 00:00:00 GMT";//GMT时间,2112年1月1日才会过期
采用本地Cookie可以让用户在未来某一段时间内都不需要进行登录操作,提升用户体验,但是,如果攻击者通过XSS得到这样的本地Cookie后,就能够在未来很长一段时间内,甚至永久控制着目标用户的账号权限。但这并不意味着内存Cookie更安全,因为攻击者可以给内存Cookie加一个过期时间,使其变为本地Cookie。
Cookie的P3P性质
HTTP响应头的P3P(Platform for Privacy Preferences Project)字段是W3C公布的一项隐私保护推荐标准。该字段用于标识是否允许目标网站的Cookie被另一个域通过加载目标网站而设置或发送,但仅IE执行了该策略。
比如,evil域通过script或iframe等方式加载foo域。加载的时候,浏览器是否会允许foo域设置自己的Cookie,或是否允许发送请求到foo域时,带上foo域已有的Cookie。
下面是关于P3P策略在设置与发送的两个场景,P3P策略在这两个场景下是有差异的:
1、设置Cookie
在IE下,默认是不允许第三方域设置Cookie(本地和内存)的,除非foo域在响应的时候带上P3P字段,如:
P3P:CP="CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR"
该字段的内容本身意义不大,不需要记(当然我们也没有时间去记这些没有特别意义的东西),只要知道这样设置后,被加载的目标域的Cookie就可以被正常设置了,设置后的Cookie在IE下会自动带上P3P属性(这个属性在Cookie中是看不到的),一次生效,即使之后没有P3P头,也有效。
2、发送Cookie
发送的Cookie如果是内存Cookie,则无所谓是否有P3P属性,就可以正常发送;如果是本地Cookie,则这个本地Cookie必须拥有P3P属性,否则,即使目标域响应了P3P头也没用。
我们可以通过以下方法进行测试:
1)给host文件添加www.foo.com与www.evil.com域。
2)将如下代码保存为foo.php,并保证能通过www.foo.com/cookie/foo.php访问到。
<?php
//header('P3P:CP="CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR"');
setcookie("test0",'local',time()+3600*3650);
setcookie("test_mem0",'memory');
var_dump($_COOKIE);
?>
3)将如下代码保存为evil.php,并保证能通过www.evil.com/cookie/evil.php访问到。
<iframe src="http://www.foo.com/cookie/foo.php"></iframe>
4)IE浏览器访问www.evil.com/cookie/evil.php,由于没有响应P3P头,foo.php设置并未成功。
5)将foo.php的P3P响应功能的注释去掉,再访问www.evil.com/cookie/evil.php,可以发现本地Cookie(test0)与内存Cookie(test_mem0)都已设置成功。
6)修改foo.php里的Cookie名,比如,test0改为test1,test_mem0改为test_mem1等,注释P3P响应功能,然后直接访问www.foo.com/cookie/foo.php,这时会设置本地Cookie(test1)与内存Cookie(test_mem1),此时这两个Cookie都不带P3P属性。
7)再通过访问www.evil.com/cookie/evil.php,可以发现内存Cookie(test_mem1)正常发送,而本地Cookie(test1)没有发送。
8)继续修改foo.php里的Cookie名,test1改为test2,test_mem1改为test_mem2,去掉P3P响应功能的注释,然后直接访问www.foo.com/cookie/foo.php,此时本地Cookie(test2)与内存Cookie(test_mem2)都有了P3P属性。
9)这时访问www.evil.com/cookie/evil.php,可以发现test2与test_mem2都发送出去了。