最近更新了Mongometer ,使其更加灵活。 发布新版本后不久,其中一位用户通过在帖子中发表评论来反馈问题。 我启动了我的机器,打开了我的IDE,发现了问题,并在半小时内将修复程序推送到了github 。 这不是快速的成功案例。 我很快就意识到,如果将来我要使用Mongometer做任何事情,我真的应该对用户如何对MongoDB中的数据库进行身份验证有更多的了解。 (我不想花一个多小时左右,因为我刚刚打开了一瓶Nyetimber Classic Cuvee –我也正在煮鸡肉派(如果需要的话可以给我盖上锅),我而不是在完成瓶子之前完成这篇文章。)在深入探讨MongoDB Security可能存在的任何文档之前,我将从一些观察开始。
因此,以典型的男人风格,让我们踢一下轮胎,然后如果需要,再踢RTFM。 启动一个mongod实例。

$ /usr/lib/mongodb/2.3.2/bin/mongod --port 27001 --fork --dbpath /data/db/2.3.2 --logpath /data/db/2.3.2/mongod.log
$ ./mongo --port 27001

创建一个管理员用户

> use admin
> db.addUser('mongouser','mongopass')
1

重新启动mongod

$ sudo kill -15 $(ps -ef | grep mongo | grep -v grep | cut -f8 -d' ')
$ /usr/lib/mongodb/2.3.2/bin/mongod --port 27001 --fork --auth --dbpath /data/db/2.3.2 --logpath /data/db/2.3.2/mongod.log
$ ./mongo --port 27001

验证给管理员

> use admin
switched to db admin
> db.aut('mongouser','mongopass')
Thu Jan 31 13:53:31.271 javascript execution failed (shell):1 TypeError: Property 'aut' of object admin is not a function
db.aut('mongouser','mongopass')
^
> db.aut('mongouser','mongopass')

哎呀 胖手指吧。 等等,我认为我发现了问题1

第1期

如果管理员用户输入的auth命令不是凭据,而不是凭据,那么实际的凭据将保留在Shell历史记录中,该历史记录将在各个会话之间持续存在。 任何其他用户都可能会来查看Shell历史记录并获取凭据。

另一方面,如果命令正确并且用户名或密码或两者都不正确,或者确实进行了身份验证尝试,则该命令不会保留在历史记录中。 (可使用与Linux框相同的方式使用mongo shell的命令历史记录-使用向上箭头)

> db.auth('mongouser','mongopass0')
{ ok: 0.0, errmsg: 'auth fails' }
0
> db.auth('mongouser0','mongopass0')
{ ok: 0.0, errmsg: 'auth fails' }
0
> db.auth('mongouser0','mongopass')
{ ok: 0.0, errmsg: 'auth fails' }
0

好。 让我们针对管理员进行身份验证并继续。

> use admin
switched to db admin
> db.auth('mongouser','mongopass')
1

哎呀 我差点错过那里。

第2期

在mongod实例重新启动之前,任何用户都可以…

> use admin
switched to db admin
> db.system.users.find()
{ '_id' : ObjectId('510a58c6de50e136190f9ed7'), 'user' : 'mongouser', 'readOnly' : false, 'pwd' : 'c49caa1cb6b287ff6b1deaeeb8f4d149' }

…获取用户名和哈希。 因此,既然我已经重新启动了mongod实例,那么任何用户都将必须针对admin进行身份验证才能查看system.users的内容。 现在,继续输入不正确的凭据,我将发起字典攻击,看看会发生什么。 噢亲爱的。 发现了另一个问题。

问题#3

没有锁定。 我写了一个快速的技巧来连接到mongod实例,切换到admin并尝试登录。使用一个相当大的词典(最后加上'mongopass'),我尝试登录了一百万次。 这仅是一次粗略的单线程尝试,大约需要17秒才能完成,但是它表明没有帐户锁定。 我有信心,如果需要的话,我可以将多线程蛮力组合在一起。 我需要对此进行进一步研究,以查看是否可以配置任何强行强制/字典式攻击警报,或者是否可以应用锁定策略。 我还没有准备好RTFM。 让我们仔细看看system.users中的密码格式。

c49caa1cb6b287ff6b1deaeeb8f4d149

在我看来,这就像MD5 。 让我们看一下可在github上浏览的代码。 哇! 我很幸运。 db.js具有以下方法:

function _hashPassword(username, password) {
return hex_md5(username + ':mongo:' + password);
}

使用hex_md5然后在utils.cpp中引用native_hex_md5

void installGlobalUtils( Scope& scope ) {
scope.injectNative( 'hex_md5' , native_hex_md5 );
scope.injectNative( 'version' , native_version );
scope.injectNative( 'sleep' , native_sleep );
installBenchmarkSystem( scope );
}

