Node提供了相对底层的API,通过它构建各种各样的Web应用都是相对容易的,但在Web应用中,不得不重视数据上传相关的安全问题。由于Node与前端Javascript的近缘性,前端Javascript甚至可以上传至服务器直接执行,但在这里我们并不讨论这样危险的动作,而是介绍内存和CSRF相关的安全问题。
1. 内存限制
在解析用户提交的表单、JSON和XML的时候,我们采取的策略是先保存所有数据,然后再解析处理,最后才传递给业务逻辑。这种策略存在潜在的问题是,它仅仅适合数据量小的提交请求, 一旦数据量过大,将发生内存被占光的情况。攻击者通过客户端能够十分容易地模拟伪造大量数 据,如果攻击者每次提交1 MB的内容,那么只要并发请求数量一大,内存就会很快地被吃光。
要解决这个问题主要有两个方案。
? 限制上传内容的大小,一旦超过限制,停止接收数据,并响应400状态码。
? 通过流式解析,将数据流导向到磁盘中,Node只保留文件路径等小数据。
首先介绍一下Connect框架中采用的上传数据量的限制方式,如下所示:
const bytes = 1024; (req, res) => { let received = 0, const len = req.headers[‘content-length‘] ? parseInt(req.headers[‘content-length‘], 10) : null; // 如果内容超过长度限制,返回请求实体过长的状态码 if (len && len > bytes) { res.writeHead(413); res.end(); return; } // limit req.on(‘data‘, function (chunk) { received += chunk.length if (received > bytes) { // 停止接收数据,触发end() req.destroy(); } }) handle(req, res) }
从上面的代码中我们可以看到,数据是由包含Content-Length的请求报文判断是否长度超过限制的,超过则直接响应413状态码。对于没有Content-Length的请求报文,则更为简略些,在每个data事件中判断即可。一旦超过限制值,服务器停止接收新的数据片段。如果是JSON文件或 XML文件,极有可能无法完成解析。对于上线的Web应用,添加一个上传大小限制十分有利于保 护服务器,在遭遇攻击时,能镇定从容应对。
2. CSRF
CSRF的全称是Cross-Site Request Forgery,中文意思为跨站请求伪造。通常而言,服务器端与客户端通过Cookie来标识和认证用户,用户通过浏览器访问服务器端的Session ID 是无法被第三方知道的,但是CSRF的攻击者并不需要知道Session ID就能让用户中招。
为了详细解释CSRF攻击是怎样一个过程,这里以一个留言的例子来说明。假设某个网站有 这样一个留言程序,提交留言的接口如下所示:
http://domain_a.com/guestbook
用户通过POST提交content字段就能成功留言。服务器端会自动从Session数据中判断是谁提 交的数据,补足username和updatedAt两个字段后向数据库中写入数据,如下所示:
(req, res) => { // req.body.content来自connect框架 const content = req.body.content || ‘‘; // session需自行实现,此处假设上文已实现session const username = req.session.username; const feedback = { username: username, content: content, updatedAt: Date.now() }; // 此处根据自己使用的数据库类型自行修改 db.save(feedback, err => { res.writeHead(200); res.end(‘Ok‘); }); }
正常的情况下,谁提交的留言,就会在列表中显示谁的信息。如果某个攻击者发现了这里的 接口存在CSRF漏洞,那么他就可以在另一个网站(http://domain_b.com/attack)上构造了一个表 单提交,如下所示:
<form id="test" method="POST" action="http://domain_a.com/guestbook"> <input type="hidden" name="content" value="vim是这个世界上最好的编辑器" /> </form> <script type="text/javascript"> $(function () { $("#test").submit(); }); </script>
这种情况下,攻击者只要引诱某个domain_a的登录用户访问这个domain_b的网站,就会自动提交一个留言。由于在提交到domain_a的过程中,浏览器会将domain_a的Cookie发送到服务器, 尽管这个请求是来自domain_b的,但是服务器并不知情,用户也不知情。
以上过程就是一个CSRF攻击的过程。这里的示例仅仅是一个留言的漏洞,如果出现漏洞的 是转账的接口,那么其危害程度可想而知。
尽管通过Node接收数据提交十分容易,但是安全问题还是不容忽视。好在CSRF并非不可防御,解决CSRF攻击的方案有添加随机值的方式,如下所示:
const generateRandom = function (len) { return crypto.randomBytes(Math.ceil(len * 3 / 4)) .toString(‘base64‘) .slice(0, len) }
也就是说,为每个请求的用户,在Session中赋予一个随机值,如下所示:
const token = req.session._csrf || (req.session._csrf = generateRandom(24));
在做页面渲染的过程中,将这个_csrf值告之前端,如下所示:
<form id="test" method="POST" action="http://domain_a.com/guestbook"> <input type="hidden" name="content" value="vim是这个世界上最好的编辑器" /> <input type="hidden" name="_csrf" value="<%=_csrf%>" /> </form>
由于该值是一个随机值,攻击者构造出相同的随机值的难度相当大,所以我们只需要在接收端做一次校验就能轻易地识别出该请求是否为伪造的,如下所示:
(req, res) => { const token = req.session._csrf || (req.session._csrf = generateRandom(24)); const _csrf = req.body._csrf; if (token !== _csrf) { res.writeHead(403); res.end("禁止访问"); } else { handle(req, res); } }
_csrf字段也可以存在于查询字符串或者请求头中。
原文地址:https://www.cnblogs.com/Mr-CCQT/p/11641941.html