Node.js 安全清单

JacksonAntony 发布于1年前
0 条问题

by Gergely Nemeth ( @nthgergo ), Co-founder of RisingStack

安全——是不容忽视的。每个人都同意它非常重要,真正严肃对待它的却没有多少。我们 RisingStack 希望你能认真对待这一问题——这就是为什么我们整理这份清单来帮助你,在你的应用被成千上万用户使用前必须要做的安全检查。

这份清单大部分内容都是通用的,不仅适用于Node.js,同样适用于其他语言和框架,只是一些明确给出了在Node.js中使用的方法。同时推荐你去阅读我们的引导文章 Node.js security ,如果你刚开始使用Node.js,推荐你看这篇文章 first chapter of Node Hero

配置管理

HTTP 安全头部

有些关于安全的HTTP头部是你的网站必须要有的:

  • Strict-Transport-Security强制将HTTP请求变为HTTPS请求

  • X-Frame-Options防止 点击劫持

  • X-XSS-Protection开启跨站脚本攻击(XSS)的过滤,大多数现代浏览器支持这个设置

  • X-Content-Type-Options禁用浏览器对响应内容MIME类型的嗅探,严格使用响应的Content-Type的值

  • Content-Security-Policy能有效防止多种攻击,包括跨站脚本和跨站注入

Node.js开发者可以使用 Helmet 模块置这些头部,代码如下:

var express = require('express');
var helmet = require('helmet');

var app = express();

app.use(helmet());

KoaThinkJS 中可以使用 koa-helmet 来设置这些头部,当然有关安全的头部不止这些,更多请看 Helmet 和 MDN HTTP Headers

当然,在大多数架构里这些头部可以设置在web服务器的配置中(Apache、Nginx),不需要对应用代码进行改动。在Nginx中的配置:

# nginx.conf

add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Content-Security-Policy "default-src 'self'";

有一个完整的Nginx配置文件, 帅气的传送门 在此。

如果你想快速检查你的网站是否有了所有的必须头部,请使用这个 在线检查器

客户端的敏感数据

当发布前段应用时,确保你的代码里永远不会包含API密码和证书,因为它可以被任何人看到。

没有什么自动化的方法去检查,但是有两个方法可以降低向客户端暴露敏感数据的风险:

  • 使用 pull requests 提交代码

  • 定期 code review

认证

暴力保护

穷举法是系统地枚举所有可能的候选者的一种解决方案,并检查每个候选是否满足问题的陈述。在Web应用程序中,登录端点可能是这方面的最佳候选者。

为了保护你的应用面免受这些攻击,你必须实现某种限速策略。在Node.js中你可以使用 ratelimiter 模块。

var email = req.body.email;
var limit = new Limiter({ id: email, db: db });

limit.get(function(err, limit) {

});

当然,你可以将它封装成一个中间件,然后在各个应用中使用它。Express和Koa都有这样的中间件,代码如下:

var ratelimit = require('koa-ratelimit');
var redis = require('redis');
var koa = require('koa');
var app = koa();

var emailBasedRatelimit = ratelimit({
  db: redis.createClient(),
  duration: 60000,
  max: 10,
  id: function (context) {
    return context.body.email;
  }
});

var ipBasedRatelimit = ratelimit({
  db: redis.createClient(),
  duration: 60000,
  max: 10,
  id: function (context) {
    return context.ip;
  }
});

app.post('/login', ipBasedRatelimit, emailBasedRatelimit, handleLogin);

我们在这段代码中限制了一个用户在给定时间窗口内可以尝试登录的次数,通过这样的方式我们可以降低暴力攻击的风险。 注意:这些配置需要根据应用进行调整,千万不要复制粘贴。

想要测试你的服务在这种情况下的表现,你可以使用这个工具 hydra

Session 管理

安全使用cookie的重要性千万不能忘:尤其是动态web应用,需要在无状态的HTTP协议中维护状态。

Cookie 标志位

下面这些属性可以设置在任何cookie上:

  • secure- 这个属性告诉浏览器只有在使用HTTPS通信的时候才发送cookie。

  • HttpOnly- 这个属性用来防止例如跨站脚本等攻击,设置它的cookie不允许通过JavaScript来访问。

