关于HTTP中的CSP

  • 在HTTP协议中为了使我们的网站足够的安全,经常要用到CSP(Content-Security-Policy)内容安全策略
  • CSP的作用
    • 限制网站中资源的获取,以及请求发送到哪里
    • 报告资源获取越权
  • CSP的限制方式
    • default-src限制全局和链接请求有关的东西
    • 指定资源类型
      • connect-src
      • manifest-src
      • img-src
      • font-src
      • media-src
      • style-src
      • frame-src
      • script-src
      • 网页上和链接有关的…

程序示例

1 ) 禁用行内脚本

  • test.html

    • 在html中写js,称为inline script
    • 写在页面里的脚本会有安全问题,如XSS
    • 在页面注入一些脚本来导致,比如网站中的富文本编辑器插入的文本
    • 处于安全考虑,不能直接执行写在页面里的脚本
    • 我们要想办法把它限制掉
    <script>
        console.log('inline js');
    </script>
    
  • server.js

    const http = require('http');
    const fs = require('fs');
    
    http.createServer((req, res) => {
        console.log('req come: ', req.url);
        const html = fs.readFileSync('test.html', 'utf8');
        res.writeHead(200, {
            'Content-Type': 'text/html',
            'Content-Security-Policy': 'default-src http: https:'
        });
        res.end('');
    }).listen(8000, () => {
        console.log('server is running on port 8000');
    });
    
  • 一般情况下谷歌会报这个错

    Refused to execute inline script because it violates the following Content Security Policy directive: "default-src http: https:". Either the 'unsafe-inline' keyword, a hash ('sha256-X4GPgiK5x/RXRod6t6oOY0TqUNEVnds4EvCOtFqUZUU='), or a nonce ('nonce-...') is required to enable inline execution. Note also that 'script-src' was not explicitly set, so 'default-src' is used as a fallback.
    
  • Safari会报这个错

    Refused to execute a script because its hash, its nonce, or 'unsafe-inline' appears in neither the script-src directive nor the default-src directive of the Content Security Policy.
    
  • 而且页面中的脚本不会被执行, 这样就限制了页面脚本的执行

2 ) 禁用行内脚本,允许外链脚本

  • 如果我们允许外链脚本不允许行内脚本,还需要修改一下代码
  • test.html
    <script>
        console.log('inline js');
    </script>
    <script src="/test.js"></script>
    
  • server.js
    const http = require('http')
    const fs = require('fs')
    
    http.createServer(function (req, res) {
    console.log('request comes', req.url)
    if (req.url === '/') {
        const html = fs.readFileSync('test.html', 'utf8')
        res.writeHead(200, {
            'Content-Type': 'text/html',
            'Content-Security-Policy': 'default-src http: https:'
        });
        res.end(html)
    } else {
        res.writeHead(200, {
            'Content-Type': 'application/javascript'
        });
        res.end('console.log("script loaded")')
    }
    }).listen(8000, () => {
        console.log('server is running on port 8000');
    });
    
  • 这个时候,行内被限制执行,外链脚本会被执行

3 ) 限制外链脚本域名进行加载

  • 稍微修改下程序
  • test.html
    <script src="/test.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
    
  • server.js
    const http = require('http')
    const fs = require('fs')
    
    http.createServer(function (req, res) {
    console.log('request comes', req.url)
    if (req.url === '/') {
        const html = fs.readFileSync('test.html', 'utf8')
        res.writeHead(200, {
            'Content-Type': 'text/html',
            'Content-Security-Policy': 'default-src \'self\' https://cdn.bootcdn.net'
        });
        res.end(html);
    } else {
        res.writeHead(200, {
            'Content-Type': 'application/javascript'
        });
        res.end('console.log("script loaded")');
    }
    }).listen(8000, () => {
        console.log('server is running on port 8000');
    });
    
  • 这样的配置可以使外链脚本不管是本站的还是指定的其他域名脚本都可以访问
  • 如果去除了指定的https://cdn.bootcdn.net,谷歌浏览器一般就会出现这个报错
    Refused to load the script 'https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js' because it violates the following Content Security Policy directive: "default-src 'self'". Note that 'script-src' was not explicitly set, so 'default-src' is used as a fallback.
    
  • 此时站内脚本可执行,站外脚本被限制了

4 ) form表单的提交限制

  • 上述的设置default-src \'self\'针对表单的跳转是无法限制的,我们稍微修改下程序

  • test.html

    <form action='http://www.taobao.com'>
        <button type='submit'>Click Here</button>
    </form>
    
  • server.js

    const http = require('http')
    const fs = require('fs')
    
    http.createServer(function (req, res) {
    console.log('request comes', req.url)
    if (req.url === '/') {
        const html = fs.readFileSync('test.html', 'utf8')
        res.writeHead(200, {
            'Content-Type': 'text/html',
            'Content-Security-Policy': 'default-src \'self\'; form-action \'self\''
        });
        res.end(html)
    } else {
        res.writeHead(200, {
            'Content-Type': 'application/javascript'
        });
        res.end('console.log("script loaded")');
    }
    }).listen(8000, () => {
        console.log('server is running on port 8000');
    });
    
  • 此时就会出现限制性报错

    Refused to send form data to 'http://www.taobao.com/?' because it violates the following Content Security Policy directive: "form-action 'self'".
    
  • 如果去除了; form-action \'self\' 则无法限制,表单会正常的跳转

  • 更多相关内容可以访问:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CSP

5 ) 全局限制变成只配置脚本限制

  • 使用default-src进行全局限制会存在一些问题, 比如图片什么的请求可能会不方便
  • 我们只针对脚本作限制,可更改为script-src

6 ) 出现了不安全的情况, 我们可以让其发送请求主动给我们汇报

  • 只需要添加; report-uri /report 即可
  • 如果出现了情况,它会主动向 /report 发送报告数据
  • 我们针对/report的请求做一些程序处理即可
  • 其发送格式如下:
    {
        "csp-report": {
            "blocked-uri": "",
            "disposition": "",
            "document-uri": "",
            "effective-directive": "",
            "original-policy": "",
            "referer": "",
            "line-number": "",
            "script-sample": "",
            "source-file": "",
            "status-code": "",
            "violated-directive": ""
        }
    }     
    
  • blocked-uri 表示策略范围内执行限制的路径
  • disposition 表示强制加载enforce还是允许加载report
    • 一般 Content-Security-Policy 都是强制
    • 如果是Content-Security-Policy-Report-Only 则是允许并报告
  • document-uri 表示当前页面地址
  • effective-directive 表示如何进行限制的
  • original-policy 表示策略的具体内容,就是服务端设置的内容
  • line-number 表示在html中的第几行
  • source-file 表示当前请求的路径
  • status-code 表示请求的状态码

7 ) 除了服务端写Header,还可以在html写meta标签

  • test.html
    <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'self';">
    
  • server.js 中去除Content-Security-Policy的设置
  • 两者效果一致,但是html中无法设置report-uri指令,只能在服务端来设置
  • 如果存在ajax的请求,可以设置connect-src
  • test.html
    <meta http-equiv="Content-Security-Policy" content="connect-src 'self'">
    <script>
        fetch('http://www.taobao.com'); // 用于限制ajax的示例
    </script>
    
  • 会报这个错误来限制
    Refused to connect to 'http://www.taobao.com/' because it violates the following Content Security Policy directive: "connect-src 'self'".
    Refused to connect to 'http://www.taobao.com/' because it violates the document's Content Security Policy.
    Uncaught (in promise) TypeError: Failed to fetch
    
  • 综上所述,仍推荐服务端来进行设置,可以更灵活更好的设置
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