文章目录
1. 前言
内容安全策略(CSP),其核心思想十分简单:网站通过发送一个 CSP 头部,来告诉浏览器什么是被授权执行的与什么是需要被禁止的。其被誉为专门为解决XSS攻击
而生的神器。
xss是跨域脚本攻击的缩写,详细可以参见《XSS跨域脚本攻击》,举例来说,可以在页面被注入非法的js脚本,然后产生不安全的后果。
内容安全策略 (CSP) 是一个额外的安全层,用于检测并削弱某些特定类型的攻击,包括跨站脚本 (XSS) 和数据注入攻击等。无论是数据盗取、网站内容污染还是散发恶意软件,这些攻击都是主要的手段。
csp不能100%杜绝xss隐患,但是可以大大减少该隐患
CSP 的实质就是白名单制度,开发者明确告诉客户端,哪些外部资源可以加载和执行,等同于提供白名单。它的实现和执行全部由浏览器完成,开发者只需提供配置。
CSP 大大增强了网页的安全性。攻击者即使发现了漏洞,也没法注入脚本,除非还控制了一台列入了白名单的可信主机。
2. CSP作用
作用主要有两点:
- 使用白名单的方式告诉客户端(浏览器)允许加载和不允许加载的资源。
- 向服务器举报这种越界加载了资源的行为,以便做出更加针对性的措施予以绝杀。
2.1 什么是资源
首先要解释下csp中的资源的概念,否则很难理解csp的作用,简单来说资源就是html出现的特定元素,比如css、iframe、script,这些元素都有一个共同点,就是有多种来源(因为只有多种来源,才有可能被注入,如果都是写死的,别人还怎么注入呢?),我们以script为例:
例子来源《JavaScript 实例》 “在何处插入 JavaScript”章节
原文可能有误,列举的“外部文件中的 JavaScript”和“外部文件夹中的 JavaScript“内容一模一样,那我们就暂且把他们归为一类。
Head 中的 JavaScript:
<head>
<script>
function myFunction() {
document.getElementById("demo").innerHTML = "段落已被更改。";
}
</script>
</head>
Body 中的 JavaScript:
<body>
<script>
function myFunction() {
document.getElementById("demo").innerHTML = "段落已被更改。";
}
</script>
</body>
外部文件中的 JavaScript,相对路径,同源 采用相对目录引入:
<script src="/demo/myScript.js"></script>
外部 url 中的 JavaScript,绝对路径,引入一个非同源的外部js:
<script src="https://www.w3school.com.cn/demo/myScript.js"></script>
正是由于script有多种来源,csp可以增加规则,比如限制只能从同源加载js脚本,禁止从外部加载js,鬼知道外部的js里面有没有攻击脚本呢:
我们可以使用script-src
满足我们的需求 ,'self'
,注意单引号不能丢,这个是专有名词,后面我们会详细介绍语法,作用是表示同源:
Content-Security-Policy: script-src 'self'
2.2 资源列表
看一下选项限制各类资源的加载:
base-uri: 用于限制可在页面的 <base> 元素中显示的网址。
child-src: 用于列出适用于工作线程和嵌入的帧内容的网址。例如:child-src https://youtube.com 将启用来自 YouTube(而非其他来源)的嵌入视频。 使用此指令替代已弃用的 frame-src 指令。
connect-src: 用于限制可(通过 XHR、WebSockets 和 EventSource)连接的来源。
font-src: 用于指定可提供网页字体的来源。Google 的网页字体可通过 font-src https://themes.googleusercontent.com 启用。
form-action: 用于列出可从 <form> 标记提交的有效端点。
frame-ancestors: 用于指定可嵌入当前页面的来源。此指令适用于 <frame>、<iframe>、<embed> 和 <applet> 标记。此指令不能在 <meta> 标记中使用,并仅适用于非 HTML 资源。
frame-src: 已弃用。请改用 child-src。
img-src: 用于定义可从中加载图像的来源。
media-src: 用于限制允许传输视频和音频的来源。
object-src: 可对 Flash 和其他插件进行控制。
plugin-types: 用于限制页面可以调用的插件种类。
report-uri: 用于指定在违反内容安全政策时浏览器向其发送报告的网址。此指令不能用于 <meta> 标记,这就是举报电话。
style-src: 是 script-src 版的样式表。
upgrade-insecure-requests: 指示 User Agent 将 HTTP 更改为 HTTPS,重写网址架构。 该指令适用于具有大量旧网址(需要重写)的网站。
简单归类一下:
-
带src的,很好理解,表明和url相关,自然与同源非通用相关,即跨域概念相关
-
不带src的,表示其他限制,比如
block-all-mixed-content:HTTPS 网页不得加载 HTTP 资源(浏览器已经默认开启) upgrade-insecure-requests:自动将网页上所有加载外部资源的 HTTP 链接换成 HTTPS 协议 plugin-types:限制可以使用的插件格式 sandbox:浏览器行为的限制,比如不能有弹出窗口等。
更详细的分类,参见《Content-Security-Policy》
这么多指令都要写?写起来不是很麻烦,不是的。你只需要写自己要求限制的指令就行,没写的都会默认没有限制。
比如你可以仅限制js的来源,css的来源可以不限制。
但是如果我想同时限制js、css、等所有的资源来源怎么办?一个个加起来太费劲了!
简单!你可以通过指定一个 default-src
指令替换大部分指令的默认行为,也就说如果你写了default-src 指令,那其他没写的指令都会服从default-src 的规则。
default-src
可以简单理解成all,置于到底包含局部元素还是包含所有的,待研究
3. csp语法
在HTTP Header上(首选)和HTML上均可使用,如何同时配置,以http为准!
详尽的语法可以参见参见《Content-Security-Policy》
语法:
Content-Security-Policy: <policy-directive>; <policy-directive>
允许配置多条<policy-directive>
,多个配置中间以分号
分隔;
<policy-directive>
又是一个复杂结构体,对应类似如下结构:
default-src <source> <source>
允许一个或多个source来源,多个源通过空格
分开.。
我们以nodejs express框架开发的服务器端为例,在响应处设置响应头,Content-Security-Policy
作为key,<policy-directive>
作为value:
app.all('*', function(req, res, next) {
res.header("Content-Security-Policy", "default-src https://127.0.0.1:8881")
next()
});
3.1 完整示例
3.1.1 复现iframe同源限制
完整的实例如下,基于《http请求中的 OPTIONS 详解 & 跨域》中的<2.1.1.1 演示简单请求被拒绝>章节:
所需文件:
express_8881.js、express_8883.js、public/index8881.html、public/index8883.html
修改代码:
改动一:修改public/index8883.html,增加一个firame引用:
<button onclick="send()">send request to 8881</button>
<br>
<!--增加iframe -->
<iframe src="http://127.0.0.1:8881/public/index8881.html"></iframe>
改动二:修改express_8883.js,增加一个iframe同源限制:
var app = express();
app.all('*', function(req, res, next) {
res.header("Content-Security-Policy", "child-src 'self'")
next()
});
注意:csp v2 中frame-src被废弃了,用child-src代替,当然在最新的csp v3中,child-src也被废弃了!
运行,查看:
页面修改完后效果,发现加载失败:
打开控制台,发现有错误信息:
Refused to frame 'http://127.0.0.1:8881/' because it violates the following Content Security Policy directive: "child-src 'self'". Note that 'frame-src' was not explicitly set, so 'child-src' is used as a fallback. index8883.html:23
如果去掉改动二的话,iframe是能加载成功的!
3.2 语法深入解析
3.1 default-src
我们以default-src为例讲解如何看文档:
片头提到了受default-src影响的所有元素列表,正好回答了我们前面的疑问。
语法:
default-src政策可以允许一个或多个来源,多个源通过空格分开:
Content-Security-Policy: default-src <source>;
Content-Security-Policy: default-src <source> <source>;
<source>
,有多种语法表示,可以是以下之一:
-
<host-source>
形式,通过主机名称(经dns转化,等价于url)、或url。简单来说,格式类似”http://test.example.com“,其中http://可以省略,默认的就是http://;端口号可以省略,默认是80。等价于”http://test.example.com:80“
支持*号,表示通配符
示例:
http://*.example.com 匹配使用http:URL方案从 example.com 的任何子域加载的所有尝试。 纠错 mail.example.com:443 匹配所有尝试访问 mail.example.com 上的端口443的尝试。省略了http:// https://store.example.com 匹配所有尝试访问store.example.com,必须使用https:,省略了端口号,默认80
因此,如果你的白名单端口号不是80的话, 一定要把端口列出来!
-
<scheme-source>
一种模式细节未研究
data: blob:
-
专有名词
单引号不能丢,表示特殊的语义
'self':当前域名,允许加载同源的外部资源,需要加引号 'none':禁止加载任何外部资源,需要加引号