跳至主要内容

防止 MongoDB 中的 SQL/NoSQL 注入攻击

通过验证输入、保障查询安全并采用纵深防御,保护 MongoDB 免受 NoSQL 注入。本文介绍常见攻击模式与防护方法。
· 6分钟 阅读

使用 AI 探索

当用户输入未经过验证时,MongoDB 应用将面临严重的安全风险。NoSQL 注入攻击可能在无声无息间入侵您的数据库,泄露敏感数据并破坏系统完整性。本文将演示经过验证的防御策略,用于清理输入、保护数据库查询,并构建对基于注入的威胁的稳健防护。

什么是 NoSQL 注入?

当攻击者利用未充分验证的用户输入来操纵数据库查询逻辑时,就会发生 NoSQL 注入。通过注入恶意载荷,攻击者可以绕过身份验证机制、提取未授权数据,或规避应用的安全控制。

不同于通过字符串拼接构造查询的 SQL 注入,NoSQL 注入利用的是灵活的数据结构和动态查询。当应用接受诸如 interface{} 或 map[string]interface{} 等松散类型输入且未进行适当验证时,风险尤为突出。

MongoDB 的不同之处

MongoDB 查询使用 BSON(二进制 JSON)对象——结构化的键值对——而非原始字符串构建。这种设计避免了许多传统的注入风险,因为查询并非通过字符串拼接组装。

然而,如果未经适当验证就将用户输入直接嵌入这些 BSON 对象,攻击者就能注入如 $ne、$gt 或 $or 等 MongoDB 运算符,从而改变查询逻辑。

示例:在 Go 中的运算符注入

filter := bson.M{"name": inputName}
collection.Find(ctx, filter)

如果 inputName 是诸如 "Joe" 这样的普通字符串,是安全的。但如果您的代码允许 map[string]interface{} 或原始 JSON,攻击者就可以注入如下的运算符对象:

{ "$ne": null }

该载荷会改变查询逻辑,匹配任何名称存在的文档,而非筛选特定值——从而有效绕过应用预期的访问控制。

识别不安全模式与常见陷阱

1. 对请求正文的隐式信任

将请求正文直接解析为 BSON 映射,会为攻击者开辟一条注入通道,使其能将恶意查询运算符和复杂逻辑结构直接嵌入数据库操作。

// Unsafe
var filter bson.M
_ = json.NewDecoder(req.Body).Decode(&filter)

应改为仅解析已知字段与类型:

type QueryInput struct {
    Name string json:"name" validate:"required"
}

2. 接受松散类型

当 API 接受过于宽松的数据类型(如 interface{} 或通用映射)时,常会出现注入漏洞。这些灵活结构可能夹带嵌入的 MongoDB 运算符或恶意对象构造,从而绕过预期的查询逻辑。

尽量避免使用松散类型,除非绝对必要;在无法避免时,务必实施严格验证。应定义与预期输入格式相匹配的显式数据结构,并约束可接受的取值范围。

3. 在查询字符串或查询运算符中使用 JSON

通过 URL 查询参数或搜索端点暴露动态筛选功能的 API 是极具吸引力的攻击面。攻击者常通过将 JSON 编码的运算符与条件逻辑直接嵌入查询字符串来针对这些接口。这样会将合法的筛选参数转变为恶意的数据库操作:

GET /search?filter={"price":{"$gt":0}}

若缺乏严格的输入验证,这种方式将使攻击者能够操纵查询结构并规避安全控制,可能导致未授权数据暴露或权限被提升至超出预期的访问级别。

缓解措施:

  • 实现字段与运算符的允许列表,将查询参数限制为预定义的安全取值。
  • 在构建数据库过滤器时,明确将所有用户提供的值包裹在 $eq 运算符内,以防注入替代性运算符。

4. 通过注入扩展逻辑查询

允许用户控制 $or 等逻辑运算符的应用,会为查询扩展攻击提供机会。攻击者可向这些运算符中注入额外的条件表达式,从根本上改变查询语义,进而可能绕过身份验证检查或数据访问限制。

filter := bson.M{
  "$or": []bson.M{
    {"role": "user"},
    {"active": true},
  },
}

如果用户输入可以直接操纵 $or 子句,攻击者就能注入未授权条件:

{ "$or": [ {"role": "user"}, {"active": true}, {"isAdmin": true} ] }

该修改后的查询可能绕过基于角色的访问控制,从而暴露管理数据,或向未授权用户授予更高权限。通过实施严格的输入验证并避免让用户直接控制查询结构,来防止对逻辑运算符的动态修改。

GET /search?filter={"price":{"$gt":0}}

缺乏严格输入验证时,此方式将使攻击者能够操纵查询结构并绕过安全控制,可能导致未授权数据暴露。

缓解措施:

  • 为允许的字段与运算符建立允许列表,拒绝任何超出预定义范围的查询参数。
  • 通过将所有用户输入包裹在 $eq 运算符中,强制执行显式相等匹配,防止替换比较或逻辑运算符。

JavaScript 执行特性

MongoDB 提供了多种服务端 JavaScript 执行能力,可能引入安全风险,包括:

这些特性带来显著的安全暴露,除非业务需求至关重要,否则应避免使用。为防止被利用,请在 MongoDB 配置中禁用脚本执行:

