安全是不容忽视的, 每个开发者都知道它非常重要, 真正严肃对待它的却没有几人我们 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 服务器的配置中(ApacheNginx), 不需要对应用代码进行改动在 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=;cat /etc/passwd``
在示例中 ; 是标点符号点的转码, 通过这种方式可以运行多个操作系统命令
为了抵御这类攻击, 你必须确保严格过滤用户的输入
仍然用 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 维护)并且很大程度受它影响
开放 Web 应用安全项目 (OWASP) 是一个全球性的非盈利慈善组织, 致力于提高软件的安全性
来源: https://juejin.im/entry/5ab8940a51882555784df3ed