【问题标题】:node.js - correct content security policy for socket.io (web sockets) using helmetnode.js - 使用头盔的 socket.io(网络套接字)的正确内容安全策略
【发布时间】:2015-01-24 04:49:30
【问题描述】:

我正在尝试在节点服务器中实施内容安全策略 (CSP),但在设置 socket.io 时遇到了问题。看起来我在下面的代码中错误地设置了connectSrc。有人可以建议设置头盔的正确方法,以便浏览器允许网络套接字连接吗?提前致谢!

我正在使用头盔模块来生成 CSP;以下是设置 CSP 的代码:

securitySetup = function(app) {
  var connectSources, helmet, scriptSources, styleSources;
  helmet = require("helmet");
  app.use(helmet());
  app.use(helmet.hidePoweredBy());
  app.use(helmet.noSniff());
  app.use(helmet.crossdomain());
  scriptSources = ["'self'", "'unsafe-inline'", "'unsafe-eval'", "ajax.googleapis.com"];
  styleSources = ["'self'", "'unsafe-inline'", "ajax.googleapis.com"];
  connectSources = ["'self'"];
  return app.use(helmet.contentSecurityPolicy({
    defaultSrc: ["'self'"],
    scriptSrc: scriptSources,
    styleSrc: styleSources,
    connectSrc: connectSources,
    reportUri: '/report-violation',
    reportOnly: false,
    setAllHeaders: false,
    safari5: false
  }));
};

这适用于所有 HTTP/AJAX 流量,但对于 ws:// 协议失败。建立 socket.io 连接时,我在 chrome 调试器中收到此错误:

Refused to connect to 'ws://localhost:3000/socket.io/1/websocket/ubexeZHZiAHwAV53WQ7u' because it violates the following Content Security Policy directive: "connect-src 'self'".

【问题讨论】:

  • 我认为你的connectSources 规则是负责任的。为您的应用程序提供服务的网络服务器是否也在端口 3000 上运行? content-security-policy.comSource List Reference 'self' 下说:允许从同一来源(相同的方案、主机和端口)加载资源。
  • @marco:是的,Web 套接字服务器与 Web 应用程序的 http 服务器是同一台服务器。看起来相同的“方案”部分是问题所在(ws:// vs http://)
  • 那么我认为 Patrik Simeks 的回答也应该适合你。也许您可以对此发表评论或接受它作为正确答案(如果有效)?

标签: javascript node.js websocket socket.io content-security-policy


【解决方案1】:

添加指定协议的地址解决了我的问题。

connectSources = ["'self'", "ws://localhost:3000"]

【讨论】:

  • 是的,我们可以这样做,但是这样就违背了“self”关键字的目的,不是吗?如果我们需要添加确切的服务器名称、端口等,那我们为什么需要这个自我业务?
【解决方案2】:

仔细阅读Content Security Policy spec 解释了为什么'self' 不起作用:

'self' 匹配具有相同主机、端口、和方案的请求。由于您使用http://https:// 方案加载的原始页面,'self' 不会匹配使用ws:// 的连接。

这使得'self' 对于 websocket 连接毫无用处。除非我遗漏了什么,否则我认为这是规范中的错误。

【讨论】:

  • 如果能以某种方式解决这个问题会很棒,尤其是对于部署到内部网络的 SPA 的麻烦(域可能并不总是已知或可能经常更改)。
【解决方案3】:

您可以使用 ws: 或 wss: 作为 websocket 的关键字,这是我的代码使用

app.use(helmet.csp({
  'default-src': ["'self'"],
  'connect-src': [
    "'self'" , "blob:",
    'wss:',
    'websocket.domain',
  ],
  'font-src': ["'self'",'s3.amazonaws.com',"maxcdn.bootstrapcdn.com"],
  'img-src': ["'self'", 'data:'],
  'style-src': ["'self'","maxcdn.bootstrapcdn.com",'s3.amazonaws.com',"'unsafe-inline'"],
  'script-src': ["'self'","'unsafe-inline'","'unsafe-eval'",'blob:'],
  reportOnly: false,
  setAllHeaders: false,
  safari5: false
}))

wss://websocket.domain 是 websocket 的域,所以如果你可以将它用于 localhost

【讨论】:

    【解决方案4】:

    这里有一种自动添加当前主机和端口的方法(es6语法):

    import csp from 'helmet-csp';
    
    app.use((req, res, next) => {
      let wsSrc = (req.protocol === 'http' ? 'ws://' : 'wss://') + req.get('host');
    
      csp({
        connectSrc: ['\'self\'', wsSrc],
      })(req, res, next);
    });
    

    【讨论】:

      【解决方案5】:

      参加聚会有点晚了,但我想我会在这个主题上加入一些东西。 @Ed4 非常正确,'self' 确实只会匹配相同的主机、端口和方案。一种解决方法是将其包含在一种或多种形式中:

      connect-src: wss: - 允许连接到整个 wss 方案 - 基本上是 any 网络套接字(可能不理想)

      connect-src: wss://yoursite.domain.com - 将其限制在特定端点。这是最理想的,但如果您的子域在部署之间发生变化(就像我们所做的那样),则可能会受到限制

      connect-src: wss://*.domain.com - 可以在其中使用通配符来加强安全性。这就是我们所做的

      TL;DR - 使用通配符使事情更具体,而不是让自己接触任何网络套接字/

      请参阅 Google 开发人员的这段话:

      每个指令中的源列表是灵活的。您可以通过方案(data:、https:)指定来源,或者从仅主机名(example.com,匹配该主机上的任何来源:任何方案、任何端口)到完全限定的 URI(https://example.com:443 ,仅匹配 HTTPS、仅 example.com 和仅端口 443)。接受通配符,但只能作为方案、端口或在主机名的最左侧位置:://.example.com:* 将匹配 example.com 的所有子域(但不是 example .com 本身),在任何端口上使用任何方案。

      https://developers.google.com/web/fundamentals/security/csp/

      【讨论】:

      • 似乎这在 Chrome 中不再是问题(“自我”按预期工作)但在 Safari 中我必须做 connect-src: wss://*.domain.com 并且它有效。
      猜你喜欢
      • 2023-02-23
      • 2021-01-12
      • 2023-04-03
      • 1970-01-01
      • 2021-05-30
      • 1970-01-01
      • 1970-01-01
      • 2021-12-24
      • 1970-01-01
      相关资源
      最近更新 更多