对于 mongod:

--noscripting

或通过配置文件:

security:
  javascriptEnabled: false

在 分片部署中的 mongos 同样应用该配置。

MongoDB 的原生查询运算符已提供全面功能,几乎在所有用例下都无需执行 JavaScript。除非您能对输入验证与执行环境保持绝对控制,否则完全避免使用 $where 子句。

如何防止 NoSQL 注入

1. 严格验证输入

实施严格的类型验证,仅接受预期的原始数据类型,如字符串、整数或布尔值。拒绝任何可能包含嵌入操作或恶意构造的嵌套对象、数组或未识别字段名。

type LoginInput struct {
    Username string json:"username" validate:"required,alphanum"
    Password string json:"password" validate:"required,min=8"
}

2. 不要信任来自用户的查询结构

使用预定义、由应用控制的字段映射和查询模板来构建所有数据库查询。禁止用户输入影响查询架构、逻辑运算符或条件结构,防止其颠覆既定的数据访问模式。

filter := bson.M{
    "username": input.Username,
    "password": input.Password,
}

避免:

// Dangerous: allows injection of operators
_ = json.NewDecoder(req.Body).Decode(&filter)

大多数 MongoDB 驱动程序都提供强类型的查询构建器,可强制执行结构化查询构造并防止意外的注入漏洞。应优先使用这些原生接口,而非通过应用端点暴露原始查询映射或 BSON 对象。

filter := bson.M{
    "username": input.Username,
    "password": input.Password,
}

避免:

// Dangerous: allows injection of operators
_ = json.NewDecoder(req.Body).Decode(&filter)

3. 阻止 MongoDB 运算符注入

防止用户可控输入充当查询运算符或逻辑结构。将所有用户数据视为字面值,而非可执行的查询组件。

不要这样做:

filter := bson.M{
    "status": input.Status,
}

应这样做:

filter := bson.M{
    "status": bson.M{"$eq": input.Status},
}

此方式强制执行严格的相等匹配,防止执行注入的运算符,如 $ne、$in 或 $regex,以及其他可能绕过预期查询逻辑的逻辑修饰符。

4. 为数据库用户使用最小权限

将应用的数据库连接配置为仅具备完成目标操作所需的最小权限。避免在应用配置中使用管理级凭据,以防在注入成功时造成更大破坏。

5. 监控与测试

实施全面日志记录以检测异常查询模式,并建立定期的安全测试流程。通过使用如下畸形载荷尝试注入来验证端点安全性:

{ "$ne": null }

确认应用会拒绝此类输入。

可考虑的测试用例:

  • 对象代替字符串: { "username": { "$ne": null } }
  • 数组代替标量: { "roles": [ { "$gt": "" } ] }
  • 将运算符作为查询值: { "field": { "$regex": ".*" } }

摘要清单

  • 输入验证:强制数据类型严格、校验格式并拒绝意外字段。
  • 查询构建:使用预定义的模式与由应用控制的模板构建所有查询。
  • 运算符防护:通过将值包裹在 $eq 中或使用类型化 API,阻止在用户输入中使用 MongoDB 运算符。
  • 服务端 JavaScript:移除 $where 子句,并在生产环境配置中禁用 JavaScript 执行。
  • 数据库访问控制:为应用连接配置最小必要权限,避免使用管理凭据。
  • 测试与监控:定期开展注入测试,并为异常查询模式实施日志监控。

结语

通过自律的安全编码实践,NoSQL 注入漏洞完全可以被预防。实施严格的输入验证、结构化的查询构建与纵深防御安全控制,能够在保持应用灵活性与性能的同时,消除注入攻击向量。

成功的关键在于理解应用的完整数据流——从最初的请求解析到最终的查询执行。对所有输入源建立零信任原则,在系统边界实施验证,并在整个技术栈中优先采用防御式编程原则。

常见问题

如果 MongoDB 使用的是 BSON 而非字符串,为什么仍然存在 NoSQL 注入风险?

在构建 BSON 对象之前仍可能发生注入。如果未经验证就将用户输入解析为诸如 map[string]interface{} 或 interface{} 等灵活类型,攻击者就可以注入如 $ne 或 $or 等 MongoDB 运算符并操纵查询逻辑。

直接将请求正文解码到 bson.M 或 map[string]interface{} 是否安全?

不安全。这样会允许用户注入任意查询运算符。始终将请求数据映射为严格类型(例如 Go 的结构体),并在构建查询前验证输入。

如果已验证输入,使用 $where 有什么问题?

即便对输入做了部分验证,$where 仍会在服务器上执行 JavaScript。任何转义或验证上的疏漏都可能导致代码注入。更安全的做法是完全避免使用 $where,或在 MongoDB 配置中禁用脚本执行。

如何测试我的端点是否容易受到 NoSQL 注入?

可尝试注入如下载荷:

{ "username": { "$ne": null } }

如果端点返回有效响应或绕过逻辑(例如登录),则说明输入未得到充分验证或约束。

在 Go 中安全构建查询的最佳方式是什么?

使用类型化结构体接收输入,避免解码到通用映射;需要时将值包裹在 $eq 中。切勿让用户定义运算符或直接注入查询的部分。

主题