¥Production Best Practices: Security
¥Overview
术语 “production” 指的是软件生命周期中的一个阶段,此时应用或 API 通常可供其终端用户或消费者使用。相比之下,在 “development” 阶段,你仍在积极编写和测试代码,并且应用不对外开放。相应的系统环境分别称为生产环境和开发环境。
¥The term “production” refers to the stage in the software lifecycle when an application or API is generally available to its end-users or consumers. In contrast, in the “development” stage, you’re still actively writing and testing code, and the application is not open to external access. The corresponding system environments are known as production and development environments, respectively.
开发和生产环境的设置通常不同,要求也大相径庭。在开发中很好的东西在生产中可能是不可接受的。例如,在开发环境中,你可能需要详细记录错误以进行调试,而同样的行为在生产环境中可能会成为安全问题。在开发中,你无需担心可伸缩性、可靠性和性能,而这些问题在生产中变得至关重要。
¥Development and production environments are usually set up differently and have vastly different requirements. What’s fine in development may not be acceptable in production. For example, in a development environment you may want verbose logging of errors for debugging, while the same behavior can become a security concern in a production environment. And in development, you don’t need to worry about scalability, reliability, and performance, while those concerns become critical in production.
注意: If you believe you have discovered a security vulnerability in Express, please see 安全政策和程序.
¥<div class="doc-box doc-info" markdown="1">注意: If you believe you have discovered a security vulnerability in Express, please see Security Policies and Procedures.
</div>
生产中 Express 应用的安全最佳实践包括:
¥Security best practices for Express applications in production include:
¥Don’t use deprecated or vulnerable versions of Express
不再维护 Express 2.x 和 3.x。这些版本中的安全和性能问题不会得到修复。不要使用它们!如果你尚未升级到版本 4,请按照 迁移指南 进行操作。
¥Express 2.x and 3.x are no longer maintained. Security and performance issues in these versions won’t be fixed. Do not use them! If you haven’t moved to version 4, follow the migration guide.
还要确保你没有使用 安全更新页面 上列出的任何易受攻击的 Express 版本。如果是,请更新到稳定版本之一,最好是最新版本。
¥Also ensure you are not using any of the vulnerable Express versions listed on the Security updates page. If you are, update to one of the stable releases, preferably the latest.
¥Use TLS
如果你的应用处理或传输敏感数据,请使用 传输层安全 (TLS) 来保护连接和数据。该技术在数据从客户端发送到服务器之前对其进行加密,从而防止一些常见(且容易)的黑客攻击。尽管 Ajax 和 POST 请求可能并不明显,并且在浏览器中看起来是 “hidden”,但它们的网络流量很容易受到 数据包嗅探 和 中间人攻击 的影响。
¥If your app deals with or transmits sensitive data, use Transport Layer Security (TLS) to secure the connection and the data. This technology encrypts data before it is sent from the client to the server, thus preventing some common (and easy) hacks. Although Ajax and POST requests might not be visibly obvious and seem “hidden” in browsers, their network traffic is vulnerable to packet sniffing and man-in-the-middle attacks.
你可能熟悉安全套接字层 (SSL) 加密。TLS 只是 SSL 的下一步。换句话说,如果你以前使用过 SSL,请考虑升级到 TLS。一般来说,我们推荐 Nginx 来处理 TLS。有关在 Nginx(和其他服务器)上配置 TLS 的良好参考,请参阅 推荐的服务器配置 (Mozilla Wiki)。
¥You may be familiar with Secure Socket Layer (SSL) encryption. TLS is simply the next progression of SSL. In other words, if you were using SSL before, consider upgrading to TLS. In general, we recommend Nginx to handle TLS. For a good reference to configure TLS on Nginx (and other servers), see Recommended Server Configurations (Mozilla Wiki).
此外,获得免费 TLS 证书的便捷工具是 让我们加密,它是 互联网安全研究组 (ISRG) 提供的免费、自动化和开放的证书颁发机构 (CA)。
¥Also, a handy tool to get a free TLS certificate is Let’s Encrypt, a free, automated, and open certificate authority (CA) provided by the Internet Security Research Group (ISRG).
¥Do not trust user input
对于 Web 应用,最关键的安全要求之一是正确的用户输入验证和处理。这有多种形式,我们不会在这里涵盖所有形式。最终,验证和正确处理应用接受的用户输入类型的责任。以下是专门使用一些 express
api 验证用户输入的一些示例。
¥For web applications, one of the most critical security requirements is proper user input validation and handling. This comes in many forms and we will not cover all of them here.
Ultimately, the responsibility for validating and correctly handling the types of user input your application accepts. Here are a few examples of validating user input specifically
using a few express
apis.
¥Prevent open redirects
潜在危险的用户输入的一个例子是开放重定向,其中应用接受 URL 作为用户输入(通常在 URL 查询中,例如 ?url=https://example.com
)并使用 res.redirect
设置 location
标头并返回 3xx 状态。
¥An example of potentially dangerous user input is an open redirect, where an application accepts a URL as user input (often in the URL query, for example ?url=https://example.com
) and uses res.redirect
to set the location
header and
return a 3xx status.
应用必须验证它是否支持重定向到传入的 URL,以避免将用户发送到恶意链接(例如钓鱼网站)等风险。
¥An application must validate that it supports redirecting to the incoming URL to avoid sending users to malicious links such as phishing websites, among other risks.
以下是使用 res.redirect
或 res.location
之前检查 URL 的示例:
¥Here is an example of checking URLs before using res.redirect
or res.location
:
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)
})
¥Use Helmet
Helmet 可以通过适当设置 HTTP 标头来帮助保护你的应用免受一些众所周知的 Web 漏洞的影响。
¥Helmet can help protect your app from some well-known web vulnerabilities by setting HTTP headers appropriately.
Helmet 是几个较小的中间件函数的集合,它们设置与安全相关的 HTTP 响应标头。一些例子包括:
¥Helmet is a collection of several smaller middleware functions that set security-related HTTP response headers. Some examples include:
helmet.contentSecurityPolicy
设置 Content-Security-Policy
标头。这有助于防止跨站点脚本攻击等等。
¥helmet.contentSecurityPolicy
which sets the Content-Security-Policy
header. This helps prevent cross-site scripting attacks among many other things.
helmet.hsts
设置 Strict-Transport-Security
标头。这有助于加强与服务器的安全 (HTTPS) 连接。
¥helmet.hsts
which sets the Strict-Transport-Security
header. This helps enforce secure (HTTPS) connections to the server.
helmet.frameguard
设置 X-Frame-Options
标头。这提供了 clickjacking 保护。
¥helmet.frameguard
which sets the X-Frame-Options
header. This provides clickjacking protection.
Helmet 还包含其他几个中间件函数,你可以阅读有关 在其文档网站上 的内容。
¥Helmet includes several other middleware functions which you can read about at its documentation website.
像任何其他模块一样安装 Helmet:
¥Install Helmet like any other module:
$ npm install --save helmet
然后在你的代码中使用它:
¥Then to use it in your code:
// ...
const helmet = require('helmet')
app.use(helmet())
// ...
¥Reduce fingerprinting
它可以帮助提供额外的安全层,以降低攻击者确定服务器使用的软件(称为 “指纹识别。”)的能力。虽然本身不是安全问题,但降低对应用进行指纹识别的能力可以改善其整体安全状况。服务器软件可以通过其响应特定请求的方式进行指纹识别,例如在 HTTP 响应标头中。
¥It can help to provide an extra layer of security to reduce the ability of attackers to determine the software that a server uses, known as “fingerprinting.” Though not a security issue itself, reducing the ability to fingerprint an application improves its overall security posture. Server software can be fingerprinted by quirks in how it responds to specific requests, for example in the HTTP response headers.
默认情况下,Express 会发送 X-Powered-By
响应标头,你可以使用 app.disable()
方法禁用该标头:
¥By default, Express sends the X-Powered-By
response header that you can
disable using the app.disable()
method:
app.disable('x-powered-by')
注意:
Disabling the X-Powered-By header
does not prevent a sophisticated attacker from determining that an app is running Express. It may discourage a casual exploit, but there are other ways to determine an app is running Express.
Express 还会发送自己的格式化 “404 未找到” 消息和格式化程序错误响应消息。这些可以由 添加你自己的未找到处理程序 和 编写自己的错误处理程序 更改:
¥Express also sends its own formatted “404 Not Found” messages and formatter error response messages. These can be changed by adding your own not found handler and writing your own error handler:
// 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!')
})
¥Use cookies securely
为确保 cookie 不会打开你的应用进行攻击,请不要使用默认会话 cookie 名称并适当设置 cookie 安全选项。
¥To ensure cookies don’t open your app to exploits, don’t use the default session cookie name and set cookie security options appropriately.
有两个主要的中间件 cookie 会话模块:
¥There are two main middleware cookie session modules:
express-session 替换了 Express 3.x 中内置的 express.session
中间件。
¥express-session that replaces express.session
middleware built-in to Express 3.x.
cookie-session 替换了 Express 3.x 中内置的 express.cookieSession
中间件。
¥cookie-session that replaces express.cookieSession
middleware built-in to Express 3.x.
这两个模块之间的主要区别在于它们如何保存 cookie 会话数据。express-session 中间件在服务器上存储会话数据;它只将会话 ID 保存在 cookie 本身中,而不是会话数据。默认情况下,它使用内存存储,并非为生产环境设计。在生产中,你需要设置一个可扩展的会话存储;见 兼容会话存储 名单。
¥The main difference between these two modules is how they save cookie session data. The express-session middleware stores session data on the server; it only saves the session ID in the cookie itself, not session data. By default, it uses in-memory storage and is not designed for a production environment. In production, you’ll need to set up a scalable session-store; see the list of compatible session stores.
相比之下,cookie-session 中间件实现了 cookie 支持的存储:它将整个会话序列化为 cookie,而不仅仅是会话键。仅当会话数据相对较小且易于编码为原始值(而不是对象)时才使用它。虽然浏览器应该支持每个 cookie 至少 4096 字节,但为确保不超过限制,每个域的大小不要超过 4093 字节。另外,请注意,cookie 数据将对客户端可见,因此如果有任何理由保持其安全或模糊,那么 express-session
可能是更好的选择。
¥In contrast, cookie-session middleware implements cookie-backed storage: it serializes the entire session to the cookie, rather than just a session key. Only use it when session data is relatively small and easily encoded as primitive values (rather than objects). Although browsers are supposed to support at least 4096 bytes per cookie, to ensure you don’t exceed the limit, don’t exceed a size of 4093 bytes per domain. Also, be aware that the cookie data will be visible to the client, so if there is any reason to keep it secure or obscure, then express-session
may be a better choice.
¥Don’t use the default session cookie name
使用默认会话 cookie 名称会使你的应用容易受到攻击。提出的安全问题类似于 X-Powered-By
:潜在的攻击者可以使用它来对服务器进行指纹识别并相应地进行攻击。
¥Using the default session cookie name can open your app to attacks. The security issue posed is similar to X-Powered-By
: a potential attacker can use it to fingerprint the server and target attacks accordingly.
为避免此问题,请使用通用的 cookie 名称;例如使用 express-session 中间件:
¥To avoid this problem, use generic cookie names; for example using express-session middleware:
const session = require('express-session')
app.set('trust proxy', 1) // trust first proxy
app.use(session({
secret: 's3Cur3',
name: 'sessionId'
}))
¥Set cookie security options
设置以下 cookie 选项以增强安全性:
¥Set the following cookie options to enhance security:
secure
- 确保浏览器仅通过 HTTPS 发送 cookie。
¥secure
- Ensures the browser only sends the cookie over HTTPS.
httpOnly
- 确保 cookie 仅通过 HTTP(S) 而不是客户端 JavaScript 发送,有助于防止跨站点脚本攻击。
¥httpOnly
- Ensures the cookie is sent only over HTTP(S), not client JavaScript, helping to protect against cross-site scripting attacks.
domain
- 表示 cookie 的域;使用它来与请求 URL 的服务器的域进行比较。如果它们匹配,则接下来检查路径属性。
¥domain
- indicates the domain of the cookie; use it to compare against the domain of the server in which the URL is being requested. If they match, then check the path attribute next.
path
- 表示 cookie 的路径;用它来比较请求路径。如果这个和域匹配,则在请求中发送 cookie。
¥path
- indicates the path of the cookie; use it to compare against the request path. If this and domain match, then send the cookie in the request.
expires
- 用于设置持久性 cookie 的到期日期。
¥expires
- use to set expiration date for persistent cookies.
下面是一个使用 cookie-session 中间件的例子:
¥Here is an example using cookie-session middleware:
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
}
}))
¥Prevent brute-force attacks against authorization
确保登录端点受到保护,以使私有数据更加安全。
¥Make sure login endpoints are protected to make private data more secure.
一种简单而强大的技术是使用两个指标来阻止授权尝试:
¥A simple and powerful technique is to block authorization attempts using two metrics:
同一用户名和 IP 地址连续尝试失败的次数。
¥The number of consecutive failed attempts by the same user name and IP address.
某个 IP 地址在一段较长时间内尝试失败的次数。例如,如果某 IP 地址在一天内尝试 100 次失败,则阻止该 IP 地址。
¥The number of failed attempts from an IP address over some long period of time. For example, block an IP address if it makes 100 failed attempts in one day.
rate-limiter-flexible 包提供了使这项技术简单快捷的工具。你可以找到 文档中的暴力保护示例
¥rate-limiter-flexible package provides tools to make this technique easy and fast. You can find an example of brute-force protection in the documentation
¥Ensure your dependencies are secure
使用 npm 管理应用的依赖非常强大且方便。但是你使用的软件包可能包含严重的安全漏洞,这些漏洞也会影响你的应用。你的应用的安全性仅与依赖中的 “最脆弱的链接” 一样强。
¥Using npm to manage your application’s dependencies is powerful and convenient. But the packages that you use may contain critical security vulnerabilities that could also affect your application. The security of your app is only as strong as the “weakest link” in your dependencies.
从 npm@6 开始,npm 会自动审查每个安装请求。此外,你可以使用 npm audit
来分析你的依赖树。
¥Since npm@6, npm automatically reviews every install request. Also, you can use npm audit
to analyze your dependency tree.
$ npm audit
如果你想保持更安全,请考虑 Snyk。
¥If you want to stay more secure, consider Snyk.
Snyk 提供 命令行工具 和 Github 整合,用于根据 Snyk 的开源漏洞数据库 检查你的应用是否存在依赖中的任何已知漏洞。按如下方式安装 CLI:
¥Snyk offers both a command-line tool and a Github integration that checks your application against Snyk’s open source vulnerability database for any known vulnerabilities in your dependencies. Install the CLI as follows:
$ npm install -g snyk
$ cd your-app
使用此命令测试你的应用是否存在漏洞:
¥Use this command to test your application for vulnerabilities:
$ snyk test
¥Avoid other known vulnerabilities
请留意可能影响 Express 或你的应用使用的其他模块的 Node 安全项目 或 Snyk 公告。总的来说,这些数据库是有关 Node 安全的知识和工具的极好资源。
¥Keep an eye out for Node Security Project or Snyk advisories that may affect Express or other modules that your app uses. In general, these databases are excellent resources for knowledge and tools about Node security.
最后,Express 应用(与任何其他 Web 应用一样)可能容易受到各种基于 Web 的攻击。熟悉已知的 网络漏洞 并采取预防措施避免它们。
¥Finally, Express apps—like any other web apps—can be vulnerable to a variety of web-based attacks. Familiarize yourself with known web vulnerabilities and take precautions to avoid them.
¥Additional considerations
以下是优秀的 Node.js 安全检查表 的一些进一步建议。有关这些建议的所有详细信息,请参阅该博客文章:
¥Here are some further recommendations from the excellent Node.js Security Checklist. Refer to that blog post for all the details on these recommendations:
始终过滤和清理用户输入以防止跨站点脚本 (XSS) 和命令注入攻击。
¥Always filter and sanitize user input to protect against cross-site scripting (XSS) and command injection attacks.
使用参数化查询或预编译的语句抵御 SQL 注入攻击。
¥Defend against SQL injection attacks by using parameterized queries or prepared statements.
使用开源 sqlmap 工具检测应用中的 SQL 注入漏洞。
¥Use the open-source sqlmap tool to detect SQL injection vulnerabilities in your app.
使用 nmap 和 sslyze 工具测试 SSL 密码、密钥和重新协商的配置以及证书的有效性。
¥Use the nmap and sslyze tools to test the configuration of your SSL ciphers, keys, and renegotiation as well as the validity of your certificate.
使用 safe-regex 确保你的正则表达式不易受到 正则表达式拒绝服务 攻击。
¥Use safe-regex to ensure your regular expressions are not susceptible to regular expression denial of service attacks.