把Express用于生产的最佳实践 - 第一篇:安全

这两篇博客是关于将Express用于生产环境的最佳实践。第一篇主要讲安全,第二篇是效率和可靠性。这里假设你已经熟悉了Node.js和网络开发的基本内容,以及生产环境的一些重要概念。

概述

术语“生产”是指在软件生命周期内,一个应用或者API可以让终端用户或者说消费者使用的场景。相对开发阶段,仍在编写和测试代码,应用是不对外开放的。对应的系统环境分别称为生产环境开发环境

通常开发环境和生产环境在设置上有很大差异,且需求也明显不同。经常会出现在开发中OK的事项,无法适用于生产。举个例子,在开发环境中用来排错的大量日志信息,可能会在生产环境中成为一个安全隐患。还有就是在开发环境中,不需要担心稳定性、可靠性及效率。

这篇博客就来讲讲,对于采用Express的应用上线后,一些安全方面的最佳实践。

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

Express 2.x和3.x已停止维护,不会再修正安全和效率上的问题。不要使用它们!如果还未更新到版本4,可以根据 这篇引导来升级程序。

同时查看一下 安全升级页面,确保没使用有漏洞的版本。如果正在用,先更新到最近的稳定版。

使用TLS

如果需要传输敏感数据,请使用 Transport Layer Security(TLS)保护连接和数据。这个技术会在客户端向服务器发送数据之前先对其加密,因而防御一些常见的(容易的)攻击。虽然像Ajax以POST发送请求会看不到数据,像是被“隐藏”在浏览器中,但脆弱的网络传输很容易被 抓包或者被 中间人攻击

如果你知道Secure Socket Layer(SSL)加密,那么 TLS就是下一代的SSL。换句话说,如果之前你在用SSL,可以考虑升级成TSL。一般来说,推荐使用Nginx处理TLS。这里有一篇不错的文章讲如何在Nginx(或其它服务器)上配置TLS, 推荐服务器配置(Mozilla Wiki).

还有 Let’s Encrypt用来获得TLS证书,由 网络安全研究组织(ISRG)提供:免费、自动的、开放认证授权(CA)。

使用Helmet

Helmet会帮你抵御一些熟知的,通过设置HTTP头 发起的网络攻击。

Helmet实质上是由9个中间价组成的集合,用来设置安全相关的HTTP头:

  • csp设置Content-Security-Policy,抵御跨站脚本攻击、跨站注入。

  • hidePoweredBy删除X-Powered-By

  • hpkp增加 Public Key Pinning,用来防御利用伪造证书的中间人攻击。

  • hsts设置Strict-Transport-Security,强制(HTTP通过SSl/TLS)安全连接服务器。

  • ieNoOpen针对IE8+设置X-Download-Options

  • noCache设置Cache-ControlPragma,禁止客户端缓存。

  • noSniff设置X-Content-Type-Options to prevent browsers from MIME-sniffing a response away from the declared content-type.

  • frameguard设置X-Frame-Options提供 clickjacking保护。

  • xssFilter设置X-XSS-Protection激活跨站脚本(XSS)过滤。

安装Helmet模块:

npm install --save helmet

然后就像普通中间价一样使用:

...
var helmet = require('helmet');
app.use(helmet());
...

至少屏蔽X-Powered-By

如果不想用Helmet,至少要屏蔽X-Powered-By。攻击者会利用这个头(默认开启)检测是否是Express的应用,然后针对攻击。

所以,最佳实践就是调用app.disable()方法关闭它:

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

如果用了Helmet,它会帮你做这件事。

使用安全cookies

为确保cookies不会出问题,不要使用默认的cookie名称,同时设置好相应的cookie安全选项。

主要有两个中间件模块:

其主要差别是如何保存cookit session数据。express-session把session数据存储在服务器上,cookie里只保存了session键。默认情况下使用内存存储,可见不是针对生产环境设计的。对于生产,需要一个可扩展的session存储,请查看 session存储兼容列表

对应的,cookie-session实现了cookie端存储:把整个session序列化到cookie,而不是只存一个session键。这种只有在session数据很小并容易编码(基本值非对象)的情况下才适用。虽然浏览器对每份cookie提供了4096字节大小的支持,但为了保证不超出限制,最好不要超过4093字节。同样,考虑到客户端可以看到cookie数据,没任何安全可言,最好还是选择express-session。

使用默认的session cookie名称等于把应用开放给攻击者。所导致的安全问题和X-Powered-By类似:攻击者会针对攻击。

为此,可以采用一个没意义的cookie名称,以express-session举例:

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

如下设置cookie选项,可以增加安全性:

  • secure – 保证浏览器只能通过HTTPS发送cookie。

  • httpOnly – 保证浏览器不用客户端JavaScript发送cookie,只能通过HTTP(S)发送,这样可以不受跨站脚本攻击。

  • domain – 指定域名。先判断指定的域名和请求服务器的域名是否匹配,匹配后会进行下一个属性path的匹配。

  • path – 指定路径。路径也匹配后,发送cookie。

  • expires – 设置数据的过期时间。

这里有一段使用cookie-session例子:

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

确保依赖库是安全的

使用npm可以有效、方便的管理应用程序,但有些包可能包含严重的安全问题。整个应用的安全程度就会像“木桶效应”那样。

幸运的是,有两个工具可以保证第三方包的安全性: nsprequireSafe。这两个工具功能上几乎一样,都用可能会显得没必要,不过对于安全来说“宁愿多做,不要犯错”。

nsp是一个命令行工具,它会检测 Node安全项目的漏洞数据库来判定应用程序是否使用了有问题的包。安装如下:

npm i nsp -g

然后使用命令提交项目的npm-shrinkwrap.jsonpackage.jsonnodesecurity.io进行验证。

cd your-app
nsp check

也可以用requireSafe审查模块:

npm install -g requiresafe
cd your-app
requiresafe check

额外的考虑

这里有一份相当不错的 Node.js安全检测规范。这里列一部分出来:

  • 实现rate-limiting,预防暴力的认证攻击。安利一个方法 StrongLoop API Gateway,或者使用比如 express-limiter的中间件,不过这样可能需要改代码。

  • 使用 csurf中间价,防御跨站伪造请求(CSRF)。

  • 始终过滤用户输入,保护跨站脚本(XSS)和命令行注入空间。

  • 使用参数化请求或者预设SQL语句,防御SQL注入攻击。

  • 使用开源 sqlmap工具,检测SQL注入漏洞。

  • 使用工具 nmapsslyze测试SSL配置,ciphers, keys, and renegotiation 以及证书是否有效。

  • 使用 safe-regex确保正则表达式不会受到 正则表达式拒绝服务的攻击。

避免其它已知的漏洞

时刻关注 Node安全项目顾问,这是一个非常棒的关于Node安全方面的项目。

最后,像其它网络应用一样,Express应用也会遭到各种漏洞攻击。尽量熟悉 网络漏洞做好提前预防。

你可能还会对这些感兴趣…​