漏洞简介
CVE-2024-53900 Mongoose 8.8.3、7.8.3 和 6.13.5 之前的版本容易受到 $where
运算符不当使用的影响。此漏洞源于 $where
子句能够在 MongoDB 查询中执行任意 JavaScript 代码,这可能导致代码注入攻击以及未经授权的数据库数据访问或操纵。
CVE-2025-23061 Mongoose 8.9.5、7.8.4 和 6.13.6 之前的版本容易受到 $where
运算符不当使用的影响。此漏洞源于 $where
子句能够在 MongoDB 查询中执行任意 JavaScript 代码,可能导致代码注入攻击以及未经授权的数据库数据访问或操纵。该问题的存在是因为CVE-2024-53900的修复不完整。
Mongoose 是一个用于 Node.js 的 MongoDB 对象建模工具,它使得与 MongoDB 数据库交互变得更加简单和高效。我们可以看到这两个漏洞描述大体相同,都是因为在使用 $where
运算符时出现了问题。
环境搭建
安装 MongoDB 不知道是不是本地环境的问题,错误百出,于是还是采用 docker 来安装 docker pull mongo docker run --name mongodb -d -p 27017:27017 mongo
快速创建一个项目并指定 mongoose 版本
npm
init
-y
npm
install mongoose@6.13.4
--save
node
test.js
漏洞复现
根据漏洞特点我编写了一个 js 脚本,在不同版本下执行,比较不同情况对应的结果
const
mongoose
=
require
(
"mongoose"
);
// 连接 MongoDB
const
MONGO_URI
=
"mongodb://localhost:27017/testdb"
;
async
function
testWhereInjection
() {
await
mongoose
.
connect
(
MONGO_URI
, {
useNewUrlParser
:
true
,
useUnifiedTopology
:
true
});
// 定义 User 模型和 Post 模型
const
UserSchema
=
new
mongoose
.
Schema
({
username
:
String
,
isAdmin
:
Boolean
,
password
:
String
});
const
PostSchema
=
new
mongoose
.
Schema
({
title
:
String
,
content
:
String
,
author
: {
type
:
mongoose
.
Schema
.
Types
.
ObjectId
,
ref
:
'User'
}
});
const
User
=
mongoose
.
model
(
"User"
,
UserSchema
);
const
Post
=
mongoose
.
model
(
"Post"
,
PostSchema
);
// 插入测试数据
await
User
.
deleteMany
({});
await
Post
.
deleteMany
({});
const
users
=
await
User
.
insertMany
([
{
username
:
"admin"
,
isAdmin
:
true
,
password
:
"admin123"
},
{
username
:
"user1"
,
isAdmin
:
false
,
password
:
"user123"
},
{
username
:
"user2"
,
isAdmin
:
false
,
password
:
"user456"
}
]);
await
Post
.
insertMany
([
{
title
:
"Post 1"
,
content
:
"Content 1"
,
author
:
users
[
0
].
_id
},
{
title
:
"Post 2"
,
content
:
"Content 2"
,
author
:
users
[
1
].
_id
}
]);
console
.
log
(
"√ 已插入测试数据"
);
// 1. 正常的 populate 查询
try
{
const
result
=
await
Post
.
findOne
().
populate
({
path
:
'author'
,
match
: {
username
:
"admin"
}
});
console
.
log
(
"√ 正常 populate 查询结果:"
,
result
);
}
catch
(
err
) {
console
.
error
(
"× 正常 populate 查询失败:"
,
err
.
message
);
}
// 2. 测试 populate match 中的 $where 注入
try
{
const
result
=
await
Post
.
findOne
().
populate
({
path
:
'author'
,
match
: {
$where
:
"this.isAdmin"
}
// 修改这里,去掉 return
});
console
.
log
(
"√ `$where` populate 查询成功,说明可能存在漏洞:"
,
result
);
}
catch
(
err
) {
console
.
error
(
"× `$where` populate 查询被拦截:"
,
err
.
message
);
}
// 3. 测试深层嵌套的 $where 注入
try
{
const
result
=
await
Post
.
findOne
().
populate
({
path
:
'author'
,
match
: {
$and
: [
{
nested
: {
$where
:
"this.isAdmin"
} }
// 修改这里,去掉 return
]
}
});
console
.
log
(
"√ 嵌套 `$where` populate 查询成功,说明可能存在漏洞:"
,
result
);
}
catch
(
err
) {
console
.
error
(
"× 嵌套 `$where` populate 查询被拦截:"
,
err
.
message
);
}
// 4. 测试数组中的 $where 注入
try
{
const
result
=
await
Post
.
findOne
().
populate
({
path
:
'author'
,
match
: [{
$where
:
"this.isAdmin"
}]
// 修改这里,去掉 return
});
console
.
log
(
"√ 数组中的 `$where` populate 查询成功,说明可能存在漏洞:"
,
result
);
}
catch
(
err
) {
console
.
error
(
"× 数组中的 `$where` populate 查询被拦截:"
,
err
.
message
);
}
await
mongoose
.
disconnect
();
}
testWhereInjection
().
catch
(
console
.
error
);
mongoose@6.13.4
mongoose@6.13.5
mongoose@6.13.6
通过执行结果我们发现,在 mongoose@6.13.4 中, $where
语句可以任意执行语句,经过修复后的 mongoose@6.13.5 中,只能通过嵌套来执行插入的语句,mongoose@6.13.6 已经修复了通过嵌套执行插入语句的问题。
漏洞分析
https://github.com/Automattic/mongoose/compare/6.13.4...6.13.5?diff=split&w=
第一次进行修复
1. 首先判断 match
是否为一个数组,使用 Array.isArray(match)
进行检查。
2. 如果 match
是一个数组,则使用 for...of
循环遍历数组中的每个元素 item
。
3. 对于每个 item,进行以下检查:
如果 item
不为 null (item !\= null)
,并且 item 对象中存在 $where
属性 (item.$where)
,则抛出一个 MongooseError 异常,错误信息为 " Cannot use $where filter with populate() match
"。这是因为在 populate() 查询中不允许使用 $where
操作符。
4. 如果 match
不是一个数组,则进行另一个判断:
如果 match
不为 null (match !\= null)
,并且 match
对象中存在 $where
属性 (match.$where !\= null)
,同样抛出一个 MongooseError 异常,错误信息为 " Cannot use $where filter with populate() match
"。
进行 populate()
查询时,防止使用 $where
操作符,检查传入的 match
参数是否包含 $where
属性,无论 match
是一个数组还是一个对象。如果发现 match
中存在 $where
属性,就会抛出一个 MongooseError 异常,提示不能在 populate()
查询中使用 $where
过滤器
https://github.com/Automattic/mongoose/compare/6.13.5...6.13.6?diff=split&w=
第二次修复
1. 函数接受一个参数 match
,表示要检查的对象。
2. 首先进行两个条件判断:
如果 match
为 null
或 undefined
,直接返回,不进行后续检查。
如果 match
的类型不是对象,也直接返回,不进行后续检查。 这两个判断是为了避免对非对象类型进行遍历和递归。
3. 使用 Object.keys(match)
获取 match
对象的所有属性键,并使用 for...of
循环遍历每个属性键 key
。
4. 对于每个属性键 key
,进行以下检查:
如果 key
等于 '$where'
,表示在 match
对象中发现了 $where 操作符,抛出一个 MongooseError 异常,错误信息为 " Cannot use $where filter with populate() match
"。
5. 如果当前属性的值 match[key]
不为 null
或 undefined
,并且其类型为对象,则递归调用 throwOn$where
函数,将 match[key]
作为参数传入,对嵌套的对象进行相同的检查。
通过递归调用 throwOn$where
函数,可以对 match
对象进行深度遍历,检查其中是否包含 $where 操作符,无论 $where 操作符位于对象的哪个层级。
本文作者: dingjiacan@antvsion.com
本文为安全脉搏专栏作者发布,转载请注明: https://www.secpulse.com/archives/205921.html