NoSQL注入

发布时间:2026/7/14 22:14:55

NoSQL注入 目录简介核心模型注入语法注入运算符注入利用语法注入提取数据识别字段名称利用运算符注入提取数据提取字段名称基于时序的注入防止NoSQL注入简介NoSQL 数据库不使用传统的关系型表格存储数据而是专为处理大规模非结构化或半结构化数据而设计。相比 SQL 数据库它们在关系约束和一致性检查上较少但在可扩展性、灵活性和性能方面表现突出。与 SQL 数据库不同NoSQL 没有统一的查询语言标准而是根据具体数据库类型使用不同的查询方式如 JSON、XML 或自定义 API。核心模型文档型 (Document stores)以 JSON/BSON/XML 等格式存储半结构化数据。例如MongoDB。键值型 (Key-value stores)通过唯一键检索值非常高效。例如Redis。宽列型 (Wide-column stores)按列族组织相关数据适合大规模数据处理。例如Cassandra。图形数据库 (Graph databases)使用节点实体和边关系来存储数据专注于关联分析。例如Neo4j。注入语法注入通过注入特殊字符打破原有的查询语法从而植入恶意的逻辑判断。1.模糊测试 (Fuzzing)使用一系列特殊字符如,,{,;,\xYZ等尝试打破查询语法。如果服务器返回错误或者页面行为发生变化例如返回了不同的数据量则说明输入可能未经过滤。注在测试漏洞时不能机械地照搬某个 Payload必须根据注入载体在哪里输入数据来对 Payload 进行“包装”。注入场景需要做的操作最终发送的 PayloadURL 参数进行 URL 编码category%22%60%7b%0d%0a%3b%24Foo%7d%0d%0a%24Foo%20%5cxYZ%00JSON 请求体进行 JSON 转义\{\r;$Foo}\n$Foo \xYZ\u00002.确认阶段发送两个请求分别注入“假”条件例如 0 和“真”条件例如 1 。如果两者的响应行为不同说明你成功地在服务器端修改了查询逻辑而非仅仅是引发了应用层的逻辑异常。3.利用阶段利用始终为真的布尔逻辑如|| 11使得查询条件恒成立。通过强制条件恒真可以检索出所有符合或不符合原始条件的数据甚至绕过登录验证或获取隐藏类别的信息。也可以通过在payload后加空字符MongoDB可能会忽略空字符之后的所有字符。运算符注入运算符注入是利用数据库原生提供的“功能性”指令来修改查询逻辑。NoSQL 数据库特别是 MongoDB允许查询参数不仅仅是字符串还可以是包含运算符的对象。MongoDB查询运算符的示例包括$where- 匹配满足JavaScript表达式的文档。$ne- 匹配所有不等于指定值的值。$in- 匹配数组中指定的所有值。$regex- 选择值与指定正则表达式匹配的文档。例常规查询{username: admin}—— 匹配用户名为 admin 的用户。注入查询{username: {$ne: invalid}}—— 匹配用户名“不等于 (not equal)” invalid 的用户。很多应用最初接收的是 URL 编码的数据但后端处理时会将其转为 JSON 对象。需要掌握这种“格式转换”的攻击技巧如果URL注入失败转成JSON试试将请求方法从GET转换为POST将头Content-Type改为application/json在消息正文中添加 JSON 代码。在 JSON 中注入查询操作符。URL 编码形式username[$ne]invalidJSON 结构形式{username: {$ne: invalid}}利用语法注入提取数据在许多 NoSQL 数据库中一些查询运算符或函数可以运行有限的 JavaScript 代码例如 MongoDB 的运算符和函数。数据库可能会将JavaScript作为查询的一部分进行评估因此可能可以使用JavaScript函数从数据库中提取数据。原始代码{$where:this.username 用户输入}当输入admin this.password[0] a || ab时后端查询变成了this.username admin this.password[0] a || ab this.password[0] a检查该用户的密码第一个字符是不是 a。|| ab闭合掉原始代码中最后的那个单引号使其不报错因为ab永远为假不影响前面的结果。如果猜对了this.password[0] a为真页面会显示admin的信息。如果猜错了 整个条件为假页面不会显示任何信息。相当于SQL注入中的布尔盲注可以先用这个payload判断密码中是否有数字admin this.password.match(/\d/) || ab用bp的intruder爆破时注意编码问题如果已经用ctrlU编码过了在intruder就取消勾选URL-encode识别字段名称因为 NoSQL 不像 SQL 有固定的TABLE结构所以需要先识别集合中的有效字段才能用 JavaScript 注入提取数据。字段探测利用了 JavaScript 的对象属性访问特性。其核心原理是如果字段存在this.fieldname会返回一个值或空字符串逻辑表达式可以正常计算。如果字段不存在this.fieldname在 JavaScript 中是undefined。响应差异基准请求肯定存在的字段admin this.username!返回admin的信息因为username肯定存在且不为空。对照请求肯定不存在的字段admin this.foo!报错或不返回数据因为this.foo是undefined。测试请求想探测的字段admin this.password!如果它的响应和 1 一样说明password字段存在如果和 2 一样说明字段不存在。做法一个方法是将this.§password§!中的字段名设为变量选取包含常见字段名的字典进行爆破看哪个存在。依靠上面的差异另一个方法是逐个提取字符利用$where或运算符注入把字段名本身猜出来。例如this.keys()[0][0] p探测第一个字段名的第一个字母是不是 p。利用运算符注入提取数据在正常的 JSON 请求中额外塞进一个数据库本不期待的运算符。发送{username:wiener, password:peter, $where:1}发送{username:wiener, password:peter, $where:0}如果上述两次结果不同说明后端直接接收了传入的整个 JSON 对象并传给了数据库数据库执行了强行注入的$where脚本。$where允许在执行数据库查询时执行JS代码提取字段名称可以使用该payload探测$where:Object.keys(this)[0].match(^.{0}a.*)Object.keys(this)[0]这是一个 JavaScript 函数它会把当前查询对象里的所有“键”字段名变成一个数组。例如[username, password, role]。这里取数组里第一个字段名.match(^.{0}a.\*)利用正则表达式探测该字段名的第一个字符。^.{0}a的意思是从开头数 0 个字符紧接着是字母a。如果想探测第二个字符就改成^.{1}b。对应的这个bp实验没做出来利用{password: {$ne: invalid}}发现虽然没有登录但响应也会比较特殊可以利用盲注。之后添加了$where参数发现其值为0时和为1时响应不同说明该参数可利用。可以爆破字段名$where:Object.keys(this)[0].match(^.{0}a.*)表示第一个字段的第一个字符是否为a可以得到四个参数idusernamepasswordemail本题要修改密码才算成功$where:this.email.match(^.{§0§}§a§.*)拿到了carlos的email为carloscarlosmontoya但是username参数和email参数都试了在get请求里没反应在post请求里让去查看邮件获得修改密码的链接。而且用爆破出来的carlos的密码登也登不进去怀疑人生很多现代 NoSQL 环境会禁用 JavaScript 执行即禁用$where但几乎没有数据库会禁用正则表达式。{username:admin, password:{$regex:^a.*}}^a表示以a开头。如果登录成功或响应不同就知道第一位是a。以此类推试出完整的字符串基于时序的注入发现页面无论怎么输入返回的内容、长度、状态码都一模一样即“真假逻辑”无法直接观察时间盲注让数据库在满足特定条件时响应延迟一会例如{$where: sleep(5000)}死循环模拟法有些数据库环境为了安全禁用了原生的sleep()函数。这时候攻击者会写一个死循环让 CPU 疯狂计算直到 5 秒钟结束。admin function(x){ var waitTill new Date(new Date().getTime() 5000); //获取当前时间再加上 5000 毫秒即 5 秒后的时间点存为 waitTill。 while((x.password[0]a) waitTill new Date()){}; //前边是注入的判断后边是判断是否没到5秒后。如果两个条件都满足就执行空语句 {}即什么都不做原地转圈 }(this) //this 代表当前查询到的那条数据库记录原生函数法admin function(x){ if(x.password[0]a){ sleep(5000) }; }(this) 防止NoSQL注入对用户输入进行净化和验证白名单机制比如用户名只允许字母和数字。如果用户输入里包含$、、{等特殊字符直接拒绝请求而不是尝试去修补它。确保预期的字符串确实是字符串而不是一个包含运算符的对象。参数化查询 最有效错误做法拼接{ username: input }正确做法参数化 使用数据库驱动提供的 API将数据作为参数传递。db.collection.find({ username: input })此时即使input是{$ne: }数据库也会把它当成一个普通的字符串去搜索而不会把它当作一个逻辑运算符来执行。针对运算符注入的防御在处理 JSON 输入时很多后端框架会自动把{$ne: }转换成对象。在将数据传给数据库之前检查 JSON 的Key键。如果发现 Key 是以$开头的这通常意味着它是 MongoDB 的运算符直接过滤掉。架构层面的加固禁用危险功能 如果应用不需要复杂的逻辑直接在数据库配置里禁用$where运算符。最小权限原则 数据库账号不应该有权访问Object.keys或者执行系统命令。

相关新闻