生产最佳实践:安全

概述

术语 “生产” 指的是软件生命周期中的一个阶段,此时应用程序或 API 通常可供其最终用户或消费者使用。相反,在 “开发” 阶段,您仍在积极编写和测试代码,并且应用程序不对外部开放。相应的系统环境分别称为 生产 环境和 开发 环境。

开发和生产环境通常设置不同,并且有截然不同的要求。在开发中没问题的东西,在生产中可能无法接受。例如,在开发环境中您可能希望详细记录错误以便调试,但相同的行为在生产环境中可能成为安全隐患。在开发中,您无需担心可伸缩性、可靠性和性能,而在生产中,这些问题变得至关重要。

注意

如果您认为您在 Express 中发现了一个安全漏洞,请参阅安全策略和程序

Express 应用程序在生产环境中的安全最佳实践包括:

不要使用已废弃或有漏洞的 Express 版本

Express 2.x 和 3.x 不再维护。这些版本中的安全和性能问题将不会得到修复。请勿使用它们!如果您尚未迁移到版本 4,请遵循迁移指南或考虑商业支持选项

另外,请确保您没有使用安全更新页面上列出的任何易受攻击的 Express 版本。如果正在使用,请更新到稳定的版本,最好是最新版本。

使用 TLS

如果您的应用程序处理或传输敏感数据,请使用 传输层安全 (TLS) 来保护连接和数据。此技术在数据从客户端发送到服务器之前对其进行加密,从而防止一些常见(且容易)的黑客攻击。尽管 Ajax 和 POST 请求在浏览器中可能不明显且看起来“隐藏”,但它们的网络流量容易受到数据包嗅探中间人攻击

您可能熟悉安全套接字层 (SSL) 加密。TLS 只是 SSL 的下一个演进。换句话说,如果您以前使用 SSL,请考虑升级到 TLS。通常,我们建议使用 Nginx 来处理 TLS。有关在 Nginx(和其他服务器)上配置 TLS 的良好参考,请参阅推荐的服务器配置 (Mozilla Wiki)

此外,一个获取免费 TLS 证书的便捷工具是 Let’s Encrypt,这是一个由 互联网安全研究组 (ISRG) 提供的免费、自动化和开放的证书颁发机构 (CA)。

不信任用户输入

对于 Web 应用程序,最关键的安全要求之一是正确的用户输入验证和处理。这有多种形式,我们不会在此处涵盖所有形式。最终,验证和正确处理应用程序接受的用户输入类型的责任在于您。

防止开放重定向