Cookie 作用域

  • domain- 这个属性用来和请求的URL指向服务端的域名进行比较,如果域名或者子域名匹配,接下来检查path属性。

  • path- 除了域名之外,还可以指定cookie有效的URL路径。如果域和路径匹配,则cookie将在请求中发送。

  • expires- 这个属性用来设计持久cookie,在这个时间之前cookie不会过期。

在Node.js中你可以使用 cookies 模块来设置cookie,强调一下,这个模块比较基础,你有可能会使用封装过的模块,例如 cookie-session

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

var app = express();

app.use(cookieSession({
  name: 'session',
  keys: [
    process.env.COOKIE_KEY1,
    process.env.COOKIE_KEY2
  ]
}));

app.use(function (req, res, next) {
  var n = req.session.views || 0;
  req.session.views = n++;
  res.end(n + ' views');
});

app.listen(3000);

示例来源于 cookie-session 模块的文档。

CSRF

跨站请求伪造是一种强制用户在已经登录的网站上执行非自愿操作的攻击。这些攻击特别针对状态变更请求,而不是窃取数据,因为攻击者无法看到对伪造请求的响应。

在Node.js中你可以使用 csrf 模块来减轻这类攻击,这个模块比较基础,有一些针对不同框架进行包装的模块。其中一个是 csurf ,在express框架中防止CSRF攻击的中间件。

在路由层你可以这样编码:

var cookieParser = require('cookie-parser');
var csrf = require('csurf');
var bodyParser = require('body-parser');
var express = require('express');

// setup route middlewares 
var csrfProtection = csrf({ cookie: true });
var parseForm = bodyParser.urlencoded({ extended: false });

// create express app 
var app = express();

// we need this because "cookie" is true in csrfProtection 
app.use(cookieParser());

app.get('/form', csrfProtection, function(req, res) {
  // pass the csrfToken to the view 
  res.render('send', { csrfToken: req.csrfToken() });
});

app.post('/process', parseForm, csrfProtection, function(req, res) {
  res.send('data is being processed');
});

在视图层你可以这样使用CSRF的token:

<form action="/process" method="POST">
  <input type="hidden" name="_csrf" value="{{csrfToken}}">

  Favorite color: <input type="text" name="favoriteColor">
  <button type="submit">Submit</button>
</form>

示例来源于 csurf 模块的文档。

数据验证

XSS

有两种相似但是不同类型的攻击需要防御。一种是反射型XSS,另一种是存储型XSS。

反射性XSS当攻击者用特制的链接将可执行JavaScript代码注入到HTML响应中时发生。

存储型XSS当存储了未经严格过滤的用户输入时发生。它在Web应用程序的权限下在用户的浏览器中运行。

为了抵御这些攻击,你需要严格过滤用户输入。

SQL 注入

SQL注入包括通过用户输入注入部分或完整的SQL查询,它也能读取敏感信息或具有破坏性。

下面是一些例子:

select title, author from books where id=$id`

如果 $id 来源于用户的输入,如果用户输入了 2 or 1=1 会怎么样?查询语句会变成这样:

select title, author from books where id=2 or 1=1`

抵御这类攻击的最简单方式就是使用参数化查询或者提前写好SQL语句。

如果你在Node.js中使用PostgerSQL,你可以使用 node-postgres 模块。创建一个参数化查询只需要这样写代码:

var q = 'SELECT name FROM books WHERE id = $1';
client.query(q, ['3'], function(err, result) {});

sqlmap 是一个开源的渗透测试工具,自动化 检测利用SQL注入漏洞并接管数据库 的过程。

命令行注入

命令注入是攻击者在远程Web服务器上运行OS命令所使用的技术。通过这种方法,攻击者甚至可以从系统获得到密码。

在实践中,如果你有这样的链接:

https://example.com/downloads?file=user1.txt`

它可以变成:

https://example.com/downloads?file=%3Bcat%20/etc/passwd`

在示例中 %3B 是标点符号点的转码,通过这种方式可以运行多个操作系统命令。

为了抵御这类攻击,你必须确保严格过滤用户的输入。

仍旧用Node.js来做例子:

child_process.exec('ls', function (err, data) {  
    console.log(data);
});

在底层 child_process.exec 调用 /bin/sh ,所以它是一个bash解释器并不是程序启动。

