🔒 CSP——前端安全第一道防线

41

cyber-security-cybersecurity-device-60504.jpg

⭐️ 更多前端技术和知识点,搜索订阅号 JS 菌 订阅

内容安全策略的主要作用就是尽量降低网站遭受 XSS 跨站脚本攻击的可能。浏览器没办法区分要执行的代码是否为页面本身的还是恶意注入的,XSS 就是利用这一点对网站进行攻击。 🙁

CSP 的全称是 Content-Security-Policy 在白名单策略中,可以使用他来指定浏览器仅渲染或执行来自白名单中的资源。即便是被恶意注入了脚本,因为脚本并不在白名单中,因此不会执行。 🌝

还可以使用 CSP 指定使用 HTTP 还是 HTTPS 从而避免数据包嗅探攻击

CSP 支持在 html 的 meta 标签中和 HTTP 头中使用

单个指令

20190329095556.png

语法规则: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 这个网站的资源,那么因为同源策略,这个图片不会被显示出来 ❌

20190329090405.png

可以看到 CSP 策略以及那个生效,页面中的图片没有展示出来

报错如上图 📌

20190329090443.png

我们尝试修改一下该策略让 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 资源

⚠️ 注意书写多个策略应当符合规范:

20190329095521.png

效果如下:

20190329094643.png

不出意外,两者都可被正常加载,但内联脚本均无法加载:

20190329094750.png

当点击 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)
})

20190329103728.png

另外,在 CSP Level 3 中还可以通过构造函数自定义事件:

20190329103912.png

报告模式和违例报告

另外,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...'))

刷新浏览器打开调试面板:

20190329105755.png

报告已发送到 report-uri,后台打开终端可看到报告详细信息:

20190329110738.png

在其他地方使用

html 的 meta 标签也可以配置 CSP

20190329102820.png

写法如上,将 http-equiv 属性设置为 Content-Security-Policy 指令则写在 content 属性中即可

在代理服务器 nginx 中使用

20190329103006.png

在 nginx 中使用 add_header 增加 http 头,下面是个例子:

add_header Content-Security-Policy "default-src 'self';"

JS 菌公众账号

请关注我的订阅号,不定期推送有关 JS 的技术文章,只谈技术不谈八卦 😊


如果觉得我的文章对你有用,请随意赞赏

你可能感兴趣的

载入中...