概述
最近,使用APPSCAN扫描系统时,扫描出存在"Content-Security-Policy"头缺失漏洞。
1. 同源策略是什么
协议相同
域名相同
端口相同
同源策略可分为以下两种情况:
1、DOM 同源策略:禁止对不同源页面 DOM 进行操作。这里主要场景是 iframe 跨域的情况,不同域名的 iframe 是限制互相访问的。
2、XMLHttpRequest 同源策略:禁止使用 XHR 对象向不同源的服务器地址发起 HTTP 请求。
同源策略的目的
因为存在浏览器同源策略,所以才会有跨域问题。那么浏览器是出于何种原因会有跨域的限制呢。其实不难想到,跨域限制主要的目的就是为了用户的上网安全。
如果浏览器没有同源策略,会存在什么样的安全问题呢。下面从 DOM 同源策略和 XMLHttpRequest 同源策略来举例说明:
如果没有 DOM 同源策略,也就是说不同域的 iframe 之间可以相互访问,那么黑客可以这样进行攻击:
1、做一个假网站,里面用 iframe 嵌套一个银行网站 http://mybank.com。
2、把 iframe 宽高啥的调整到页面全部,这样用户进来除了域名,别的部分和银行的网站没有任何差别。
3、这时如果用户输入账号密码,我们的主网站可以跨域访问到 http://mybank.com 的 dom 节点,就可以拿到用户的账户密码了。如果没有 XMLHttpRequest 同源策略,那么黑客可以进行 CSRF(跨站请求伪造) 攻击:
1、用户登录了自己的银行页面 http://mybank.com,http://mybank.com 向用户的 cookie 中添加用户标识。
2、用户浏览了恶意页面 http://evil.com,执行了页面中的恶意 AJAX 请求代码。
3、http://evil.com 向 http://mybank.com 发起 AJAX HTTP 请求,请求会默认把 http://mybank.com 对应 cookie 也同时发送过去。
4、银行页面从发送的 cookie 中提取用户标识,验证用户无误,response 中返回请求数据。此时数据就泄露了。
5、而且由于 Ajax 在后台执行,用户无法感知这一过程。
2.跨域解决方法
一、请求跨域
CORS
JSONP
WebSocket
代理转发
二、页面跨域
postMessage
document.domain
window.name
location.hash
请求跨域解决方法
- CORS
优点:
1、CORS 通信与同源的 AJAX 通信没有差别,代码完全一样,容易维护。
2、支持所有类型的 HTTP 请求。
缺点:
1、存在兼容性问题,特别是 IE10 以下的浏览器。
2、第一次发送非简单请求时会多一次请求。
- JSONP 实现跨域
由于 script 标签不受浏览器同源策略的影响,允许跨域引用资源。因此可以通过动态创建 script 标签,然后利用 src 属性进行跨域,这也就是 JSONP 跨域的基本原理。
直接通过下面的例子来说明 JSONP 实现跨域的流程:
// 1. 定义一个 回调函数 handleResponse 用来接收返回的数据
function handleResponse(data) {
console.log(data);
};
// 2. 动态创建一个 script 标签,并且告诉后端回调函数名叫 handleResponse
var body = document.getElementsByTagName('body')[0];
var script = document.gerElement('script');
script.src = 'http://www.laixiangran.cn/json?callback=handleResponse';
body.appendChild(script);
// 3. 通过 script.src 请求 `http://www.laixiangran.cn/json?callback=handleResponse`,
// 4. 后端能够识别这样的 URL 格式并处理该请求,然后返回 handleResponse({"name": "laixiangran"}) 给浏览器
// 5. 浏览器在接收到 handleResponse({"name": "laixiangran"}) 之后立即执行 ,也就是执行 handleResponse 方法,获得后端返回的数据,这样就完成一次跨域请求了。
优点
使用简便,没有兼容性问题,目前最流行的一种跨域方法。
缺点
只支持 GET 请求。
由于是从其它域中加载代码执行,因此如果其他域不安全,很可能会在响应中夹带一些恶意代码。
要确定 JSONP 请求是否失败并不容易。虽然 HTML5 给 script 标签新增了一个 onerror 事件处理程序,但是存在兼容性问题。
- WebSocket跨域
Websocket 是 HTML5 规范提出的一个应用层的全双工协议,适用于浏览器与服务器进行实时通信场景。
全双工通信传输的一个术语,这里的“工”指的是通信方向。
“双工”是指从客户端到服务端,以及从服务端到客户端两个方向都可以通信,“全”指的是通信双方可以同时向对方发送数据。与之相对应的还有半双工和单工,半双工指的是双方可以互相向对方发送数据,但双方不能同时发送,单工则指的是数据只能从一方发送到另一方。
下面是一段简单的示例代码。在 a 网站直接创建一个 WebSocket 连接,连接到 b 网站即可,然后调用 WebScoket 实例 ws 的 send() 函数向服务端发送消息,监听实例 ws 的 onmessage 事件得到响应内容。
let ws = new WebSocket("ws://b.com");
ws.onopen = function(){
// ws.send(...);
}
ws.onmessage = function(e){
// console.log(e.data);
}
- 请求代理
我们知道浏览器有同源策略的安全限制,但是服务器没有限制,所以我们可以利用服务器进行请求转发。
以 webpack 为例,利用 webpack-dev-server 配置代理, 当浏览器发起前缀为 /api 的请求时都会被转发到 http://localhost:3000 服务器,代理服务器将获取到响应返回给浏览器。对于浏览器而言还是请求当前网站,但实际上已经被服务端转发。
// webpack.config.js
module.exports = {
//...
devServer: {
proxy: {
'/api': 'http://localhost:3000'
}
}
};
// 使用 Nginx 作为代理服务器
location /api {
proxy_pass http://localhost:3000;
}
页面跨域解决方法
请求跨域之外,页面之间也会有跨域需求,例如使用 iframe 时父子页面之间进行通信。常用方案如下:
postMessage
document.domain
window.name(不常用)
location.hash + iframe(不常用)
- postMessage
window.postMessage(message,targetOrigin) 方法是 HTML5 新引进的特性,可以使用它来向其它的 window 对象发送消息,无论这个 window 对象是属于同源或不同源。这个应该就是以后解决 dom 跨域通用方法了。
调用 postMessage 方法的 window 对象是指要接收消息的那一个 window 对象,该方法的第一个参数 message 为要发送的消息,类型只能为字符串;第二个参数 targetOrigin 用来限定接收消息的那个 window 对象所在的域,如果不想限定域,可以使用通配符 *。
需要接收消息的 window 对象,可是通过监听自身的 message 事件来获取传过来的消息,消息内容储存在该事件对象的 data 属性中。
页面 http://www.laixiangran.cn/a.html 的代码:
页面 http://laixiangran.cn/b.html 的代码:
- document.domain跨域
对于主域名相同,而子域名不同的情况,可以使用 document.domain 来跨域。这种方式非常适用于 iframe 跨域的情况。
比如,有一个页面,它的地址是 http://www.laixiangran.cn/a.html,在这个页面里面有一个 iframe,它的 src 是 http://laixiangran.cn/b.html。很显然,这个页面与它里面的 iframe 框架是不同域的,所以我们是无法通过在页面中书写 js 代码来获取 iframe 中的东西的。
这个时候,document.domain 就可以派上用场了,我们只要把 http://www.laixiangran.cn/a.html和 http://laixiangran.cn/b.html这两个页面的 document.domain 都设成相同的域名就可以了。但要注意的是,document.domain 的设置是有限制的,我们只能把 document.domain 设置成自身或更高一级的父域,且主域必须相同。例如:a.b.laixiangran.cn 中某个文档的 document.domain 可以设成 a.b.laixiangran.cn、b.laixiangran.cn 、laixiangran.cn 中的任意一个,但是不可以设成 c.a.b.laixiangran.cn ,因为这是当前域的子域,也不可以设成 baidu.com,因为主域已经不相同了。
例如,在页面 http://www.laixiangran.cn/a.html 中设置document.domain:
在页面 http://laixiangran.cn/b.html 中也设置 document.domain,而且这也是必须的,虽然这个文档的 domain 就是 laixiangran.cn,但是还是必须显式地设置 document.domain 的值:
这样,http://www.laixiangran.cn/a.html 就可以通过 js 访问到 http://laixiangran.cn/b.html 中的各种属性和对象了。
- window.name跨域
window 对象有个 name 属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面(不管是相同域的页面还是不同域的页面)都是共享一个 window.name 的,每个页面对 window.name 都有读写的权限,window.name 是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。
通过下面的例子介绍如何通过 window.name 来跨域获取数据的。
页面 http://www.laixiangran.cn/a.html 的代码:
页面 http://laixiangran.cn/b.html 的代码:
- location.hash跨域
location.hash 方式跨域,是子框架修改父框架 src 的 hash 值,通过这个属性进行传递数据,且更改 hash 值,页面不会刷新。但是传递的数据的字节数是有限的。
页面 http://www.laixiangran.cn/a.html 的代码:
页面 http://laixiangran.cn/b.html 的代码:
内容安全策略(CSP)
内容安全策略(Content Security Policy,简称CSP)是一种以可信白名单作机制,来限制网站是否可以包含某些来源内容,缓解广泛的内容注入漏洞,比如 XSS。 简单来说,就是我们能够规定,我们的网站只接受我们指定的请求资源。默认配置下不允许执行内联代码(