精华 Express 部署最佳实践-安全篇
发布于 8 年前 作者 zhangmingkai4315 16519 次浏览 来自 分享

引言

本篇章翻译自Express官方网站,主要是讨论关于如何部署一套安全的Express Web App,虽然内容不是太多,但是比较通用,对于安全不是特别了解的可以参考这篇文章做个入门,翻译过来供大家讨论,对于其中一些原文内容介绍不足的地方,做了一定的介绍补充。

对于开发一套Web App,我们一般会定义很多个版本,比如最终的生产线版本(production),开发版本(development)等。对于生产线版本,一般是最终面对用户的版本,而开发版本则一般只对开发人员开放。不同版本的运行环境,也分别称之为生产环境和开发环境。这两种运行环境一般也在分离的环境中。

在开发软件期间,为了调试的目的我们会设置很多调试信息的输出,然后这些调试信息由于一些安全方面的原因,必须在生产线版本中关闭,并且我们的最终的生产版本对于性能,可靠性和可安全性的要求也要比开发版本更加的苛刻。

这篇文章主要是讨论如何部署一套安全的Express App,所以更多的是倾向于生产线版本的安全部署原则。

选择安全稳定的运行版本

Express 2.x和3.x官方已宣布不再继续维护,所以这些版本中的安全和性能上的问题不会再被修复,如果尚在使用,可以参考官方的迁移手册将其升级到Exress4.x。

另外官方维护了一个安全的升级日志,里面包含了不同版本的一些安全和性能问题。如果你的运行版本属于其中,请考虑升级到安全稳定的版本运行。以下是其中的几个版本的更新,我们可以了解一下都涉及了哪些安全相关的修复补丁。

	4.11.1
	修复了root路径暴露的风险(express.static, res.sendfile, and res.sendFile)
	4.10.7
	修复了重定向脆弱性(express.static (advisory, CVE-2015-1164)).
	4.8.8
	修改目录浏览脆弱性(express.static (advisory , CVE-2014-6394)).
	4.8.4
	Node.js 0.10 可能会导致fds的泄露进而影响express.static 和 res.sendfile的执行,特意构造的请求可能导致服务器EMFILE错误以及服务器无法响应。
	4.8.0
	1. 查询字段中如果存在闲置的大容量数组,可能会导致进程耗尽内存,并使服务器宕机。 2. 查询字段如果包含非常多层级的对象,可能会导致进程被阻塞,服务器无法响应。

使用TLS加密数据的传输

如果传输的信息的敏感性较高,比如用户的个人信息或者安全秘钥信息的传递,请考虑使用TLS协议来加密数据传输。另外普通的Ajax请求和Post请求在不加密的情况下很容易被包嗅探工具扫描到,并且可能遭受中间人攻击等风险。

可能很多人对于传统的SSL加密比较熟悉,而TLS就是SSL的下一代安全加密产品。对于web app 我们可以使用Nginx来配置TLS加密,具体的操作方法可参考如下的配置文件,该配置文件适用于几乎所有的现代浏览器(老式浏览器可能会对于一些算法的支持度不好):

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    # certs sent to the client in SERVER HELLO are concatenated in ssl_certificate
    ssl_certificate /path/to/signed_cert_plus_intermediates;
    ssl_certificate_key /path/to/private_key;
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;
    ssl_session_tickets off;

    # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
    ssl_dhparam /path/to/dhparam.pem;

    # modern configuration. tweak to your needs.
    ssl_protocols TLSv1.2;
    ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
    ssl_prefer_server_ciphers on;

    # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
    add_header Strict-Transport-Security max-age=15768000;

    # OCSP Stapling ---
    # fetch OCSP records from URL in ssl_certificate and cache them
    ssl_stapling on;
    ssl_stapling_verify on;

    ## verify chain of trust of OCSP response using Root CA and Intermediate certs
    ssl_trusted_certificate /path/to/root_CA_cert_plus_intermediates;

    resolver <IP DNS resolver>;

    ....
}

另外网上存在一些免费的TLS加密方式Let’s Encrypt,他可以提供一套免费的CA认证系统供TLS安全传输认证使用。

使用Helmet

Helmet可以帮助你的app抵御一些比较常见的安全web安全隐患,它其实是将9个安全中间件集中到了一起,做了合并,大部分都是对于http header的操作,下面我们就来看一下这几个模块:

  1. csp: 通过设置Content-Security-Policy来阻止XSS攻击和一些其他的跨站注入风险。
  2. hidePoweredBy: 删除了header中的X-Powered-By标签
  3. hpkp:通过增加Public key Pinning来阻止伪造证书导致的中间人攻击。
  4. hsts: 设置 Strict-Transport-Security 来强制安全连接(http over SSL)到服务器(服务器需要支持https)
  5. ieNoOpen:为ie8设置 X-Download-Options
  6. noCache:设置Cache-Control关闭客户端缓存。
  7. noSniff: 设置X-Content-Type-Options阻止浏览器MIME-sniffing。
  8. frameguard:设置X-Frame-Options阻止点击劫持风险
  9. xssFilter: 设置X-XSS-Protection启用XSS过滤器

对于使用Helmet 非常简单我们只需要通过npm来安装,在express应用中直接使用即可:

# 安装
npm install --save helmet

# app代码中引入
var helmet = require('helmet');
app.use(helmet());

如果对于上述的几个模块所涉及的安全风险不熟悉,至少我们可以从最简单的关闭X-Powered-By来尝试一下,默认情况下express会将该header设置为express,攻击者可以根据此来设定针对该框架的安全攻击方式。关闭的方式:

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

