概述
最近,使用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 的代码:
<iframe src="http://laixiangran.cn/b.html" id="myIframe" onload="test()" style="display: none;">
<script>
// 1. iframe载入 "http://laixiangran.cn/b.html 页面后会执行该函数
function test() {
// 2. 获取 http://laixiangran.cn/b.html 页面的 window 对象,
// 然后通过 postMessage 向 http://laixiangran.cn/b.html 页面发送消息
var iframe = document.getElementById('myIframe');
var win = iframe.contentWindow;
win.postMessage('我是来自 http://www.laixiangran.cn/a.html 页面的消息', '*');
}
</script>
页面 http://laixiangran.cn/b.html 的代码:
<script type="text/javascript">
// 注册 message 事件用来接收消息
window.onmessage = function(e) {
e = e || event; // 获取事件对象
console.log(e.data); // 通过 data 属性得到发送来的消息
}
</script>
- 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:
<iframe src="http://laixiangran.cn/b.html" id="myIframe" onload="test()">
<script>
document.domain = 'laixiangran.cn'; // 设置成主域
function test() {
console.log(document.getElementById('myIframe').contentWindow);
}
</script>
在页面 http://laixiangran.cn/b.html 中也设置 document.domain,而且这也是必须的,虽然这个文档的 domain 就是 laixiangran.cn,但是还是必须显式地设置 document.domain 的值:
<script>
document.domain = 'laixiangran.cn'; // document.domain 设置成与主页面相同
</script>
这样,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 的代码:
<iframe src="http://laixiangran.cn/b.html" id="myIframe" onload="test()" style="display: none;">
<script>
// 2. iframe载入 "http://laixiangran.cn/b.html 页面后会执行该函数
function test() {
var iframe = document.getElementById('myIframe');
// 重置 iframe 的 onload 事件程序,
// 此时经过后面代码重置 src 之后,
// http://www.laixiangran.cn/a.html 页面与该 iframe 在同一个源了,可以相互访问了
iframe.onload = function() {
var data = iframe.contentWindow.name; // 4. 获取 iframe 里的 window.name
console.log(data); // hello world!
};
// 3. 重置一个与 http://www.laixiangran.cn/a.html 页面同源的页面
iframe.src = 'http://www.laixiangran.cn/c.html';
}
</script>
页面 http://laixiangran.cn/b.html 的代码:
<script type="text/javascript">
// 1. 给当前的 window.name 设置一个 http://www.laixiangran.cn/a.html 页面想要得到的数据值
window.name = "hello world!";
</script>
- location.hash跨域
location.hash 方式跨域,是子框架修改父框架 src 的 hash 值,通过这个属性进行传递数据,且更改 hash 值,页面不会刷新。但是传递的数据的字节数是有限的。
页面 http://www.laixiangran.cn/a.html 的代码:
<iframe src="http://laixiangran.cn/b.html" id="myIframe" onload="test()" style="display: none;">
<script>
// 2. iframe载入 "http://laixiangran.cn/b.html 页面后会执行该函数
function test() {
// 3. 获取通过 http://laixiangran.cn/b.html 页面设置 hash 值
var data = window.location.hash;
console.log(data);
}
</script>
页面 http://laixiangran.cn/b.html 的代码:
<script type="text/javascript">
// 1. 设置父页面的 hash 值
parent.location.hash = "world";
</script>
内容安全策略(CSP)
内容安全策略(Content Security Policy,简称CSP)是一种以可信白名单作机制,来限制网站是否可以包含某些来源内容,缓解广泛的内容注入漏洞,比如 XSS。 简单来说,就是我们能够规定,我们的网站只接受我们指定的请求资源。默认配置下不允许执行内联代码(<script>块内容,内联事件,内联样式),以及禁止执行eval() , newFunction() , setTimeout([string], …) 和setInterval([string], …) 。
- CSP使用方式
CSP可以由两种方式指定: HTTP Header 和 HTML。
通过定义在HTTP响应 header 中使用:
"Content-Security-Policy:" 策略集
通过定义在 HTML meta标签中使用:
<meta http-equiv="content-security-policy" content="策略集">
策略是指定义 CSP 的语法内容。
如果 HTTP 头 与 meta 标签同时定义了 CSP,则会优先采用 HTTP 头的 。
定义后,凡是不符合 CSP策略的外部资源都会被阻止加载。
- CSP语法
策略
每一条策略都是指令与指令值组成:
Content-Security-Policy:指令1 指令值1
策略与策略之间用分号隔开,例如
Content-Security-Policy:指令1 指令值1;指令2 指令值2;指令3 指令值3
在一条策略中,如果一个指令中有多个指令值,则指令值之间用空号隔开:
Content-Security-Policy:指令a 指令值a1 指令值a2
CSP指令
default-src : 定义针对所有类型(js/image/css/font/ajax/iframe/多媒体等)资源的默认加载策略,如果某类型资源没有单独定义策略,就使用默认的。
script-src : 定义针对 JavaScript 的加载策略。
style-src : 定义针对样式的加载策略。
img-src : 定义针对图片的加载策略。
font-src : 定义针对字体的加载策略。
media-src : 定义针对多媒体的加载策略,例如:音频标签和视频标签。
object-src : 定义针对插件的加载策略,例如:、、。
child-src :定义针对框架的加载策略,例如: ,。
connect-src : 定义针对 Ajax/WebSocket 等请求的加载策略。不允许的情况下,浏览器会模拟一个状态为400的响应。
sandbox : 定义针对 sandbox 的限制,相当于 的sandbox属性。
report-uri : 告诉浏览器如果请求的资源不被策略允许时,往哪个地址提交日志信息。
form-action : 定义针对提交的 form 到特定来源的加载策略。
referrer : 定义针对 referrer 的加载策略。
reflected-xss : 定义针对 XSS 过滤器使用策略。CSP指令值
*允许加载任何内容
‘none’ 不允许加载任何内容
‘self’ 允许加载相同源的内容
www.a.com 允许加载指定域名的资源
*.a.com 允许加载 a.com 任何子域名的资源
https://a.com 允许加载 a.com 的 https 资源
https: 允许加载 https 资源
data: 允许加载 data: 协议,例如:base64编码的图片
‘unsafe-inline’ 允许加载 inline 资源,例如style属性、onclick、inline js、inline css等
‘unsafe-eval’ 允许加载动态 js 代码,例如 eval()CSP例子
eg1:
所有内容均来自网站的自己的域:
Content-Security-Policy:default-src 'self'
eg2:
所有内容都来自网站自己的域,还有其他子域(假如网站的地址是:a.com):
Content-Security-Policy:default-src 'self' *.a.com
eg3:
网站接受任意域的图像,指定域(a.com)的音频、视频和多个指定域(a.com、b.com)的脚本
Content-Security-Policy:default-src 'self';img-src *;media-src a.com;script-src a.com b.com
- CSP默认特性
一、阻止内联代码执行
CSP除了使用白名单机制外,默认配置下阻止内联代码执行是防止内容注入的最大安全保障。
虽然CSP中已经对script-src和style-src提供了使用”unsafe-inline”指令来开启执行内联代码,但为了安全起见还是慎用”unsafe-inline”。
二、eval相关功能被禁用
用户输入字符串,然后经过eval()等函数转义进而被当作脚本去执行。这样的攻击方式比较常见。于是乎CSP默认配置下,eval() , newFunction() , setTimeout([string], …) 和setInterval([string], …)都被禁止运行。
同样CSP也提供了”unsafe-eval”去开启执行eval()等函数,但强烈不建议去使用”unsafe-eval”这个指令。
CSP分析报告
可以用report-uri指令使浏览器发送HTTP POST请求把攻击报告以JSON格式传送到你指定的地址。接下来给大家介绍你的站点如何配置来接收攻击报告。
启用报告
默认情况下,违规报告不会发送。为了能使用违规报告,你必须使用report-uri指令,并至少提供一个接收地址。
Content-Security-Policy: default-src 'self'; report-uri http://reportcollector.example.com/collector.cgi
如果想让浏览器只汇报报告,不阻止任何内容,可以改用Content-Security-Policy-Report-Only头。
违规报告语法
该报告JSON对象包含以下数据:
blocked-uri:被阻止的违规资源
document-uri:拦截违规行为发生的页面
original-policy:Content-Security-Policy头策略的所有内容
referrer:页面的referrer
status-code:HTTP响应状态
violated-directive:违规的指令
违规报告例子
http://example.com/signup.html 中CSP 规定只能加载cdn.example.com的CSS样式。
Content-Security-Policy: default-src 'none'; style-src cdn.example.com; report-uri /test/csp-report.php
signup.html中的代码类似与这样:
<!DOCTYPE html>
<html>
<head>
<title>Sign Up</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
... Content ...
</body>
</html>
你能从上面的代码找出错误吗?策略是只允许加载cdn.example.com中的CSS样式。但signup.html试图加载自己域的style.css样式。这样违反了策略,浏览器会向 http://example.com/test/csp-report.php 发送POST请求提交报告,发送格式为JSON格式。
{
"csp-report": {
"document-uri": "http://example.com/signup.html",
"referrer": "",
"blocked-uri": "http://example.com/css/style.css",
"violated-directive": "style-src cdn.example.com",
"original-policy": "default-src 'none'; style-src cdn.example.com; report-uri /_/csp-reports",
}
}
原文链接:https://blog.csdn.net/guo15890025019/article/details/123179250