xinyi709

Loading...

NOSQL注入

以下是我根据在THM网站的学习,写下的笔记

NOSQL介绍

MongoDB

就像MySQL、MariaDB或PostgreSQL一样,MongoDB是另一个数据库,可以以有序的方式存储数据。MongoDB允许以快速和结构化的形式检索数据子集。它的工作原理与其他数据库相似,只是它的信息不是存储在表上,而是存储在文档中。

可以把这些文档想象成一个简单的字典结构,其中存储键值对。在某种程度上,它们与传统关系数据库上的记录非常相似,但信息的存储方式不同。

例如,在人力资源部门创建一个Web应用程序,存储基本的员工信息。在MongoDB中,会为每个员工创建一个文档,其格式如下:

{"_id" : ObjectId("5f077332de2cdf808d26cd74"), "username" :  "lphillips", "first_name" : "Logan", "last_name" : "Phillips", "age" :  "65", "email" : "lphillips@example.com" }

由此可见,MongoDB中的文档存储在一个关联数组中,其中包含任意数量的字段。

6093e17fa004d20049b6933e-1719679544440

多个集合最终被分组到数据库中,这是MongoDB中最高的分层元素。所以这里的集合就相当于关系型数据库中的

6093e17fa004d20049b6933e-1719679596244

查询数据库

使用MongoDB时,查询要使用结构化关联数组,其中包含要满足的一组标准来过滤信息。这些过滤器提供了与SQL中WHERE子句类似的功能,并在需要时为操作员提供了构建复杂查询的能力。

例如,这里有一个包含以下三个文档的人的集合数据库:

6093e17fa004d20049b6933e-1719679617512

如果我们只想检索 last_name 为 "Sandler" 的文档,那么构造的语句如下:

['last_name' => 'Sandler']

因此,此查询只检索第二个文档。

如果我们想过滤性别是男性的文档,并且 last_name 是 Phillips,那么构造的语句如下:

['gender' => 'male', 'last_name' => 'Phillips']

这只会返回第一份文档。

如果我们想检索年龄小于50的所有文档,我们可以使用以下语句:

['age' => ['$lt'=>'50']]

这将返回第二和第三份文档。

注意这里,在刚才的嵌套数组中使用了 $lt 运算操作符。我们还可以通过其它的运算操作符进行更复杂的查询。

Name Description
$eq 匹配等于指定值的值。
$ne 匹配所有不等于指定值的值
$gt 匹配大于指定值的值。
$gte 匹配大于等于指定值的值。
$lt 匹配小于指定值的值
$lte 匹配小于等于指定值的值
$in 匹配数组中指定的任意值
$nin 匹配不在指定的数组中的值

NOsql注入

使用SQL注入,最常见的方法是注入单个或双引号,终止当前数据连接并允许攻击者修改查询,同样的方法也适用于NoSQL注入。使用NoSQL注入,即使我们无法逃避当前的查询,我们仍然有机会操纵查询本身。因此,NoSQL注入主要有两种类型:

语法注入 — 这类似于SQL注入,我们有能力突破查询并注入自己的payload。SQL 注入的关键区别在于用于执行注入攻击的语法。
运算符注入 — 即使我们不能中断查询,我们也可能注入一个 NoSQL 查询运算符来操纵查询的行为,从而允许我们进行身份验证绕过等攻击。

举例如下:

以下是用php写的一个简单的登录界面

<?php
$con = new MongoDB\Driver\Manager("mongodb://localhost:27017");


if(isset($_POST) && isset($_POST['user']) && isset($_POST['pass'])){
        $user = $_POST['user'];
        $pass = $_POST['pass'];

        $q = new MongoDB\Driver\Query(['username'=>$user, 'password'=>$pass]);
        $record = $con->executeQuery('myapp.login', $q );
        $record = iterator_to_array($record);

        if(sizeof($record)>0){
                $usr = $record[0];

                session_start();
                $_SESSION['loggedin'] = true;
                $_SESSION['uid'] = $usr->username;

                header('Location: /sekr3tPl4ce.php');
                die();
        }
}
header('Location: /?err=1');

?>

