Ruby China 的 Mongodb Hash 注入漏洞
今天在 Ruby China 上看见一个 帖子,从下面的回复中发现是 Mongodb 的漏洞,然后顺便学习了下。 漏洞详细介绍 以用户登陆而言,需要先根据用户传过来的帐户名从数据库中找到这条记录,然后再验证密码。 用户登陆流程 一个登陆表单 input type= "text" name=
今天在 Ruby China 上看见一个 帖子,从下面的回复中发现是 Mongodb 的漏洞,然后顺便学习了下。
漏洞详细介绍
以用户登陆而言,需要先根据用户传过来的帐户名从数据库中找到这条记录,然后再验证密码。
用户登陆流程
一个登陆表单
type="text" name="session[account]">
type="text" name="session[password]">
当提交后,服务端得到的数据是这样的(去除其它 token 等信息)。
{ "session" => { "account" => "username", "password" => "password" } }
然后服务端通过帐户名从数据库中取得记录
User.find_by(account: params[:session][:account]) # => User.find_by(account: "username")
看起来很正常,但是问题就出现在这一步。
上面的查询语句转换成 Mongodb 查询语句是这样的
db.users.find({ account : params[:session][:account] }).limit(1)
如果参数是普通的字符串,那么是没有问题的,但是如果它是一个 Hash 呢?
如果 params[:session][:account]
的值是 { "$ne" => "username" }
,那么得到的 Mongodb 查询语句就是这样的
db.users.find({ account : { $ne : "username" } }).limit(1)
这段代码什么意思?找到所有 account 不等于 username 的记录。同样 $ne
可以换成其他 Mongodb 支持的操作,比如 $gt
, $lt
。username 也可以换成一串乱序字符串,这样就能得到用户集合中的所有记录。
注入
想让服务端得到的参数是 Hash 很简单,只需要手动修改一下表单就行了。
原表单
type="text" name="session[account]">
修改后的表单
type="text" name="session[account][$ne]">
这样,服务端得到的参数就是这个样子的。
{ "session" => { "account" => { "$ne" => "username" }, "password" => "password" } }
解决方法
将参数转化为字符串
Ruby China 的解决方法就是这种。
account = params[:session][:account].to_s User.find_by(account: account)
Strong Parameters
Rails 4 开始提供了 Strong Parameters 用来对 params 参数进行过滤。基本语法是
def session_params params.require(:session).permit(:account, :password) end
然后使用过滤后的数据进行查询数据库。
User.find_by(account: session_params[:account])
Strong Parameters
Strong Parameters 是 Rails 4 中提供的用于过滤用户输入的机制,其核心的两个方法是
ActionController::Parameters#require
ActionController::Parameters#permit
require 用来获取参数中指定键的值
如果不存在则产生 ParameterMissing
异常
对于以下参数
{ "session" => { "account" => { "$ne" => "username" }, "password" => "password" } }
使用 params.require(:session)
后得到的结果是这样的
{ "account" => { "$ne" => "username" }, "password" => "password" }
permit 用来对参数进行实际的过滤
对于 { "account" => "username", "password" => "password" }
,使用 permit(:account, :password)
得到的结果还是原 Hash,因为该 Hash 中的两个键都被 permit 了,而使用 permit(:account)
得到的结果是 { "account" => "username" }
,由于没有 permit :password
,所以结果中 password 被过滤掉了。
如果是 { "account" => { "$ne" => "username" } }
的话,直接 permit(:account)
的结果是 nil
。
如果需要保留多级参数,需要明确指出。
permit(:password, account: :$ne) # 或者多个键 permit(:password, account: [ :$ne, :$regexp ])
总结
- 这个漏洞对于普通的用户表单登陆没有多大影响,因为这里只是查找记录,然后验证密码,所以只会提示用户密码错误而已。但是对于 API 接口就有隐患了,API 接口是通过 token 而不是验证密码登陆的。
- 这件事让我更加了解了 Rails 4 中 Strong Parameters 的厉害之处!
本文出自:http://blog.sloger.info/, 原文地址:http://sloger.info/posts/mongodb-hash-injection-bugs, 感谢原作者分享。