为你的网站带上帽子 — 使用 helmet 保护 Express 应用

Express 基于 Node.js,是一款用于构建 Web 服务的优秀框架。它很容易上手,且得益于其中间件的概念,能够很方便地进行配置与拓展。尽管如今有各类各样的用于建立 Web 应用的框架,但个人第一选择始终是 Express。然而,直接使用 Express 不能彻底遵循安全性的最佳实践。所以咱们须要使用相似 helmet 的模块来改善应用的安全性。php

部署

在开始以前,请确认你已经安装好了 Node.js 以及 npm(或 yarn)。你能够在 Node.js 官网下载以及查看安装指南css

咱们将以一个新的工程为例,不过你也能够将这些功能应用于现有的工程中。html

在命令行中运行如下命令建立一个新的工程:前端

mkdir secure-express-demo
cd secure-express-demo
npm init -y
复制代码

运行如下命令安装 Express 模块:node

npm install express --save
复制代码

secure-express-demo 目录下建立一个名为 index.js 的文件,加入如下代码:android

const express = require('express');
const PORT = process.env.PORT || 3000;
const app = express();

app.get('/', (req, res) => {
  res.send(`<h1>Hello World</h1>`);
});

app.listen(PORT, () => {
  console.log(`Listening on http://localhost:${PORT}`);
});
复制代码

保存文件,试运行看看它是否能正常工做。运行如下命令启动服务:ios

node index.js
复制代码

访问 http://localhost:3000,你应该能够看到 Hello Worldgit

hello-world.png

检查 Headers

giphy.gif

如今让咱们经过增长与删除一些 HTTP headers 来改善应用安全性。你能够用一些工具来检查它的 headers,例如使用 curl 运行如下命令:github

curl http://localhost:3000 --include
复制代码

--include 标志可让其输出 response 的 HTTP headers。若是你没有安装 curl,也能够用你最经常使用浏览器开发者工具的 network 面板代替。web

你能够看到在收到的 response 中包含的如下 HTTP headers:

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 20
ETag: W/"14-SsoazAISF4H46953FT6rSL7/tvU"
Date: Wed, 01 Nov 2017 13:36:10 GMT
Connection: keep-alive
复制代码

通常来讲,由 X- 开头的 header 是非标准头部。请注意那个 X-Powered-By 的 header,它会暴露你使用的框架。对于攻击者来讲,这能够下降攻击成本,由于他们只专一攻击此框架的已知漏洞便可。

戴上头盔(helmet)

giphy.gif

来看看若是咱们使用 helmet 会发生什么。运行如下命令安装 helmet

npm install helmet --save
复制代码

helmet 中间件加入你的应用中。对 index.js 进行以下修改:

const express = require('express');
const helmet = require('helmet');
const PORT = process.env.PORT || 3000;
const app = express();

app.use(helmet());

app.get('/', (req, res) => {
  res.send(`<h1>Hello World</h1>`);
});

app.listen(PORT, () => {
  console.log(`Listening on http://localhost:${PORT}`);
});
复制代码

这样就使用了 helmet 的默认配置。接下来看看它作了什么事情。重启服务,再次经过如下命令检查 HTTP headers:

curl http://localhost:3000 --inspect
复制代码

新的 headers 会相似于下面这样:

HTTP/1.1 200 OK
X-DNS-Prefetch-Control: off
X-Frame-Options: SAMEORIGIN
Strict-Transport-Security: max-age=15552000; includeSubDomains
X-Download-Options: noopen
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Content-Type: text/html; charset=utf-8
Content-Length: 20
ETag: W/"14-SsoazAISF4H46953FT6rSL7/tvU"
Date: Wed, 01 Nov 2017 13:50:42 GMT
Connection: keep-alive
复制代码

首先值得庆祝的是 X-Powered-By header 不见了。但如今又多了好些新的 header,它们是作什么的呢?

X-DNS-Prefetch-Control

这个 header 对增长安全性并无太大做用。它的值为 off 时,将关闭浏览器对页面中 URL 的 DNS 预读取。DNS 预读取能够提升你的网站的性能,根据 MDN 描述,它能够增长 5% 或更高的图片加载速度。不过开启这项功能也可能会使用户在屡次访问同一个网页时缓存出现问题。

