Content Security Policy (CSP)
https://developer.mozilla.org/zh-CN/docs/Web/Security/CSP
它体现在:
- 限制资源获取
- 报告资源获取越权
限制方式
- default-src 限制全局
- 制定资源类型(资源类型如,connect-src, img-src, manifest-src, img-src, style-src, media-src, font-src, script-src, frame-src, ...)
下面我们来实验一下。
先是server.js 如下
const http = require('http')
const fs = require('fs')
const zlib = require('zlib')
http.createServer(function (request, response) {
console.log('request come', request.url)
const html = fs.readFileSync('test.html')
response.writeHead(200, {
'Content-Type': 'text/html'
})
response.end(html)
}).listen(8888)
console.log('serve listening on 8888')
然后test.html 如下。
MyHtml
hello world
don't speak
test.html 中有inline js 代码,我们不希望执行inline js 代码。因为XSS 攻击就是执行的inline js 代码。那么我们可以这样做,如下。
const http = require('http')
const fs = require('fs')
const zlib = require('zlib')
http.createServer(function (request, response) {
console.log('request come', request.url)
const html = fs.readFileSync('test.html')
response.writeHead(200, {
'Content-Type': 'text/html',
'Content-Security-Policy': 'default-src http: https:'
})
response.end(html)
}).listen(8888)
console.log('serve listening on 8888')
重启服务,刷新页面,就会发现不会执行inline js 并会在控制条打印出报错信息。
下面,我们将html 中加入一个src 它的内容请求自后台。如下。
MyHtml
hello world
don't speak
然后去改一下后台代码,如下。
const http = require('http')
const fs = require('fs')
const zlib = require('zlib')
http.createServer(function (request, response) {
console.log('request come', request.url)
if (request.url === '/') {
const html = fs.readFileSync('test.html')
response.writeHead(200, {
'Content-Type': 'text/html',
'Content-Security-Policy': 'default-src http: https:'
})
response.end(html)
} else {
response.writeHead(200, {
'Content-Type': 'application/javascript'
})
response.end('console.log("loaded script")')
}
}).listen(8888)
console.log('serve listening on 8888')
我们重启服务,刷新页面就可以看到:
不仅如此,我们还可以限制,通过外部链接加载的js 文件,它可以通过哪些域名进行加载。比如限制,只能根据本域名下的js 进行加载,如下。
const http = require('http')
const fs = require('fs')
const zlib = require('zlib')
http.createServer(function (request, response) {
console.log('request come', request.url)
if (request.url === '/') {
const html = fs.readFileSync('test.html')
response.writeHead(200, {
'Content-Type': 'text/html',
'Content-Security-Policy': 'default-src \'self\''
// cannot inline js
// 'Content-Security-Policy': 'default-src http: https:'
})
response.end(html)
} else {
response.writeHead(200, {
'Content-Type': 'application/javascript'
})
response.end('console.log("loaded script")')
}
}).listen(8888)
console.log('serve listening on 8888')
然后,我们在test.html 中加入一个外链script (不同域),如下。
MyHtml
hello world
don't speak
重启,刷新后,发现:
当然,也可以设置有些域名的js 外链可访问,如下。
const http = require('http')
const fs = require('fs')
const zlib = require('zlib')
http.createServer(function (request, response) {
console.log('request come', request.url)
if (request.url === '/') {
const html = fs.readFileSync('test.html')
response.writeHead(200, {
'Content-Type': 'text/html',
'Content-Security-Policy': 'default-src \'self\' https://cdn.bootcss.com/'
// cannot inline js
// 'Content-Security-Policy': 'default-src http: https:'
})
response.end(html)
} else {
response.writeHead(200, {
'Content-Type': 'application/javascript'
})
response.end('console.log("loaded script")')
}
}).listen(8888)
console.log('serve listening on 8888')
我们还可以限制form 表单的提交方向。form 表单不接受default-src 的限制.设置如下。
const http = require('http')
const fs = require('fs')
const zlib = require('zlib')
http.createServer(function (request, response) {
console.log('request come', request.url)
if (request.url === '/') {
const html = fs.readFileSync('test.html')
response.writeHead(200, {
'Content-Type': 'text/html',
'Content-Security-Policy': 'default-src \'self\'; form-action \'self\''
// 'Content-Security-Policy': 'default-src \'self\' https://cdn.bootcss.com/'
// cannot inline js
// 'Content-Security-Policy': 'default-src http: https:'
})
response.end(html)
} else {
response.writeHead(200, {
'Content-Type': 'application/javascript'
})
response.end('console.log("loaded script")')
}
}).listen(8888)
console.log('serve listening on 8888')
test.html 如下。
MyHtml
hello world
don't speak
注意:default-src 是将所有的src 属性限制了,包括 img 的src 。如果只想限制js 的src ,可以将default-src 改为 script-src .
汇报
内容安全策略当出现了,我们不希望出现的情况的时候,我们可以申请它向服务器发送一个请求来进行汇报。如下。
const http = require('http')
const fs = require('fs')
const zlib = require('zlib')
http.createServer(function (request, response) {
console.log('request come', request.url)
if (request.url === '/') {
const html = fs.readFileSync('test.html')
response.writeHead(200, {
'Content-Type': 'text/html',
'Content-Security-Policy': 'default-src \'self\'; form-action \'self\'; report-uri /report'
// 'Content-Security-Policy': 'default-src \'self\' https://cdn.bootcss.com/'
// cannot inline js
// 'Content-Security-Policy': 'default-src http: https:'
})
response.end(html)
} else {
response.writeHead(200, {
'Content-Type': 'application/javascript'
})
response.end('console.log("loaded script")')
}
}).listen(8888)
console.log('serve listening on 8888')
重启刷新,会发现,network 中会发送 report 请求。
我们同时,还可以不阻止src,只是发送report ,如下。
const http = require('http')
const fs = require('fs')
const zlib = require('zlib')
http.createServer(function (request, response) {
console.log('request come', request.url)
if (request.url === '/') {
const html = fs.readFileSync('test.html')
response.writeHead(200, {
'Content-Type': 'text/html',
'Content-Security-Policy-Report-Only': 'default-src \'self\'; form-action \'self\'; report-uri /report'
// 'Content-Security-Policy': 'default-src \'self\'; form-action \'self\'; report-uri /report'
// 'Content-Security-Policy': 'default-src \'self\' https://cdn.bootcss.com/'
// cannot inline js
// 'Content-Security-Policy': 'default-src http: https:'
})
response.end(html)
} else {
response.writeHead(200, {
'Content-Type': 'application/javascript'
})
response.end('console.log("loaded script")')
}
}).listen(8888)
console.log('serve listening on 8888')