NoSQL 注入 是一种Web安全漏洞,类似于传统的 SQL 注入,但它针对的是 NoSQL 数据库系统。当应用程序在构建 NoSQL 数据库查询时,未能正确地清洗或参数化来自用户输入的数据时,攻击者可以通过注入恶意构造的字符串或数据结构,来篡改查询的逻辑,从而绕过认证、获取未经授权的数据,甚至执行远程代码。

核心思想:利用 NoSQL 数据库查询语言的灵活性及其对数据类型(特别是 JSON 或类似 BSON 格式)的处理方式,将恶意数据作为查询逻辑的一部分注入,从而改变预期的查询行为。


一、为什么存在 NoSQL 注入?对传统 SQL 注入的继承与发展

NoSQL 数据库因其高可伸缩性、灵活性和无模式(schema-less)特性而广受欢迎,但随着其普及,也带来了新的安全挑战。NoSQL 注入就是其中之一。

与 SQL 注入的共性:

  • 输入验证不足:核心原因都是应用程序未能正确地验证、过滤或转义用户输入。
  • 查询构建不当:攻击者能够操纵应用程序构建的数据库查询或命令。
  • 信任用户输入:应用程序盲目信任并直接将用户输入拼接到查询中。

与 SQL 注入的区别:

  • 查询语言不同:NoSQL 数据库有多种类型(键值、文档、列族、图数据库等),其查询语言和数据模型与 SQL 大相径庭。因此,SQL 注入的常规负载(如 ' OR 1=1 --)在 NoSQL 注入中通常无效。
  • 数据模型区别:SQL 数据库通常是关系型的二维表格结构,而 NoSQL 数据库的数据结构更加多样,如 JSON 文档、键值对、图等。
  • 攻击载荷多样:NoSQL 注入的攻击载荷不再局限于字符串拼接,可能涉及 JSON 语法、JavaScript 代码、正则表达式、运算符等,其形式取决于具体的 NoSQL 数据库类型和其查询语言特性。
  • 注入点更广:除了常见的用户输入字段,一些 NoSQL 数据库(特别是 MongoDB)允许在查询中使用 JavaScript 代码或特殊运算符,这为攻击者提供了更多注入点。

二、NoSQL 注入的核心机制

NoSQL 注入的核心是利用 NoSQL 数据库查询接口的特性,通过用户输入改变查询的语义。最常见的 NoSQL 注入类型发生在 MongoDB 数据库中,因为它广泛使用 BSON(JSON 的二进制形式) 作为数据存储和查询语言,并且支持在查询中使用 JavaScript。

2.1 MongoDB NoSQL 注入原理

MongoDB 的查询通常是基于 JSON 对象的。例如,一个登录验证的查询可能看起来像这样(伪代码):

1
2
3
4
5
6
7
8
9
10
11
// 后端 JavaScript (或 Go/Python/Java 驱动的等价实现)
var username = req.body.username; // 用户输入
var password = req.body.password; // 用户输入

db.users.findOne({ "username": username, "password": password }, function(err, user) {
if (user) {
// 登录成功
} else {
// 登录失败
}
});

如果 usernamepassword 未经清理直接用于构建查询,攻击者就有了利用的机会。

2.1.1 运算符注入

MongoDB 查询支持丰富的运算符(如 $gt, $lt, $ne, $in, $or, $and 等)。攻击者可以注入这些运算符来改变查询逻辑。

攻击载荷示例 (修改登录逻辑):

假设应用程序的查询是 {"username": username, "password": password}

  1. 绕过密码验证

    • 注入 password 字段: {"password": {"$ne": null}} (密码不为空即可) 或 {"password": {"$gt": ""}} (密码是非空字符串即可)。
    • 攻击者输入:
      • username: admin
      • password: {"$ne": null} (作为 JSON 字符串提交)
    • 后端拼接后的查询(伪代码):
      1
      db.users.findOne({ "username": "admin", "password": { "$ne": null } })
    • 这个查询现在将匹配所有 _id 字段存在且不为 null 的文档,这通常是所有文档。只要 admin 用户存在,且其 password 字段不为 null,攻击便成功绕过密码验证。
  2. 利用 $or 运算符 (布尔盲注)

    • 攻击者输入:
      • username: admin
      • password: $ne null, "or":[{"username":"admin"},{"username":"alice"}]
    • 真实攻击通常不会这么直接。更常见的是通过 JSON 结构注入。
    • 注入到 username 字段:'{"$ne":"admin"}' 实际上会查找所有用户名不是 admin 的用户。
    • 如果目标查询是 { "username": inputUsername, "password": inputPassword }
    • 攻击者可能构造 usernameadminpassword{ "$eq": "some_dummy_pass" }
      或者,更常见的是,攻击者在输入中注入一个包含 $or 的 JSON 结构,从而使得查询的逻辑判断被改变。
    • 复杂的 $or 注入可以这样实现:
      • username: {"$or": [{"username": "admin"}, {"username": "anything"}]}
      • password: {"$eq": "fake_password"} (此字段被 $or 覆盖)
    • 或者,攻击者直接在username字段注入 {"$ne":"dummy","$where":"this.password === 'expectedPass'"},这会利用 $where 进行 JavaScript 代码执行。

2.1.2 $where$js 注入 (JavaScript 执行)

MongoDB 允许在查询中使用 JavaScript 表达式,这提供了强大的灵活性,但也带来了巨大的安全风险。$where$eval 运算符可以直接执行服务器端的 JavaScript 代码。

攻击载荷示例 (MongoDB JS 注入):

假设应用程序过滤功能或搜索功能中使用了 $where

1
2
3
// 后端伪代码 (不安全的实现)
var criteria = req.body.criteria; // 用户输入
db.collection.find({ "$where": "this.data == '" + criteria + "'" });

攻击者输入 criteria 为:

1
'; while(true){ print("Injected infinite loop!"); }; return true; //

1
'; var adminUser = db.users.findOne({username: "admin"}); adminUser.password = "newpassword"; db.users.save(adminUser); return true; //

攻击效果:

  • 注入无限循环可能导致服务器拒绝服务 (DoS)。
  • 注入密码重置代码可以直接更改数据库中的用户密码,从而实现账户劫持。
  • 执行任意数据库操作,如读取、修改或删除数据。
  • 执行系统命令(若 MongoDB 配置允许)。

2.2 CouchDB NoSQL 注入原理

CouchDB 采用 HTTP/JSON 作为其 API,并且其查询视图(view)是基于 JavaScript 的。注入点通常发生在 Map/Reduce 视图的 JavaScript 函数中。

攻击载荷示例 (CouchDB JS 注入):

假设后端动态地构建一个 Map 函数:

1
2
3
4
// 后端伪代码 (不安全的实现)
var userFilter = req.body.filter; // 用户输入
var mapFunction = "function(doc){ if(doc.type == 'user' && doc.name == '" + userFilter + "') { emit(doc._id, doc); } }";
db.view("design_doc", "user_access", { map: mapFunction });

攻击者输入 userFilter 为:

1
'; var _ = require('lodash'); _.forEach([1,2,3], print); return true; //

攻击效果:

  • 与 MongoDB 类似,可能导致拒绝服务或任意 JavaScript 代码执行。
  • 暴露数据库内部结构或敏感数据。

三、NoSQL 注入的危害

NoSQL 注入的危害与传统 SQL 注入类似,甚至可能更广,因为它可能直接导致服务器端代码执行:

  • 数据泄露:获取未授权的敏感数据,包括用户凭证、个人信息、业务数据等。
  • 认证绕过:通过修改查询逻辑,在没有正确凭证的情况下登录系统,获取管理员权限。
  • 数据篡改或删除:修改、插入或删除数据库中的任意数据。
  • 拒绝服务 (DoS):通过注入复杂的查询或无限循环的 JavaScript 代码,耗尽数据库或应用程序服务器资源。
  • 远程代码执行 (RCE):在某些 NoSQL 数据库(如 MongoDB 的 $where 或 CouchDB 的视图)中,如果配置不当,可以直接执行操作系统命令或任意 JavaScript 代码,对整个服务器造成严重威胁。
  • 权限提升:获取数据库管理员权限。

四、防御措施

防范 NoSQL 注入的核心原则与 SQL 注入相同:永不信任用户输入

  1. 参数化查询 (Prepared Statements / Type-Safe APIs)

    • 这是最有效和推荐的防御机制。大多数 NoSQL 驱动程序都提供了参数化查询的 API。避免手动拼接查询字符串。
    • 例如,在 MongoDB Node.js 驱动中:
      1
      2
      3
      4
      5
      // 安全的参数化查询
      db.collection('users').findOne({
      username: req.body.username,
      password: req.body.password
      }, function(err, user) { /* ... */ });
      这种方法会清晰地分隔数据和查询逻辑,驱动程序会负责正确地转义和处理数据,防止注入。
  2. 严格的输入验证 (Input Validation)

    • 白名单验证:只允许输入符合预期的字符、格式和长度。例如,用户名只允许字母数字,密码有最小长度和字符组合要求。
    • 黑名单过滤 (不推荐作为主防御):尝试过滤潜在恶意字符或关键字,但这很难做到完全覆盖,容易被绕过。
    • 数据类型检查:确保用户输入的数据类型与预期一致(例如,如果期望整数,就只接受整数)。
  3. 禁用或限制敏感功能

    • 禁用 JavaScript 执行:对于 MongoDB,除非绝对必要,否则应禁用 $where$eval 等能够执行服务器端 JavaScript 的操作。如果必须使用,确保其输入是严格控制和验证的。
    • 最小权限原则:应用程序连接数据库时,应使用权限受限的用户账户,只授予执行其业务功能所需的最低权限。
    • 安全配置:确保 NoSQL 数据库本身配置安全,关闭不必要的端口和服务,启用认证和授权。
  4. 避免在查询中使用用户提供的表达式

    • 如果业务逻辑需要复杂的查询,尽量在服务器端硬编码这些查询逻辑,而不是允许用户动态构建它们。
    • 对于排序、过滤等功能,提供预定义的选项让用户选择,而不是让用户直接输入字段名或运算符。
  5. 错误信息管理

    • 不要在错误消息中暴露数据库的内部结构、错误堆栈信息或其他敏感数据,这会帮助攻击者分析数据库类型和攻击方式。
  6. 代码审查和安全测试

    • 定期进行代码审查,特别关注与数据库交互的代码。
    • 进行渗透测试和模糊测试(Fuzz Testing),尝试各种异常输入来发现潜在的注入点。

五、总结

NoSQL 注入是现代 Web 应用中一个日益增长的安全威胁。尽管其攻击载荷和技术细节与传统 SQL 注入有所不同,但核心原因都是应用程序未能正确地处理用户输入。

防御 NoSQL 注入的关键在于:始终使用数据库驱动提供的参数化查询 API,结合严格的输入验证,并配置 NoSQL 数据库以禁用或限制高风险功能。开发者必须理解所使用的 NoSQL 数据库的查询语言特性及其安全隐患,才能有效地构建健壮、安全的应用程序。