Web应用程序正在使用“myapp”数据库和“login”集合向MongoDB进行查询,请求任何通过查询语句['username'=>$user,'password'=>$pass]的文档,其中$user$pass直接从HTTP POST的参数中获得。

如果以某种方式,我们可以向 $user$pass 发送一个数组,并用以下内容 :

$user = ['$ne'=>'xxxx'] 

$pass = ['$ne'=>'yyyy'] 

那么生成的查询语句最终会看起来像这样

['username'=>['$ne'=>'xxxx'], 'password'=>['$ne'=>'yyyy']]

那就可以使数据库返回任何用户名不等于“xxxx”且密码不等于“yyyy”的文档。这就可能会返回登录集合中的所有文档。

不过有一个问题是,如何将数组作为 POST HTTP 请求的一部分传递?

事实证明,PHP 和许多其他语言允许在 POST Request Body 上使用以下符号传递数组:

user[$ne]=xxxx&pass[$ne]=yyyy

绕过登录界面

访问靶机的web登录界面,随便输入用户名密码

6093e17fa004d20049b6933e-1719679685897

bp抓包,得到如下结果

6093e17fa004d20049b6933e-1719679753604

然后改成数组传参

6093e17fa004d20049b6933e-1719679773572

成功登录

屏幕截图 2025-07-15 123359

以其他用户身份登录

刚才已经设法绕过应用程序的登录,但使用刚才的方法,我们只能作为数据库返回的第一个用户登录。通过使用$nin运算操作符,我们可以控制我们想要获得的用户。

$nin 运算操作符是匹配不在指定数组中的值,因此,如果想以除用户管理员以外的任何用户身份登录,可以修改payload如下所示:

['username'=>['$nin'=>['admin'] ], 'password'=>['$ne'=>'aweasdf']]

它告诉数据库返回任何用户,用户名不是管理员,密码不是 aweasdf。这样就有了对其他用户帐户的访问权限。

我们可以继续拓展指定数组里的值

6093e17fa004d20049b6933e-1719679886213

这会导致这样的查询语句:

['username'=>['$nin'=>['admin', 'jude'] ], 'password'=>['$ne'=>'aweasdf']]

根据需要重复多次,就可以访问所有可用的帐户。

提取用户密码

这里要使用$regex(这个用于正则匹配)

构造以下payload:

6093e17fa004d20049b6933e-1719679978838

结果返回报错了,看来用户名密码长度不是7

经过尝试,得出了正确的长度

屏幕截图 2025-07-16 092124

现在我们知道用户管理员的密码长度是5。为了弄清楚实际内容,修改payload如下:

6093e17fa004d20049b6933e-1719679996762

使用长度为5的正则(单个字母c加4个点),匹配发现的密码长度,并询问管理员的密码是否与正则^c....$相匹配,这个正则的含义是:它以小写c开头,后面是任意4个字符。由于服务器响应是无效的登录,我们现在知道密码的第一个字母不能是“c”。我们继续遍历所有可用字符,直到我们从服务器获得成功响应:

6093e17fa004d20049b6933e-1719680018918

这证实了管理员密码的第一个字母是'a'。对于其他字符,可以重复相同的过程,直到恢复出完整的密码。

寻找语法注入

在注入界面,先输入一个单引号' ,发现回显报错,说明有NOSQL注入

屏幕截图 2025-07-16 160247

我们可以看到,用户名变量直接连接到查询字符串,并且在 find 命令中执行 JavaScript 函数,允许我们注入语法。在这种情况下,我们有冗长的错误消息,以指示我们注入是可能的。但是,即使没有冗长的错误消息,我们也可以通过提供虚假和真实条件来测试语法注入,并看到输出不同,如下例所示:

admin' && 0 && 'x
admin' && 1 && 'x

屏幕截图 2025-07-16 160630

现在我们已经确认了语法注入,我们可以利用这个注入点来转储所有电子邮件地址。为此,我们希望确保条件的测试语句始终评估为 true。可以使用'||1||'的pyload来查询到所有的用户信息。

屏幕截图 2025-07-16 160809

posted @ 2026-05-25 17:48  xinyi709  阅读(2)  评论(0)    收藏  举报