译注:缓存问题未查到资料,若是您了解这块请留言

它的默认值是 off,若是你但愿经过它提高性能,能够在调用 helmet() 时传入 { dnsPrefetchControl: { allow: true }} 开启 DNS 预读取。

X-Frame-Options

X-Frame-Options 可让你控制页面是否能在 <frame/><iframe/> 或者 <object/> 之类的页框内加载。除非你的确须要经过这些方式来打开页面,不然请经过下面的配置彻底禁用它:

app.use(helmet({
  frameguard: {
    action: 'deny'
  }
}));
复制代码

全部的现代浏览器都支持 X-Frame-Options。你也能够经过稍后将介绍的内容安全策略来控制它。

Strict-Transport-Security

它也被称为 HSTS(严格安全 HTTP 传输),用于确保在访问 HTTPS 网站时不出现协议降级(回到 HTTP)的状况。若是用户一旦访问了带有此 header 的 HTTPS 网站,浏览器就会确保未来再次访问次网站时不容许使用 HTTP 进行通讯。此功能有助于防范中间人攻击。

有时,当你使用公共 WiFi 时尝试访问 https://google.com 之类的门户网页时就能看到此功能运做。WiFi 尝试将你重定向到他们的门户网站去,但你曾经经过 HTTPS 访问过 google.com,且它带有 Strict-Transport-Security 的 header,所以浏览器将阻止重定向。

你能够访问 MDN 或者 OWASP wiki 查看更多相关信息。

X-Download-Options

这个 header 仅用于保护你的应用免受老版 IE 漏洞的困扰。通常来讲,若是你部署了不能被信任的 HTTP 文件用于下载,用户能够直接打开这些文件(而不须要先保存到硬盘去)而且能够直接在你 app 的上下文中执行。这个 header 能够确保用户在访问这种文件前必须将其下载到本地,这样就能防止这些文件在你 app 的上下文中执行了。

你能够访问 helmet 文档MSDN 博文查看更多相关信息。

X-Content-Type-Options

一些浏览器不使用服务器发送的 Content-Type 来判断文件类型,而使用“MIME 嗅探”,根据文件内容来判断内容类型并基于此执行文件。

假设你在网页中提供了一个上传图片的途径,但攻击者上传了一些内容为 HTML 代码的图片文件,若是浏览器使用 MIME 嗅探则会将其做为 HTML 代码执行,攻击者就能执行成功的 XSS 攻击了。

经过设置 header 为 nosniff 能够禁用这种 MIME 嗅探。

X-XSS-Protection

此 header 能在用户浏览器中开启基本的 XSS 防护。它不能避免一切 XSS 攻击,但它能够防范基本的 XSS。例如,若是浏览器检测到查询字符串中包含相似 <script> 标签之类的内容,则会阻止这种疑似 XSS 攻击代码的执行。这个 header 能够设置三种不一样的值:011; mode=block。若是你想了解更多关于如何选择模式的知识,请查看 X-XSS-Protection 及其潜在危害 一文。

升级你的 helmet

以上只是 helmet 提供的默认设置。除此以外,它还可让你设置 Expect-CTPublic-Key-PinsCache-ControlReferrer-Policy 之类的 header。你能够在 helmet 文档 中查找更多相关配置。

保护你的网页免受非预期内容的侵害

giphy.gif

跨站脚本执行对于 web 应用来讲是没法根绝的威胁。若是攻击者能够在你的应用中注入并运行代码,其后果对于你和你的用户来讲多是一场噩梦。有一种能试图阻止在你网页中运行非预期代码的方案:CSP(内容安全策略)。

CSP 容许你设定一组规则,以定义你的页面可以加载资源的来源。任何违反规则的资源都会被浏览器自动阻止。

你能够经过修改 Content-Security-Policy HTTP header 来指定规则,或者你不能改 header 时也可使用 meta 标签来设定。

这个 header 相似于这样:

