生产最佳实践:安全
术语“生产(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.
Note
如果你认为自己发现了 Express 的安全漏洞,请参阅 安全政策与程序。
🌐 If you believe you have discovered a security vulnerability in Express, please see Security Policies and Procedures.
在生产环境中,Express 应用的安全最佳实践包括:
🌐 Security best practices for Express applications in production include:
不要使用已弃用或存在漏洞的 Express 版本
🌐 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 or consider Commercial Support Options.
还要确保你没有使用《安全更新页面》(/advanced/security-updates)上列出的任何易受攻击的 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.
使用 TLS
🌐 Use TLS
如果你的应用处理或传输敏感数据,请使用传输层安全(TLS)来保护连接和数据。这项技术会在数据从客户端发送到服务器之前进行加密,从而防止一些常见(且容易的)黑客攻击。虽然 Ajax 和 POST 请求在浏览器中可能不明显,看起来像是“隐藏”的,但它们的网络流量仍然容易受到数据包嗅探和中间人攻击的影响。
🌐 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的良好参考,请参见推荐的服务器配置(TLSRef)。
🌐 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 (TLSRef).
此外,一个获取免费 TLS 证书的方便工具是 Let’s Encrypt,这是由 Internet Security Research Group (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应用,最关键的安全要求之一是正确的用户输入验证和处理。这有多种形式,我们在这里不会涵盖所有内容。最终,验证和正确处理应用接受的用户输入类型的责任在于你。
🌐 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 is yours.
防止开放重定向
🌐 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);});使用Helmet
🌐 Use Helmet
Helmet 可以通过适当地设置 HTTP 头来帮助保护你的应用免受一些已知的网络漏洞侵害。
Helmet 是一个中间件函数,用于设置与安全相关的 HTTP 响应头。Helmet 默认设置以下响应头:
🌐 Helmet is a middleware function that sets security-related HTTP response headers. Helmet sets the following headers by default:
Content-Security-Policy:一个强大的允许列表,列出在你的页面上可以发生的内容,从而减轻许多攻击Cross-Origin-Opener-Policy:帮助将你的页面进行进程隔离Cross-Origin-Resource-Policy:阻止其他人跨域加载你的资源Origin-Agent-Cluster:将进程隔离更改为基于来源的Referrer-Policy:控制Referer头部Strict-Transport-Security:告诉浏览器优先使用 HTTPSX-Content-Type-Options:避免 MIME 嗅探X-DNS-Prefetch-Control:控制 DNS 预取X-Download-Options:强制保存下载(仅限 Internet Explorer)X-Frame-Options:用于减轻 Clickjacking 攻击的遗留头X-Permitted-Cross-Domain-Policies:控制 Adobe 产品(如 Acrobat)的跨域行为X-Powered-By:有关网络服务器的信息。已移除,因为可能被用于简单攻击X-XSS-Protection:遗留的头部尝试缓解 XSS 攻击,但反而使情况更糟,因此 Helmet 将其禁用
每个标题都可以配置或禁用。要了解更多信息,请访问 它的文档网站。
🌐 Each header can be configured or disabled. To read more about it please go to its documentation website.
像安装其他模块一样安装 Helmet:
🌐 Install Helmet like any other module:
npm install helmetyarn add helmetpnpm add helmetbun add helmet然后在你的代码中使用它:
🌐 Then to use it in your code:
// ...
const helmet = require('helmet');app.use(helmet());
// ...import helmet from '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');Note
禁用 X-Powered-By header 并不能阻止高级攻击者判断一个应用正在运行 Express。它可能会让随意的利用行为望而却步,但还有其他方法可以判断应用正在运行 Express。
🌐 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 404app.use((req, res, next) => { res.status(404).send("Sorry can't find that!");});
// custom error handlerapp.use((err, req, res, next) => { console.error(err.stack); res.status(500).send('Something broke!');});安全使用 Cookie
🌐 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中间件。 - cookie-session,用于替换内置于 Express 3.x 的
express.cookieSession中间件。
这两个模块的主要区别在于它们保存 cookie 会话数据的方式。express-session 中间件将会话数据存储在服务器上;它只在 cookie 本身中保存会话 ID,而不保存会话数据。默认情况下,它使用内存存储,并不适用于生产环境。在生产环境中,你需要设置一个可扩展的会话存储;请参见兼容的会话存储列表。
🌐 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.
不要使用默认的会话 Cookie 名称
🌐 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 proxyapp.use( session({ secret: 's3Cur3', name: 'sessionId', }));import session from 'express-session';
app.set('trust proxy', 1); // trust first proxyapp.use( session({ secret: 's3Cur3', name: 'sessionId', }));设置 Cookie 安全选项
🌐 Set cookie security options
设置以下 Cookie 选项以增强安全性:
🌐 Set the following cookie options to enhance security:
secure- 确保浏览器仅通过 HTTPS 发送 cookie。httpOnly- 确保 cookie 仅通过 HTTP(S) 发送,而不是通过客户端 JavaScript 发送,有助于防止跨站脚本攻击。domain- 指示 cookie 的域;使用它与请求 URL 的服务器域进行比较。如果它们匹配,则接下来检查路径属性。path- 指示 cookie 的路径;用它与请求路径进行比较。如果此路径和域匹配,则在请求中发送 cookie。expires- 用于设置持久性 Cookie 的过期日期。
这里是一个使用 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 hourapp.use( session({ name: 'session', keys: ['key1', 'key2'], cookie: { secure: true, httpOnly: true, domain: 'example.com', path: 'foo/bar', expires: expiryDate, }, }));import session from 'cookie-session';import express from 'express';
const app = express();
const expiryDate = new Date(Date.now() + 60 * 60 * 1000); // 1 hourapp.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地址连续失败尝试的次数。
- 来自某个 IP 地址在较长时间内的失败尝试次数。例如,如果一个 IP 地址在一天内失败尝试达到 100 次,则阻止该 IP 地址。
rate-limiter-flexible 包提供了使这一技术变得简单快捷的工具。你可以在文档中找到暴力破解保护的示例
确保你的依赖是安全的
🌐 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 或你的应用使用的其他模块的 GitHub Advisory Database 或 Snyk 公告。通常,这些数据库是关于 Node 安全的知识和工具的绝佳资源。
🌐 Keep an eye out for GitHub Advisory Database 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 的攻击。熟悉已知的 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)和命令注入攻击。
- 通过使用参数化查询或预处理语句来防御 SQL 注入攻击。
- 使用开源 sqlmap 工具检测你的应用中的 SQL 注入漏洞。
- 使用 nmap 和 sslyze 工具测试你的 SSL 加密套件、密钥和重新协商的配置,以及证书的有效性。
- 使用 safe-regex 来确保你的正则表达式不容易受到 正则表达式拒绝服务 攻击。