最近遇到了两道nosql注入了,和传统的sql注入不同,nosql注入指的是not noly sql,查询语句更加的灵活
先从tryhackme的靶场中学习一下什么是nosql注入
What is nosql
MongoDB
MongoDB是一个流行的开源文档型数据库,它使用类似 JSON 的文档模型存储数据,这使得数据存储变得非常灵活。
MongoDB 是一个基于文档的 NoSQL 数据库,由 MongoDB Inc. 开发。
MongoDB 旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。
MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。
它的信息不是储存在表上,而是储存在文档上,像一个字典一样
{"_id" : ObjectId("5f077332de2cdf808d26cd74")"username" : "lphillips", "first_name" : "Logan", "last_name" : "Phillips", "age" : "65", "email" : "lphillips@example.com" }
MongoDB允许我们将具有类似功能的多个文档分组到更高的层次结构中,称为集合,以用于组织目的。集合等同于关系数据库中的表。继续我们的 HR 示例,所有员工的文档都将方便地分组到一个名为“人员”的集合中,如下图所示。
多个集合最终被分组到数据库中,这是MongoDB中最高的层次结构元素。在关系数据库中,数据库概念将表组合在一起。在MongoDB中,它对相关的集合进行分组。
Querying the Database
与任何数据库一样,使用特殊语言从数据库中检索信息。正如关系数据库使用 SQL 的某种变体一样,非关系数据库(如 MongoDB)也使用 NoSQL。一般而言,NoSQL 是指查询非 SQL 数据库的任何方式,这意味着它可能因所使用的数据库而异。
使用 MongoDB 时,查询使用结构化关联数组,其中包含要满足的条件组以过滤信息。这些过滤器提供与 SQL 中的 WHERE 子句类似的功能,并在需要时为操作员提供构建复杂查询的能力。
MongoDB Operator Reference这个是一下操作符
为了更好地理解 NoSQL 查询,我们首先假设我们有一个数据库,其中包含包含以下三个文档的人员集合:
如果我们想要构建一个过滤器,以便仅检索last_name为“Sandler”的文档,我们的过滤器将如下所示:
['last_name' => 'Sandler']
因此,此查询仅检索第二个文档。
如果我们想要过滤性别为男性且last_name为菲利普斯的文档,我们将有以下过滤器:
['gender' => 'male', 'last_name' => 'Phillips']
这将仅返回第一个文档。
如果我们想检索年龄小于 50 岁的所有文档,我们可以使用以下过滤器:
['age' => ['$lt'=>'50']]
NoSQL Injection
Injection is Injection
虽然考虑 NoSQL 注入可能看起来很复杂,但当我们将注入攻击归结为它们的本质时,我们可以理解 SQL 注入和 NoSQL 注入之间的相似之处。
注入攻击的根本原因是,将不受信任的用户输入不正确地串联到命令中,攻击者可以更改命令本身。使用 SQL 注入时,最常见的方法是注入单引号或双引号,这会终止当前数据连接并允许攻击者修改查询。同样的方法也适用于 NoSQL 注入。如果将不受信任的用户输入直接添加到查询中,则我们有机会修改查询本身。但是,使用 NoSQL 注入,即使我们无法转义当前查询,我们仍然有机会操纵查询本身。因此,有两种主要类型的 NoSQL 注入:
语法注入 - 这类似于 SQL 注入,我们有能力脱离查询并注入我们自己的有效负载。与 SQL 注入的主要区别在于用于执行注入攻击的语法。
运算符注入 - 即使我们无法脱离查询,我们也可能会注入一个 NoSQL 查询运算符来操纵查询的行为,从而使我们能够上演身份验证绕过等攻击。
How to Inject NoSQL
在查看 NoSQL 过滤器的构建方式时,绕过它们来注入任何有效负载可能看起来是不可能的,因为它们依赖于创建结构化数组。与SQL注入不同,在SQL注入中,查询通常是通过简单的字符串连接构建的,而NoSQL查询需要嵌套的关联数组。从攻击者的角度来看,这意味着要注入 NoSQL,必须能够将数组注入到应用程序中。
幸运的是,许多服务器端编程语言允许在 HTTP 请求的查询字符串上使用特殊语法来传递数组变量。出于此示例的目的,让我们重点介绍以下用 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”的任何文档。这可能会返回登录集合中的所有文档。因此,应用程序将假定已执行正确的登录,并让我们以与从数据库获取的第一个文档相对应的用户权限进入应用程序.
Operator Injection: Bypassing the Login Screen
我们用不等于的方式来显示数据
Operator Injection: Logging in as Other Users
Logging in as Other Users
我们已经设法绕过了应用程序的登录屏幕,但是使用前一种技术,我们只能作为数据库返回的第一个用户登录。通过使用 $nin 运算符,我们将修改我们的有效载荷,以便我们可以控制我们想要获取的用户。
首先,$nin运算符允许我们通过指定条件来创建过滤器,其中所需文档具有某个字段,而不是在值列表中。因此,如果我们想以除用户 admin 以外的任何用户身份登录,我们可以将有效负载修改为如下所示:
这将转换为具有以下结构的过滤器:
['username'=>['$nin'=>['admin'] ], 'password'=>['$ne'=>'aweasdf']]
它告诉数据库返回用户名不是 admin 且密码不是 aweasdf 的任何用户。因此,我们现在被授予对另一个用户帐户的访问权限。
请注意,$nin运算符会收到要忽略的值列表。我们可以通过调整有效载荷来继续扩展列表,如下所示:
这将导致类似这样的过滤器:
['username'=>['$nin'=>['admin', 'jude'] ], 'password'=>['$ne'=>'aweasdf']]
可以根据需要多次重复此操作,直到我们获得对所有可用帐户的访问权限。
Operator Injection: Extracting Users' Passwords
Extracting Users' Passwords
此时,我们可以访问应用程序中的所有帐户。但是,尝试提取正在使用的实际密码非常重要,因为它们可能会在其他服务中重复使用。为了实现这一点,我们将滥用$regex操作员向服务器提出一系列问题,这些问题使我们能够通过类似于玩游戏刽子手的过程来恢复密码。
首先,让我们以之前发现的一位用户为例,尝试猜测他的密码长度。我们将使用以下有效负载来执行此操作:
请注意,我们正在询问数据库是否存在用户名为 admin 且密码与正则表达式匹配的用户: ^.{7}$
。这基本上表示长度为 7 的通配符字。由于服务器以登录错误响应,因此我们知道用户 admin 的密码长度不是 7。经过一番反复试验,我们终于得出了正确的答案:
我们现在知道用户 admin 的密码长度为 5。现在为了弄清楚实际内容,我们修改我们的有效载荷,如下所示:
我们现在正在处理长度为 5(单个字母 c 加 4 个点)的正则表达式,匹配发现的密码长度,并询问管理员的密码是否与正则表达式 ^c....$
匹配,这意味着它以小写字母 c 开头,后跟任何 4 个字符。由于服务器响应是无效登录,我们现在知道密码的第一个字母不能是“c”。我们继续遍历所有可用字符,直到我们从服务器获得成功的响应:
这确认了管理员密码的第一个字母是“a”。可以对其他字母重复相同的过程,直到恢复完整的密码。如果需要,也可以对其他用户重复此操作。