Content-Security-Policy: default-src 'none';
    script-src 'nonce-XQY ZwBUm/WV9iQ3PwARLw==';
    style-src 'nonce-XQY ZwBUm/WV9iQ3PwARLw==';
    img-src 'self';
    font-src 'nonce-XQY ZwBUm/WV9iQ3PwARLw==' fonts.gstatic.com;
    object-src 'none';
    block-all-mixed-content;
    frame-ancestors 'none';
复制代码

在这个例子中,你能够看到咱们只容许从本身的域名或者 Google Fonts 的 fonts.gstatic.com 来获取字体;只容许加载本域名下的图片;只容许加载不指定来源,但必须包含指定 nonce 值的脚本及样式文件。这个 nonce 值须要用下面这样的方式指定:

<script src="myscript.js" nonce="XQY ZwBUm/WV9iQ3PwARLw=="></script>
<link rel="stylesheet" href="mystyles.css" nonce="XQY ZwBUm/WV9iQ3PwARLw==" />
复制代码

当浏览器收到 HTML 时,为了安全起见它会清除全部的 nonce 值,其它的脚本没法获得这个值,也就没法添加进网页中了。

你还能够禁止全部在 HTTPS 页面中包含的 HTTP 混合内容和全部 <object /> 元素,以及经过设置 default-srcnone 来禁用一切不为图片、样式表以及脚本的内容。此外,你还能够经过 frame-ancestors 来禁用 iframe。

你能够本身手动去编写这些 header,不过走运的是 Express 中已经有了许多现成的 CSP 解决方案。helmet 支持 CSP,但 nonce 须要你本身去生成。我我的为此使用了一个名为 express-csp-header 的模块。

安装及运行 express-csp-header

npm install express-csp-header --save
复制代码

为你的 index.js 添加并修改如下内容,启用 CSP:

const express = require('express');
const helmet = require('helmet');
const csp = require('express-csp-header');

const PORT = process.env.PORT || 3000;
const app = express();

const cspMiddleware = csp({
  policies: {
    'default-src': [csp.NONE],
    'script-src': [csp.NONCE],
    'style-src': [csp.NONCE],
    'img-src': [csp.SELF],
    'font-src': [csp.NONCE, 'fonts.gstatic.com'],
    'object-src': [csp.NONE],
    'block-all-mixed-content': true,
    'frame-ancestors': [csp.NONE]
  }
});

app.use(helmet());
app.use(cspMiddleware);

app.get('/', (req, res) => {
  res.send(`
    <h1>Hello World</h1>
    <style nonce=${req.nonce}>
      .blue { background: cornflowerblue; color: white; }
    </style>
    <p class="blue">This should have a blue background because of the loaded styles</p>
    <style>
      .red { background: maroon; color: white; }
    </style>
    <p class="red">This should not have a red background, the styles are not loaded because of the missing nonce.</p>
  `);
});

app.listen(PORT, () => {
  console.log(`Listening on http://localhost:${PORT}`);
});
复制代码

重启服务,访问 http://localhost:3000,能够看到一个带有蓝色背景的段落,由于相关的样式成功被加载了。而另外一个段落没有样式,由于其样式缺乏了 nonce 值。

csp-output.png

CSP header 还能够设定报告违规的 URL,你还能够在严格启用 CSP 以前仅开启报告模式,收集相关数据。

你能够在 MDN CSP 介绍查看更多信息,并浏览 “Can I Use” 网站查看 CSP 兼容性。大多数主流浏览器都支持这项特性。

总结

giphy.gif

惋惜的是,在安全性方面不存在所谓的万能方案,新的漏洞层出不穷。可是,你能够很轻松地在你的 web 应用中设置这些 HTTP header,显著地提高你应用的安全性,何乐而不为呢?若是你想了解更多有关 HTTP header 提升安全性的最佳实践,请浏览 securityheaders.io

若是你想了解更多 web 安全方面的最佳实践,请访问 Open Web Applications Security Project(OWASP),它涵盖了普遍的主题及有用的资源。

若是你有任何问题,或有其它用于提高 Node.js web 应用的工具,请随时联系我:


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索