当用户的输入传到这个地方,就会产生问题,任意一个反撇号或者 $() ,一个新的命令将会被攻击者注入。反引号的作用就是将反引号内的Linux命令先执行,然后将执行结果赋予变量。

可以简单使用 child_process.execFile 解决这个问题。

安全传输

SSL Version, Algorithms, Key length

As HTTP is a clear-text protocol it must be secured via SSL/TLS tunnel, known as HTTPS. Nowadays high grade ciphers are normally used, misconfiguration in the server can be used to force the use of a weak cipher - or at worst no encryption.

HTTP是一个明文协议,它必须通过SSL / TLS隧道进行安全保护,我们熟知的HTTPS就是这样。现在高等级的密码被广泛使用,在服务器配置错误可以迫使一个弱密码的使用,或至少没有加密。

你需要去测试:

  • 密码、钥匙和协议协商配置正确

  • 证书有效性

使用 nmapsslyze 工具可以讲这件事变得简单。

检查证书信息

nmap --script ssl-cert,ssl-enum-ciphers -p 443,465,993,995 www.example.com`

使用sslyze测试SSL/TLS漏洞

./sslyze.py --regular example.com:443`

HSTS

在配置管理部分,我们简要的谈到了它 。 Strict-Transport-Security 头部强制和服务器使用安全连接(HTTP over SSL/TLS),下面的配置来自Twitter:

strict-transport-security:max-age=631138519`

这里的 max-age ,定义了浏览器应该自动将HTTP请求转换为HTTPS的有效时间。

可以通过下面的命令简单的测试:

curl -s -D- https://twitter.com/ | grep -i Strict`

拒绝服务

账号锁定

帐户锁定是一种减轻蛮力猜测攻击的技术。在尝试登录失败几次之后,系统禁止在给定时间内登录,最初只限制几分钟,以后成倍的增加限制时间。

你可以使用上面我们讨论过的限速模式来抵御这种攻击。

正则表达式

这种攻击利用了大多数正则表达式实现的极端情况,导致它们工作非常缓慢。这种正则表达式被称为 Evil Regexpes :

  • 使用重复分组

  • 重复组中出现:

    • 重复

    • 交替重叠

([a-zA-Z]+)* 、 (a+)+ 或者 (a|a?)+ 都是有漏洞的正则表达式,像 aaaaaaaaaaaaaaaaaaaaaaaa! 这样简单的输入就可以产生很大的计算量。更多信息请看 正则表达式DOS .

可以使用Node.js的工具 safe-regex 检查你的正则表达式。

To check your Regexes against these, you can use a Node.js tool called safe-regex 。它可能会误报,所以小心使用。

$ node safe.js '(beep|boop)*'
true
$ node safe.js '(a+){10}'
false

异常处理

异常代码、错误栈

在不同的错误场景中,应用程序可能泄漏有关底层基础设施的敏感细节, 比如: X-Powered-By:Express 。

错误栈跟踪本身不是错误,但是它经常泄露让攻击者感兴趣的信息。提供debug信息作为操作产生错误的结果是一种糟糕的做法,你应该打印而不是向用户输出这些信息。

NPM

能力越大责任越大,NPM有大量可以方便使用的模块,相应的你需要检查你的应用用到了哪些,它们可能包含了至关重要的安全问题。

Node 安全项目

Luckily the Node Security project has a great tool that can check your used modules for known vulnerabilities.

幸运的是Node 安全项目有一个非常棒的工具,你可以检查你使用的模块已知的漏洞。

npm i nsp -g
# either audit the shrinkwrap
nsp audit-shrinkwrap
# or the package.json
nsp audit-package

你还可以使用 requireSafe 来帮你做这件事。

Snyk

Snyk和Node 安全项目相似,但是它的目标不仅是提供工具发现漏洞,还能在你的项目仓库解决相关安全问题。

可以尝试一下 snyk.io

最后的笔记和想法

这个清单基于 Web Application Security Testing Cheat SheetOWASP 维护)并且很大程度受它影响。

开放Web应用安全项目(OWASP)是一个全球性的非盈利慈善组织,致力于提高软件的安全性

查看原文: Node.js 安全清单

  • blackostrich
  • biglion
  • yellowgorilla
需要 登录 后回复方可回复, 如果你还没有账号你可以 注册 一个帐号。