安全的使用会话Cookie

对于会话cookie的操作,首先不要使用缺省的会话cookie名称,另外要确保设定安全的cookie设置。对于cookie的使用我们一般有两个模块可以操作:

  1. express-session
  2. cookie-sesion

两个的主要区别在于如何保存会话的cookie数据,express-session会将数据保存在服务器端,而且仅仅会在cookie中保存会话的id,默认使用内存保存,但是我们可以在生产环境中使用任意兼容的会话存储列表中方式存储会话内容。

对于cookie-session中间件则会序列化整个的会话信息到cookie中,对于浏览器一般会有cookie的大小限制,所以使用它的时候要注意不要存储的数据太大,超过了限制。而且对于客户端来说cookie都是可见的,所以机密的信息一定要注意保存安全。因此使用express-session或许能提供更好的会话cookie的存储.

不要使用缺省的会话名称:

在使用的时候,一定要注意不要使用缺省的名称,这里我们可以通过session接口来修改默认的会话名称和秘钥。

var session = require('express-session');
app.set('trust proxy', 1) // trust first proxy
app.use( session({
   secret : 's3Cur3',
   name : 'sessionId',
  })
);
安全的设定cookie选项

对于cookie的设置包含下面几个参数:

  1. secure - 浏览器仅仅通过HTTPS来传递cookie.
  2. httpOnly - 仅仅通过http或者https来传递而不是通过客户端的脚本,可以有效的阻止XSS攻击
  3. domain - 设定cookie可被发送的域名名称。
  4. path - 设定cookie可被发送的路径,只有域名和路径都正确的情况下才发送该cookie
  5. expires - 设定cookie到期的时间。

下面是一个使用cookie-session来设定cookie选项的例子:

var session = require('cookie-session');
var express = require('express');
var app = express();

var 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
          }
  })
);

确保依赖库的安全性

借助于社区提供的大量的依赖库,我们可以更加聚焦在自己的业务代码的编码上。对于依赖库我们一般通过npm来管理,非常方便,但是引入的包是否会给我们的应用引入新的安全风险呢?我们可能听说过的短板效应,描述的就是这种情况,当我们苦心积虑的去考虑了任何安全的风险后写出自己的业务代码。但是却忽视了引入的库的安全性,则之前所作出的努力可能都白费了。

我们可以使用如下的两个工具来管理第三方的依赖库的安全问题。 nsprequireSafe,两个工具功能上大致相同。

nsp是一个命令行工具,通过一个node安全项目 数据库来决定我们的应用所依赖的包是否具有安全风险

//nsp会提交package.json中的内容进行安全检查

$ npm i nsp -g
$ nsp check

// requiresafe的使用基本相似

$ npm install -g requiresafe
$ cd your-app
$ requiresafe check

额外需要考虑的问题

  1. 使用提交速率限制,防止蛮力攻击,我们可以使用StrongLoop API Gateway,或者使用express-limiter来限制用户的提交和查询的速度。
  2. 使用csurf来阻止CSRF攻击
  3. 永远不要相信用户的输入,客户端和服务器端的双校验
  4. 过滤用户输入,防止XSS或者命令注入攻击
  5. sqlmap可以检测是否存在sql注入的风险。
  6. nmap 和 sslyze可以用来检测配置的ssl是否正确,以及验证认证的有效性。
  7. safe-regex可以确保你的正则表达式不会成为正则表达式DOS攻击的受害者

避免已知的漏洞攻击

经常查看 Node Security Project 上的建议,确保你的app能够实时的更新安全漏洞,防范已知的攻击的发生。

21 回复

这个是很好的,可以精华

赞,学习了

mark

来自酷炫的 CNodeMD

收藏了,很多有用的包

赞,学习了

马克

来自酷炫的 CNodeMD

很有用谢谢

来自酷炫的 CNodeMD

nice, 很有用

用nsp检查出了问题,但是是最新版本的库的依赖库的依赖库出来问题该怎么办 做了一个项目有nsp工具检查了一下依赖库的安全性。 这是其中一个结果: ┌────────────┬────────────────────────────────────────────────────────────────────┐ │ │ Regular Expression Denial of Service │ ├────────────┼────────────────────────────────────────────────────────────────────┤ │ Name │ minimatch │ ├────────────┼────────────────────────────────────────────────────────────────────┤ │ CVSS │ 7.5 (High) │ ├────────────┼────────────────────────────────────────────────────────────────────┤ │ Installed │ 2.0.10 │ ├────────────┼────────────────────────────────────────────────────────────────────┤ │ Vulnerable │ <=3.0.1 │ ├────────────┼────────────────────────────────────────────────────────────────────┤ │ Patched │ >=3.0.2 │ ├────────────┼────────────────────────────────────────────────────────────────────┤ │ Path │ kails@0.1.0 > gulp@3.9.1 > vinyl-fs@0.3.14 > glob-stream@3.1.18 > │ │ │ minimatch@2.0.10 │ ├────────────┼────────────────────────────────────────────────────────────────────┤ │ More Info │ https://nodesecurity.io/advisories/118 │ └────────────┴────────────────────────────────────────────────────────────────────┘

minimatch@2.0.10 版本有问题,但是它是 gulp@3.9.1(最新)的依赖库的依赖的依赖。完全没法改呀,请问什么好的建议可以解决这个问题。

回到顶部