MongoDB 注入方式
MongoDB 注入的原理
MongoDB 注入的核心原理是利用MongoDB 查询中的特殊操作符或特性,来绕过应用程序的认证或执行未授权的数据库操作。当应用程序将用户输入直接拼接或用于构建查询对象时,攻击者可以通过构造恶意的输入来改变查询的逻辑
例如,一个典型的登录查询可能看起来像这样:
db.collection.findOne({
username: request.body.username,
password: request.body.password
});
如果攻击者在username
字段输入{"$ne": null}
,在password
字段输入{"$ne": null}
,那么查询就会变成:
db.collection.findOne({
username: {"$ne": null},
password: {"$ne": null}
});
这个查询的含义是“查找 username
和 password
都不为空的任何文档”。这就可以绕过密码验证,成功以第一个匹配的用户身份登录
常见的 MongoDB 注入方式
1. 逻辑操作符注入
这是最常见、最基础的注入方式。攻击者利用 MongoDB 的逻辑操作符,如$ne
(不等于)、$gt
(大于)、$lt
(小于)等来改变查询的逻辑
认证绕过
利用 $ne
操作符,可以构造登录认证绕过
- 用户名:
'{"$ne": null}'
- 密码:
'{"$ne": null}'
在某些情况下,也可以使用布尔类型来绕过:
- 用户名:`'admin'
- 密码:
'{"$gt": ""}'
(即密码大于空字符串的任何值)
这种方法简单且有效,因为它利用了数据库自身的查询特性
2. 数组操作符注入
MongoDB 的 $in
操作符可以用来查询某个字段是否在给定的数组中。攻击者可以利用这个特性进行注入
例如,一个查询可能用来检查用户的角色:
db.users.findOne({
username: request.body.username,
role: "admin"
});
攻击者可以尝试在 username
字段输入一个数组,来绕过这个检查:
- 用户名:
'{"$in": ["admin", "guest"]}'
- 密码:
'password'
如果应用程序没有正确处理,这个查询可能被解析为: db.users.findOne({ username: { "$in": ["admin", "guest"] }, role: "admin" });
这可以用来枚举用户或绕过某些基于角色的访问控制
3. 正则表达式注入
MongoDB 支持正则表达式查询,这使得攻击者可以利用它来进行更复杂的攻击,比如盲注
假设存在一个查询,用来查找匹配用户名的文档:
db.users.find({ username: request.body.username });
攻击者可以构造一个正则表达式,通过布尔盲注的方式来猜测数据
$regex
:用于匹配正则表达式$where
:这个操作符允许在查询中使用 JavaScript 代码,是最高危的注入点之一
盲注步骤:
- 测试是否存在注入点:
- 输入
{"$where": "this.username == 'admin'"}
,如果返回管理员信息,则说明存在注入
- 输入
- 布尔盲注猜解数据:
- 构造一个查询来猜解管理员的密码长度
{"$where": "this.username == 'admin' && this.password.length > 5"}
- 如果响应正常(例如返回管理员信息),则说明密码长度大于 5
- 否则,说明小于等于 5
- 构造一个查询来猜解管理员的密码长度
- 逐位猜解密码:
- 构造一个正则表达式来猜解密码的每一个字符
{"$where": "this.username == 'admin' && this.password.match(/^a/)"}
- 这个查询会检查管理员密码是否以
a
开头 - 通过二分法或逐位猜测,攻击者可以逐步猜解出完整的密码
- 构造一个正则表达式来猜解密码的每一个字符
4. eval
注入
在早期版本中,MongoDB 的 db.eval()
命令允许在服务器端执行 JavaScript 代码。这在功能上非常强大,但同时也是一个巨大的安全隐患,因为攻击者可以直接执行任意代码,包括操作系统命令