一个潜在危险的用户输入示例是 开放重定向,即应用程序接受 URL 作为用户输入(通常在 URL 查询中,例如 ?url=https://example.com),并使用 res.redirect 设置 location 头部并返回 3xx 状态。

应用程序必须验证它是否支持重定向到传入 URL,以避免将用户发送到恶意链接(例如网络钓鱼网站)以及其他风险。

以下是在使用 res.redirectres.location 之前检查 URL 的示例

app.use((req, res) => {
  try {
    if (new Url(req.query.url).host !== 'example.com') {
      return res.status(400).end(`Unsupported redirect to host: ${req.query.url}`)
    }
  } catch (e) {
    return res.status(400).end(`Invalid url: ${req.query.url}`)
  }
  res.redirect(req.query.url)
})

使用 Helmet

Helmet 可以通过适当设置 HTTP 头部来帮助保护您的应用程序免受一些众所周知的 Web 漏洞攻击。

Helmet 是一个中间件函数,用于设置与安全相关的 HTTP 响应头。Helmet 默认设置以下头部:

每个头部都可以配置或禁用。要了解更多信息,请访问其文档网站

像安装其他模块一样安装 Helmet:

$ npm install helmet

然后在代码中使用它:

// ...

const helmet = require('helmet')
app.use(helmet())

// ...

减少指纹识别

它有助于提供额外的安全层,以减少攻击者确定服务器所用软件的能力,这被称为“指纹识别”。虽然指纹识别本身不是一个安全问题,但减少应用程序被指纹识别的能力可以提高其整体安全态势。服务器软件可以通过其对特定请求的响应方式(例如 HTTP 响应头)中的怪癖来被指纹识别。

默认情况下,Express 会发送 X-Powered-By 响应头,您可以使用 app.disable() 方法禁用它:

app.disable('x-powered-by')

注意

禁用 X-Powered-By 头部并不能阻止经验丰富的攻击者确定应用程序正在运行 Express。它可能会阻止一般的利用,但仍有其他方法可以确定应用程序正在运行 Express。

Express 还会发送其自己格式化的“404 Not Found”消息和格式化错误响应消息。这些可以通过添加您自己的未找到处理程序编写您自己的错误处理程序来更改。

// last app.use calls right before app.listen():

// custom 404
app.use((req, res, next) => {
  res.status(404).send("Sorry can't find that!")
})

// custom error handler
app.use((err, req, res, next) => {
  console.error(err.stack)
  res.status(500).send('Something broke!')
})

安全地使用 Cookie

为确保 Cookie 不会使您的应用程序容易受到攻击,请不要使用默认的会话 Cookie 名称,并适当设置 Cookie 安全选项。

主要有两种中间件 Cookie 会话模块:

这两个模块之间的主要区别在于它们保存 Cookie 会话数据的方式。express-session 中间件将会话数据存储在服务器上;它只在 Cookie 本身中保存会话 ID,而不是会话数据。默认情况下,它使用内存存储,不适合生产环境。在生产环境中,您需要设置一个可扩展的会话存储;请参阅兼容会话存储列表。

相反,cookie-session 中间件实现了 Cookie 支持的存储:它将整个会话序列化到 Cookie 中,而不仅仅是一个会话键。仅当会话数据相对较小且易于编码为原始值(而不是对象)时才使用它。尽管浏览器应该支持每个 Cookie 至少 4096 字节,但为确保您不超过限制,请不要超过每个域 4093 字节的大小。另外,请注意 Cookie 数据对客户端是可见的,因此如果有任何理由保持其安全或模糊,那么 express-session 可能是更好的选择。

使用默认的会话 Cookie 名称可能会使您的应用程序面临攻击。由此产生的安全问题与 X-Powered-By 类似:潜在攻击者可以使用它来对服务器进行指纹识别并相应地发起攻击。

为避免此问题,请使用通用 Cookie 名称;例如,使用 express-session 中间件:

const session = require('express-session')
app.set('trust proxy', 1) // trust first proxy
app.use(session({
  secret: 's3Cur3',
  name: 'sessionId'
}))

设置以下 Cookie 选项以增强安全性:

以下是使用 cookie-session 中间件的示例:

const session = require('cookie-session')
const express = require('express')
const app = express()

const expiryDate = new Date(Date.now() + 60 * 60 * 1000) // 1 hour
app.use(session({
  name: 'session',
  keys: ['key1', 'key2'],
  cookie: {
    secure: true,
    httpOnly: true,
    domain: 'example.com',
    path: 'foo/bar',
    expires: expiryDate
  }
}))

防止针对授权的暴力破解攻击

确保登录端点受到保护,以提高私人数据的安全性。

一个简单而强大的技术是使用两个指标来阻止授权尝试:

  1. 同一用户名和 IP 地址连续失败尝试的次数。
  2. 在较长时间内,来自某个 IP 地址的失败尝试次数。例如,如果一个 IP 地址一天内有 100 次失败尝试,则将其阻止。

rate-limiter-flexible 包提供了使此技术简单快速的工具。您可以在文档中找到暴力破解保护的示例

确保您的依赖项是安全的

使用 npm 管理应用程序的依赖项功能强大且方便。但您使用的包可能包含可能影响您应用程序的关键安全漏洞。您的应用程序的安全性仅取决于您依赖项中的“最薄弱环节”。

自 npm@6 起,npm 自动审核每个安装请求。此外,您可以使用 npm audit 来分析您的依赖树。

$ npm audit

如果您想保持更安全,请考虑使用 Snyk

Snyk 提供命令行工具Github 集成,可根据Snyk 的开源漏洞数据库检查您的应用程序中是否存在任何已知的依赖项漏洞。按如下方式安装 CLI:

$ npm install -g snyk
$ cd your-app

使用此命令测试应用程序的漏洞:

$ snyk test

避免其他已知漏洞

请留意可能影响 Express 或您的应用程序使用的其他模块的 Node Security ProjectSnyk 安全公告。总的来说,这些数据库是关于 Node 安全知识和工具的优秀资源。

最后,Express 应用程序——像任何其他 Web 应用程序一样——可能容易受到各种基于 Web 的攻击。熟悉已知的Web 漏洞并采取预防措施避免它们。

额外考量

以下是来自出色的Node.js 安全清单的一些进一步建议。有关这些建议的所有详细信息,请参阅该博客文章:

编辑此页面