安全是不容忽视的,每个开发者都知道它非常重要,真正严肃对待它的却没有几人。我们 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());
Koa和ThinkJS框架中可以使用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 标志位
下面这些属性可以设置在任何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
HTTP是一个明文协议,它必须通过SSL / TLS隧道进行安全保护,我们熟知的HTTPS就是这样。现在高等级的密码被广泛使用,如果在服务器配置错误,会使用一个弱密码来替代,或没有加密。
你需要去测试:
密码、钥匙和协议协商配置正确
证书有效性
使用 nmap和sslyze 工具可以将这件事变得简单。
检查证书信息
``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.
你可以使用工具 safe-regex检查你的正则表达式,它可能会误报,所以小心使用。
$ node safe.js '(beep|boop)*'
true
$ node safe.js '(a+){10}'
false
异常处理
异常代码、错误跟踪栈
在不同的错误场景中,应用程序可能泄漏有关底层基础设施的敏感细节, 比如:X-Powered-By:Express。
错误跟踪栈本身不是错误,但是它经常泄露让攻击者感兴趣的信息。提供debug信息作为操作产生错误的结果是一种糟糕的做法,你应该打印而不是向用户输出这些信息。
NPM
能力越大责任越大,NPM有大量可以方便使用的模块,相应的你需要检查你的应用用到了哪些,它们可能包含了至关重要的安全问题。
Node 安全项目
幸运的是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。
Final
这个清单基于 Web Application Security Testing Cheat Sheet(OWASP维护)并且很大程度受它影响。