static BSONObj native_hex_md5( const BSONObj& args, void* data ) {
uassert( 10261, 'hex_md5 takes a single string argument -- hex_md5(string)',
args.nFields() == 1 && args.firstElement().type() == String );
const char * s = args.firstElement().valuestrsafe();

md5digest d;
md5_state_t st;
md5_init(&st);
md5_append( &st , (const md5_byte_t*)s , strlen( s ) );
md5_finish(&st, d);

return BSON( '' << digestToString( d ) );
}

是时候快速回顾一下了。 万一您错过任何东西:

  1. 哈希算法为MD5 ; 我最不喜欢的哈希算法。
  2. 要散列的字符串格式为username + ':mongo:' + password ; 使用相同的“盐”不是最佳选择…
  3. 字符串:mongo:是全局的; 我不太确定为什么它会一直存在。

我认为这可能现在就足够了,否则它将变成tl; dr,并且我可能会超出自我施加的时间限制。 回想我有关MongoDB的任何讨论,相同的陈述总是出现在安全性的背景下。

  1. 默认情况下,身份验证处于关闭状态。
  2. MongoDB始终旨在部署在受信任的环境中

我不得不说,即使启用了身份验证,我们仍然遇到一些棘手的问题。 此外,我认为不存在受信任的环境 。 此时,就安全性而言,是时候进行RTFM了。 我希望找到一个定义好的路线图,以解决上述问题,或者已经可以采取一些缓解措施。 因此,在不久的将来会有一些 身份验证功能。 看来新的身份验证功能仅在MongoDB Subscriber Edition下可用,我不确定这意味着什么……我也遇到了这个已知问题 ,它构成了……的基础。

问题#4

如果用户在多个数据库中具有相同的密码,则所有数据库上的哈希将相同。 恶意用户可能利用此漏洞使用不同用户的凭据来访问第二个数据库。 [原文]让我们分解一下。

“如果用户在多个数据库中具有相同的密码,则所有数据库上的哈希将相同。”

是。 正确。 相同的用户名,相同的密码和相同的“盐”(即“:mongo:”字符串”)等于相同的哈希。 好,很酷,让我们继续前进。

“恶意用户可能利用此漏洞使用不同用户的凭据来访问第二个数据库。” [原文]

恶意用户只有当在两个数据库中都拥有非只读用户时,才能利用此漏洞。

如果他们仅具有只读访问权限,则他们将无法列出system.users集合。 在这种情况下,他们将永远不会首先看到不同数据库之间的哈希值是相同的。 如果它们不是只读的,则可以列出system.users集合,并使散列的密码脱机破解。

总结来说,如果散列在数据库之间不匹配,您将不得不进入破解领域:

  1. 用户属性将是相同的。 在具有用户的不同数据库上,不同用户的几率可能很高。
  2. pwd属性将是相同的。 不同用户创建相同密码的几率很高。
  3. “盐”是相同的,因此在这里没有实际意义。

因此,这里的问题是用户(不是只读用户)可以提取给定数据库的所有密码哈希,然后将其脱机以进行破解。 恶意用户已经具有用户名和“盐”,他们所需要寻找的只是密码。

结论

第1期

这有点令人难过。 正确输入命令后(忽略凭据是否正确),命令不会显示在历史记录中。 如果未正确输入命令,则很难知道要从命令历史记录中排除的内容。 我猜您可以追溯删除导致身份验证之前导致错误的命令(即无效命令)。 那不是解决方案……

第2期

可能存在一个论点,即在admin数据库的system.users中创建了admin用户后,应强制重新启动。

问题#3

不费吹灰之力。 我已经多次编写了密码策略(我过着什么样的生活,是吗?),帐户锁定是密码101。

问题#4

似乎为每个数据库创建一个“盐”(':mongo:')可以解决此问题。 看一下代码,看起来实现是轻而易举的事,是一次轻松快捷的胜利。 添加选项以手动设置它将会很盛大。 在幕后实施一个独特的“盐”,使用户不必考虑它,同样是宏伟的。 这样,Nyetimber完成了,发布完成了。 我并不是说这篇文章中有什么新的或聪明的地方,这只是粗略的浏览。 我没有去; 我提到的一切只是观察。 我几乎每天都安装mongo,因为它是一个很棒的产品,但是我确实喜欢拥有平衡的视野并识别房间中的任何大象。 我会对任何反馈感兴趣。

参考:来自我们的JCG合作伙伴 Jan Ettles的MongoDB身份验证 ,位于ExceptionalExceptions例外博客。

翻译自: https://www.javacodegeeks.com/2013/02/mongodb-authentication.html

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