51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

Mongoose 搜索注入漏洞分析

漏洞简介

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

image

快速创建一个项目并指定 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

image

mongoose@6.13.5

image

mongoose@6.13.6

image

通过执行结果我们发现,在 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=

image

第一次进行修复

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=

image

image

第二次修复

1. 函数接受一个参数 match ,表示要检查的对象。

2. 首先进行两个条件判断:

  如果 matchnullundefined ,直接返回,不进行后续检查。

  如果 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] 不为 nullundefined ,并且其类型为对象,则递归调用 throwOn$where 函数,将 match[key] 作为参数传入,对嵌套的对象进行相同的检查。

通过递归调用 throwOn$where 函数,可以对 match 对象进行深度遍历,检查其中是否包含 $where 操作符,无论 $where 操作符位于对象的哪个层级。


01.png


本文作者: dingjiacan@antvsion.com

本文为安全脉搏专栏作者发布,转载请注明: https://www.secpulse.com/archives/205921.html

赞(3)
未经允许不得转载:工具盒子 » Mongoose 搜索注入漏洞分析