⭐️ 更多前端技术和知识点,搜索订阅号 JS 菌 订阅
内容安全策略的主要作用就是尽量降低网站遭受 XSS 跨站脚本攻击的可能。浏览器没办法区分要执行的代码是否为页面本身的还是恶意注入的,XSS 就是利用这一点对网站进行攻击。????
CSP 的全称是 Content-Security-Policy 在白名单策略中,可以使用他来指定浏览器仅渲染或执行来自白名单中的资源。即便是被恶意注入了脚本,因为脚本并不在白名单中,因此不会执行。????
还可以使用 CSP 指定使用 HTTP 还是 HTTPS 从而避免数据包嗅探攻击
CSP 支持在 html 的 meta 标签中和 HTTP 头中使用
单个指令
语法规则:Content-Security-Policy: <policy-directive>; <policy-directive>
比方说限制 img 标签的 src 只能使用同源的:
Content-Security-Policy: img-src ‘self’
在后台创建个简单的服务 ????:
const express = require(‘express’)
const app = express()
const html = `
<!DOCTYPE html>
<head>
<meta charset=”UTF-8″>
</head>
<body>
<img src=”http://httpbin.org/image/png” alt=””>
</body>
</html>
`
app.get(‘/’, function(req, res) {
res.set(‘Content-Security-Policy’, “img-src ‘self'”)
res.end(html)
res.type(‘.html’)
})
app.listen(5500, ‘localhost’, () => console.log(‘listening…’))
服务端返回一个 html 数据,其中包含一个 img 标签,src 指向 httpbin.org 这个网站的资源,那么因为同源策略,这个图片不会被显示出来 ❌
可以看到 CSP 策略以及那个生效,页面中的图片没有展示出来
报错如上图 ????
我们尝试修改一下该策略让 httpbin 的资源生效
app.get(‘/’, function(req, res) {
+ res.set(‘Content-Security-Policy’, img-src http://httpbin.org”)
res.end(html)
res.type(‘.html’)
})
这样图片就可以加载出来了
⚠️ 一定要注意在使用策略的时候针对关键字 需要加上引号 不然会被认为是一个服务器
多个指令
针对 XSS 攻击的内联脚本,如果攻击者使用 script 在页面中加载恶意代码会导致严重问题 ❗️
CSP 针对这种攻击也有相应的解决办法——禁止内联脚本,包括 script 标签中的脚本,javascript: 的脚本等
如果非要使用内联脚本,那么一种方式是在 HTTP 头中增加一条 Content-Security-Policy: script-src unsafe-inline 另一种方法是在 Level 2 的 CSP 策略中计算内联脚本的 SHA 哈希值:
<script>alert(‘Hello, world.’);</script> 这个代码的哈希值计算结果放在 CSP 里面:Content-Security-Policy: script-src ‘sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZDob_Tng=’
下面是个例子 ????
我们只允许 self 或 75CDN 的 js 资源在页面中能够正常加载:
const html = `
<!DOCTYPE html>
<head>
<meta charset=”UTF-8″>
</head>
<body>
+ <script src=”https://lib.baomitu.com/jquery/3.3.1/jquery.slim.min.js”></script>
+ <img onClick=”javascript:console.log(‘hack’)” src=”http://httpbin.org/image/png” alt=””>
+ <script src=”./main.js”></script>
+ <script>
+ alert(‘hello’)
+ </script>
</body>
</html>
`
app.get(‘/’, function(req, res) {
+ res.set(‘Content-Security-Policy’, “script-src https://lib.baomitu.com ‘self’; img-src http://httpbin.org”)
res.end(html)
res.type(‘.html’)
})
+ app.use(express.static(‘public’))
这里我们有两种执行 js 的模式一种是 javascript: 一种是 script 内联标签的形式,在 CSP 中我们设置了只允许 https://cdn.baomitu.com/ 和 self 的 JS 资源
⚠️ 注意书写多个策略应当符合规范:
效果如下:
不出意外,两者都可被正常加载,但内联脚本均无法加载:
当点击 img 标签时报错
其他众多指令还有:
child-src:为 web workers 和其他内嵌浏览器内容定义 合法的源,例如用 <frame> 和 <iframe> 加载到页面的内容。如果开发者希望管控内嵌浏览器内容和 workers,那么应分别使用 frame-src 和 worker-src 指令,而不是 child-src。
connect-src:限制能通过脚本接口加载的 URL。
default-src:为其他取指令提供备用服务 fetch directives.
font-src:限制通过 @font-face 加载的字体源。
frame-src:限制通过类似 <frame> 和 <iframe> 标签加载的内嵌内容源。
img-src: 限制图片和图标源
manifest-src:限制 application manifest 文件源。
media-src:限制通过 <audio> 或 <video> 标签加载的媒体文件源。
object-src:限制通过 <object>, <embed>,<applet> 标签加载源。
script-src:限制 javascript 源。
style-src:限制层叠样式表文件源。
worker-src:限制 Worker, SharedWorker, 或者 ServiceWorker 脚本源。
详情见 CSP2 文档:https://www.w3.org/TR/CSP2/#d…
事件处理函数
当违反了内容安全策略,浏览器会触发一个名为 securitypolicyviolation 的事件,该事件详细描述了被禁止的 URI 地址、违反的策略指令、时间戳等信息 ????
该事件是在 CSP Level 2 中定义的
document.addEventListener(“securitypolicyviolation”, (e) => {
console.dir(e)
})
另外,在 CSP Level 3 中还可以通过构造函数自定义事件:
报告模式和违例报告
另外,CSP 策略可以设置为 report-only,这样 CSP 就不是强制性的,通过指定 report-uri 如果企图违反所建立的策略,那么就会自动发送违规的报告到这个地址上 ????
我们重置代码并增加解析 body 的依赖,在触发违反策略的情况下,服务端打印报告信息 ????
const express = require(‘express’)
+ const bodyParser = require(‘body-parser’)
const app = express()
const html = `
<!DOCTYPE html>
<head>
<meta charset=”UTF-8″>
</head>
<body>
+ <img src=”http://httpbin.org/image/png” alt=””>
</body>
</html>
`
+ app.use(bodyParser.json({type: ‘application/csp-report’}))
app.get(‘/’, function(req, res) {
+ res.set(‘Content-Security-Policy’, “img-src ‘self’; report-uri http://localhost:5500/reporter”)
res.type(‘.html’)
res.end(html)
})
+ app.post(‘/reporter’, function(req, res) {
+ res.end()
+ console.log(req.body)
+ })
app.use(express.static(‘public’))
app.listen(5500, ‘localhost’, () => console.log(‘listening…’))
刷新浏览器打开调试面板:
报告已发送到 report-uri,后台打开终端可看到报告详细信息:
在其他地方使用
html 的 meta 标签也可以配置 CSP
写法如上,将 http-equiv 属性设置为 Content-Security-Policy 指令则写在 content 属性中即可
在代理服务器 nginx 中使用
在 nginx 中使用 add_header 增加 http 头,下面是个例子:
add_header Content-Security-Policy “default-src ‘self’;”
请关注我的订阅号,不定期推送有关 JS 的技术文章,只谈技术不谈八卦 ????