Cookie 是小甜饼的意思。顾名思义,cookie 确实非常小,它的大小限制为4KB左右,是网景公司的前雇员 Lou Montulli 在1993年3月的发明。它的主要用途有保存登录信息,比如你登录某个网站市场可以看到“记住密码”,这通常就是通过在 Cookie 中存入一段辨别用户身份的数据来实现的。
Cookie是一小段文本信息,伴随着用户请求在 Web 服务器和浏览器之间传递。它存储于访问者的计算机中,每当同一台计算机通过浏览器请求某个页面时,就会发送这个 cookie。
cookie是存于用户硬盘的一个文件,这个文件通常对应于一个域名,也就是说,cookie可以跨越一个域名下的多个网页,但不能跨越多个域名使用。
cookie 将信息存储于用户硬盘,因此可以作为全局变量,这是它最大的一个优点。它最根本的用途是 Cookie 能够帮助 Web 站点保存有关访问者的信息,以下列举cookie的几种小用途。
- 保存用户登录信息。这应该是最常用的了。当您访问一个需要登录的界面,例如微博、百度及一些论坛,在登录过后一般都会有类似"下次自动登录"的选项,勾选过后下次就不需要重复验证。这种就可以通过cookie保存用户的id。
- 创建购物车。购物网站通常把已选物品保存在cookie中,这样可以实现不同页面之间数据的同步(同一个域名下是可以共享cookie的),同时在提交订单的时候又会把这些cookie传到后台。
- 跟踪用户行为。例如百度联盟会通过cookie记录用户的偏好信息,然后向用户推荐个性化推广信息,所以浏览其他网页的时候经常会发现旁边的小广告都是自己最近百度搜过的东西。这是可以禁用的,这也是cookie的缺点之一。
以上信息出自此文章
一、Cookie的重要属性
Cookie有两个很重要的属性:Domain和Path,用来指示此Cookie的作用域:
domain字段为可以访问此cookie的域名,告诉浏览器当前要添加的Cookie的域名归属,如果没有明确指明则默认为当前域名,可以设置当前域名或者(
父域名必须以“.”开始,例如.xxx.com
)。为了保证安全性,cookie无法设置除当前域名或者其父域名之外的其他domain。path字段为可以访问此cookie的页面路径。如果没有明确指明则默认为当前路径,比如通过访问。 比如domain是abc.com,path是/test,那么只有/test路径下的页面可以读取此cookie,"/"表示根路径。
例如
类型 | domain | path |
---|---|---|
Cookie1 | .amy.com | / |
Cookie2 | www.amy.com | /to/ |
Cookie3 | hh.amy.com | / |
Cookie4 | www.amy.com | / |
当我访问www.amy.com时:
Cookie1 可以提交
Cookie2 不可以提交,path不一样
Cookie3 不可以提交,hh.amy.com不是www.amy.com 的父域名
Cookie4 可以提交
二、同源策略
同源政策由 Netscape 公司引入浏览器。目前,所有浏览器都实行这个政策。最初,它的含义是指,A网页设置的 Cookie,B网页不能打开,除非这两个网页"同源"。所谓"同源"指的是"三个相同"。
同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制
同源策略:同一协议,同一域名,同一端口号。只要不满足三者其中一种都是属于跨域问题。
三、什么是跨域?跨域有几种实现形式
基础概念
同源政策由 Netscape 公司引入浏览器。目前,所有浏览器都实行这个政策。最初,它的含义是指,A网页设置的 Cookie,B网页不能打开,除非这两个网页"同源"。所谓"同源"指的是"三个相同"。
同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。
只要协议、域名、端口有任何一个不同,都被当作是不同的域,跨域就是访问非本域的资源。
举几个简单的例子
1: https://www.a.com:8080到http://www.a.com:8080的请求会出现跨域(域名、端口相同但协议不同)
2: https://www.a.com:8080到https://www.b.com:8080的请求会出现跨域(协议、端口相同但域名不同)
3: https://www.a.com:8080到https://www.a.com:9090的请求会出现跨域(协议、域名相同但端口不同)
跨域:跨域的安全限制都是对浏览器端来说的,服务器端是不存在跨域安全限制的。浏览器的同源策略限制从一个源加载的文档或脚本与来自另一个源的资源进行交互。
区别JSON和JSONP
JSON的全称为JavaScript Object Notation,是一种轻量级的数据交互格式。它基于 ECMAScript (欧洲计算机协会制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简单来说,json就是一种用来传输数据的数据格式。
JSONP是一种非正式传输协议,该协议的一个要点就是允许用户传递一个callback(或者一开始就定义一个回调方法)参数给服务端,然后服务端返回数据时会将这个callback 参数作为函数名来包裹住 JSON 数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了。
跳出同源的“舒适圈”
我们发现,在web页面调用js文件是不受是否跨域问题的影响的。而且我们还发现凡是拥有src这个属性的标签都拥有跨域的能力,比如img、iframe和script。在html页面中我们经常会做引入图片的操作,通过img标签中的src属性,我们就可以请求得到一个静态资源。
我们可以看到这本质上就是一个GET请求,同理,link和script里的href和src同样可以通过GET请求去请求资源。
<script src="http://localhost:9090/api"></script>
它们并没有受到同源策略的影响,jsonp的实现原理其实就是利用了这个策略的小“bug”,从而实现跨域请求的。既然是一个GET请求,服务器一定可以收到这个请求并作出响应。下面就让我们来具体实现一下吧!
原理及跨域实现
1、JSONP:
-
优点
- 它不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制
- 它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持
- 并且在请求完毕后可以通过调用callback的方式回传结果
-
缺点
- 它只支持GET请求而不支持POST等其它类型的HTTP请求,因为是通过引用的资源,参数全都显式的放在URL里,和 AJAX 没有关系。
- 它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题, 动态插入标签其实就是一种脚本注入,会受到跨站脚本攻击。
具体流程(原理)
jsonp的执行流程其实就是简单的两步。第一,在前端预先定义好一个带参数的回调函数用来接受后端传来的数据。第二,在后端启动一个server服务,将要传的数据以定义好了的回调函数名加上返回结果的方式传给前端。
// 前端部分
<script>
// 1 callback
// 2 后端 callbackName(数据)
function onResponse(posts) {
console.log(posts);
}
// 前端没有调用
</script>
<!-- 后端返回结果 -->
<!-- 调用 -->
<script src="http://localhost:9090/api"></script>
再来后台代码~
//后端部分
const http = require('http');
http.createServer((req, res) => {
if (req.url === '/api') {
let posts = ['js', 'php'];
res.end(`onResponse(${JSON.stringify(posts)})`);
}
})
.listen(9090, () => {
console.log(9090)
})
前端script中的src请求完毕以后,后端会给前端返回一个字符串onResponse(["js","php"]),因为script标签的原因,浏览器会把这一段字符串当做js来执行。这样我们一开始在前端定义好了的回调就会执行,我们就拿到数据了。
- 封装
以上只是有一个简单的请求,实际项目中肯定会有很多个请求,我们肯定不可以定义一排的script标签和回调函数。这样写出来的代码就太不灵活了。封装的目的之一也就是为了前端可以灵活地修改预定义回调函数的名字,而不是在前后端把回调函数定死。同时,把代码封装以后,我们就不用手动地创建回调函数了,封装后的函数会帮我们自动放src的地址,自动创建回调函数名。
// 后端
app.get('*', function(req, res) {
let callback = req.query.func;
let content = callback+"({'message':'测试数据2'})";
res.send(content);
});
function showJsonp(obj){
console.log(obj.message);
}
var url = 'http://127.0.0.1:8787/?func=showJsonp'
var script = document.createElement('script');
script.setAttribute('src',url);
script.setAttribute('type','text/javascript');
document.getElementsByTagName('head')[0].appendChild(script);
JSONP 劫持漏洞的利用过程如下:
用户在网站B 注册并登录,网站B 包含了用户的id,name,email等信息;
用户通过浏览器向网站A发出URL请求;
网站A向用户返回响应页面,响应页面中注册了 JavaScript 的回调函数和向网站B请求的 script 标签,示例代码如下:
<script type="text/javascript">
function Callback(result)
{
alert(result.name);
}
</script>
<script type="text/javascript" src="http://B.com/user?jsonp=Callback"></script>
用户收到响应,解析 JS 代码,将回调函数作为参数向网站B发出请求;
网站 B 接收到请求后,解析请求的 URL,以 JSON 格式生成请求需要的数据,将封装的包含用户信息的 JSON 数据作为回调函数的参数返回给浏览器,网站B返回的数据实例如下:
Callback({"id":1,"name":"test","email":"test@test.com"})
网站B数据返回后,浏览器则自动执行 Callback 函数对步骤4返回的 JSON 格式数据进行处理,通过 alert 弹窗展示了用户在网站B的注册信息。另外也可将 JSON 数据回传到网站A的服务器,这样网站A利用网站B的JSONP漏洞便获取到了用户在网站B注册的信息。
防护方案
1、严格安全的实现 CSRF 方式调用 JSON 文件:限制 Referer 、部署一次性 Token 等。
2、严格安装 JSON 格式标准输出 Content-Type 及编码( Content-Type : application/json; charset=utf-8 )。
3、严格过滤 callback 函数名及 JSON 里数据的输出。
4、严格限制对 JSONP 输出 callback 函数名的长度(如防御上面 flash 输出的方法)。
5、其他一些比较“猥琐”的方法:如在 Callback 输出之前加入其他字符(如:/**/、回车换行)这样不影响 JSON 文件加载,又能一定程度预防其他文件格式的输出。还比如 Gmail 早起使用 AJAX 的方式获取 JSON ,听过在输出 JSON 之前加入 while(1) ;这样的代码来防止 JS 远程调用。
2、CORS
浏览器将CORS请求分成两类:简单请求(simple request)和 非简单请求(not-so-simple request)。只要同时满足以下两个条件就属于简单请求否则属于非简单请求(主要通过请求方法进行判断):
- 请求方法是(HEAD,GET,POST)三种之一;
- HTTP的头信息不超出(Accept,Accept-Language,Content-Language,Lat-Event-ID,Content-Type)这几种字段。
** CORS跨域——简单请求的流程 **
简单请求:浏览器直接发出CORS请求。大致流程是浏览器发现这一次向服务器提交的请求是简单请求,所以自动在头信息中增加了一个Origin的字段,用来表示这次的请求来自哪个域。当服务器接收到请求后发现Origin字段指定的域名在许可范围内,服务器会在响应包中增加三个与CORS相关的字段:
Access-Control-Allow-Origin:该字段是必须存在的,它的值可能是 Origin 字段的值或者是一个通配符“*”,表示可以接受任意域名的请求,当然大部分服务器如果配置了通配符的话,信息泄露的风险骤然加大;
Access-Control-Allow-Credentials:字段不是必选字段,它的值是一个布尔值且只能设置为 true,表示服务器允许浏览器将 cookie 包含在请求中,否则就不添加此字段。但需要注意的是,如果要发送 cookie,Access-Control-Allow-Origin 就不能设为星号,必须明确指定与请求网页一致的域名,同时Cookie依然遵循同源策略。
Access-Control-Expose-Headers:该字段主要是指定想要获取 XMLHttpRequest 对象中 getResponseHeader() 方法的其他服务器字段。
具体的 CORS 简单跨域请求流程如下:
CORS跨域——非简单请求的流程
所谓非简单请求就是那种对服务器提出特殊要求的请求,例如请求方法为 PUT 或 DELETE,或者Content-Type字段的类型是application/json。非简单的 CORS 请求会在正式通信之前,增加一次 HTTP 查询请求,称之为 “预检请求” 。浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单里以及可以使用哪些 HTTP 动词和头信息字段。只有获得了肯定响应,浏览器才会正式发出 XMLHttpRequest 请求否则就报错。这种请求的好处是对传统的没有 CORS 支持的服务器减小压力,给服务器一个提前拒绝的机会。
具体流程如上图所示,当构造请求包的方法是 PUT 或 DELETE 并传给浏览器时,浏览器发现此请求是非简单请求所以浏览器构造一个预检请求包,请求头是 OPTIONS,并携带三个关键字段:Origin、Access-Control-Request-Method、Access-Control-Request-Headers。其中 Access-Control-Request-Method 表示浏览器的 CORS 请求会用到哪些HTTP方法, Access-Control-Request-Headers 表示浏览器 CORS 请求会额外发送的头信息字段。服务器收到预检请求后,检查了三个核心字段以后如果确定允许跨域请求,会返回一个正常的 HTTP 回应,并携带传入的 CORS 头信息。如果服务器否定请求,虽然也会返回一个正常的 HTTP 回应但是没有任何 CORS 相关的头信息字段,或明确表示请求不符合条件。浏览器根据预请求的返回结果决定接下来是进行简单请求还是拒绝请求。
注意
- CORS与JSONP的使用目的相同,但是比JSONP更强大。
- JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。
tips:如果想要带上cookie的话Access-Control-Allow-Origin不能设置*可以用nginx这样配置
我们也可以测试下带有 CORS 字段的网站是否有 CORS 漏洞,如果服务器响应包的请求头是以下几种情况则可存在 CORS 漏洞:
实锤存在: 有且仅有如下请求头
Access-Control-Allow-Origin: *
实锤存在:同时存在如下两个请求头
Access-Control-Allow-Origin: https://attacker.com
Access-Control-Allow-Credentials: true
可能存在:同时存在如下两个请求头
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
但是如果是如下组合,则绝对没有漏洞,因为该配置下浏览器会自动阻止:
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
防护方案
关闭不必要开启的CORS;
白名单限制:定义“源”的白名单,避免使用正则表达式,不要配置 Access-Control-Allow-Origin 为通配符 * 或 null ,严格效验来自请求数据包中的 Origin 的值;
仅允许使用安全协议,避免中间人攻击;
尽可能的返回 Vary: Origin 头部,以避免攻击者利用浏览器缓存进行攻击;
避免将 Access-Control-Allow-Credentials 标头设置为默认值 true ,跨域请求若不存在必要的凭证数据,则根据实际情况将其设置为 false;
限制跨域请求允许的方法,Access-Control-Allow-Methods 最大限度地减少所涉及的方法,降低风险;
限制浏览器缓存期限:建议通过 Access-Control-Allow-Methods 和 Access-Control-Allow-Headers 头部,限制浏览器缓存信息的时间。通过配置 Access-Control-Max-Age 标头来完成,该头部接收时间数作为输入,该数字是浏览器保存缓存的时间。配置相对较低的值,确保浏览器在短时间内可以更新策略;
仅在接收到跨域请求时才配置有关于跨域的头部,并确保跨域请求是合法的源,以减少攻击者恶意利用的可能性。
3、降域
4、PostMessage
ajax会自动带上同源的cookie,不会带上不同源的cookie
可以通过前端设置withCredentials为true, 后端设置Header的方式来让ajax自动带上不同源的cookie,但是这个属性对同源请求没有任何影响, 会被自动忽略。