NoSQL 注入详解
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 | // 后端 JavaScript (或 Go/Python/Java 驱动的等价实现) |
如果 username 和 password 未经清理直接用于构建查询,攻击者就有了利用的机会。
2.1.1 运算符注入
MongoDB 查询支持丰富的运算符(如 $gt, $lt, $ne, $in, $or, $and 等)。攻击者可以注入这些运算符来改变查询逻辑。
攻击载荷示例 (修改登录逻辑):
假设应用程序的查询是 {"username": username, "password": password}
绕过密码验证:
- 注入
password字段:{"password": {"$ne": null}}(密码不为空即可) 或{"password": {"$gt": ""}}(密码是非空字符串即可)。 - 攻击者输入:
username:adminpassword:{"$ne": null}(作为 JSON 字符串提交)
- 后端拼接后的查询(伪代码):
1
db.users.findOne({ "username": "admin", "password": { "$ne": null } })
- 这个查询现在将匹配所有
_id字段存在且不为null的文档,这通常是所有文档。只要admin用户存在,且其password字段不为null,攻击便成功绕过密码验证。
- 注入
利用
$or运算符 (布尔盲注):- 攻击者输入:
username:adminpassword:$ne null,"or":[{"username":"admin"},{"username":"alice"}]
- 真实攻击通常不会这么直接。更常见的是通过 JSON 结构注入。
- 注入到
username字段:'{"$ne":"admin"}'实际上会查找所有用户名不是admin的用户。 - 如果目标查询是
{ "username": inputUsername, "password": inputPassword } - 攻击者可能构造
username为admin,password为{ "$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 | // 后端伪代码 (不安全的实现) |
攻击者输入 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 | // 后端伪代码 (不安全的实现) |
攻击者输入 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 注入相同:永不信任用户输入。
参数化查询 (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) { /* ... */ });
严格的输入验证 (Input Validation):
- 白名单验证:只允许输入符合预期的字符、格式和长度。例如,用户名只允许字母数字,密码有最小长度和字符组合要求。
- 黑名单过滤 (不推荐作为主防御):尝试过滤潜在恶意字符或关键字,但这很难做到完全覆盖,容易被绕过。
- 数据类型检查:确保用户输入的数据类型与预期一致(例如,如果期望整数,就只接受整数)。
禁用或限制敏感功能:
- 禁用 JavaScript 执行:对于 MongoDB,除非绝对必要,否则应禁用
$where和$eval等能够执行服务器端 JavaScript 的操作。如果必须使用,确保其输入是严格控制和验证的。 - 最小权限原则:应用程序连接数据库时,应使用权限受限的用户账户,只授予执行其业务功能所需的最低权限。
- 安全配置:确保 NoSQL 数据库本身配置安全,关闭不必要的端口和服务,启用认证和授权。
- 禁用 JavaScript 执行:对于 MongoDB,除非绝对必要,否则应禁用
避免在查询中使用用户提供的表达式:
- 如果业务逻辑需要复杂的查询,尽量在服务器端硬编码这些查询逻辑,而不是允许用户动态构建它们。
- 对于排序、过滤等功能,提供预定义的选项让用户选择,而不是让用户直接输入字段名或运算符。
错误信息管理:
- 不要在错误消息中暴露数据库的内部结构、错误堆栈信息或其他敏感数据,这会帮助攻击者分析数据库类型和攻击方式。
代码审查和安全测试:
- 定期进行代码审查,特别关注与数据库交互的代码。
- 进行渗透测试和模糊测试(Fuzz Testing),尝试各种异常输入来发现潜在的注入点。
五、总结
NoSQL 注入是现代 Web 应用中一个日益增长的安全威胁。尽管其攻击载荷和技术细节与传统 SQL 注入有所不同,但核心原因都是应用程序未能正确地处理用户输入。
防御 NoSQL 注入的关键在于:始终使用数据库驱动提供的参数化查询 API,结合严格的输入验证,并配置 NoSQL 数据库以禁用或限制高风险功能。开发者必须理解所使用的 NoSQL 数据库的查询语言特性及其安全隐患,才能有效地构建健壮、安全的应用程序。
