MongoDb学习

快速了解MongoDB

基础概念

  MongoDB 是由C++语言编写的,是一个基于分布式文件存储的开源数据库系统。MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成。MongoDB 文档类似于JSON对象。字段值可以包含其他文档,数组及文档数组。
image.png
image.png

常用MongoDB语句分析

  通常使用findone(),find().pretty()find()函数来对文档进行查询,find() 方法以非结构化的方式来显示所有文档,加上pretty()方法以格式化的方式来显示所有文档(实际上就是显示起来更易读),findone()只返回一个文档。MongoDB 中存储的文档必须有一个_id键,这个键的值可以是任何类型的,默认是个 ObjectId 对象,可以把它当作关系型数据库中的唯一主键吧。

语句分析

  首先要知道几个比较常用的比较运算符,对于这种带$的标识符,在MongoDB中我们都可以把它们当作是一种表达式,把他们放在花括号{}中就行。
image.png
  下面就简单看看MongoDB PHP扩展与Mongodb shell下以及相应Mysql的相应查询语句对比,这样能更快让我们学会和理解MongoDB的相关语法。

<?php
echo extension_loaded("mongo") ? "loaded\n" : "not loaded\n";
$m = new MongoClient("mongodb://localhost:27017"); // 连接
$db = $m->test->tr1ple;
$cursor = $db->find();
foreach ($cursor as $document) {
    var_dump($document). "\n";
}
?>

  内置数据如下
image.png
  例1.
  MongoDB shell:db.tr1ple.find({num:{$gt:2}})
image.png
  MongoDB php:$db->find(['num'=> ['$gt'=>2]]);
image.png
  Mysql: select * from tr1ple where num>2
  例2.
  对于OR和AND这种运算符我们既可以对单个变量进行多个条件限制,也可以针对多个变量进行条件限制
  and->单个变量多条件限制:
  MongoDB shell:db.tr1ple.find({'num':{"$gt":2,"$lt":4}})
image.png
  MongoDB php:$db->find(['num'=>['$gt'=>2,'$lt'=>4]]);
image.png
  Mysql: select * from tr1ple where num>2 and num <4
  例3.
  and->多个变量条件限制:
  MongoDB shell:db.tr1ple.find({'num':{"$gt":2},'name':'test_3'})
image.png
  MongoDB php:$db->find(["num"=>['$gt'=>2],"name"=>"test_3"])
image.png
  Mysql: select * from tr1ple where num>2 and name="test_3"
  例4.
  而对于or连接,语法可能稍有不同:
  MongoDB shell:db.tr1ple.find({$or:[{"name":"test_1"},{"name":"test_2"}]})
image.png
  MongoDB php:$db->find(['$or'=>[["name"=>"test_1"],["name"=>"test_2"]]]);
image.png
  Mysql: select * from tr1ple where name=“test_1“ or name = "test_2"
  例5.
  当然也可以将and和or结合进行查询:
  MongoDB shell:db.tr1ple.find({"num":{"$gt":1},$or:[{"name":"test_1"},{"name":"test_2"}]})
  MongoDB php:$db->find(["num"=>['$gt'=>1],'$or'=>[["name"=>"test_1"],["name"=>"test_2"]]]);
  Mysql: select * from tr1ple where num>1 and (name="test_1" or name="test_2")
  最后还要再了解一点关于MongoDB的管道的概念,同样我们可以类比Linux上的管道概念来进行理解
  MongoDB中聚合(aggregate)主要用于处理数据(诸如统计平均值,求和等),并返回计算后的数据结果,有点类似sql语句中的count(*)。MongoDB的管道将MongoDB文档在一个管道处理完毕后将结果传递给下一个管道处理。管道操作是可以重复的,以下是几个管道相关操作中的表达式:
  $project:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档,可以把它当作指定域,因为默认find()函数将范围集合中所有的域的数据(ps:这个域前面表里说过了可以把它当作SQL里面的字段来理解)。
  $limit:用来限制MongoDB聚合管道返回的文档数。
  $group:将集合中的文档分组,可用于统计结果。
  $sort:将输入文档排序后输出。(使用1和-1来指定排序的方式,其中1为升序排列,而-1是用于降序排列)

NopeSQL解题

  题目环境还没关,地址:http://173.199.118.226/index.php?
  web题,拿到题,先扫目录,有.git泄露,githack走起,就能得到index.php的源码了。
image.png
image.png
  接下来就是代码审计,其中登陆验证的逻辑在这里
image.png
image.png
  将post的用户名和密码直接带入find0ne()函数进行查找,所以明显在这里存在注入,我们可以看到query为'{"username": "'.$username.'", "password": "'.$password.'"}',因此我们可以很容易闭合它,用户名随意,比如username=123password=111","password":{"$ne":"tr1ple"},"username":"admin,这里相当于我们既闭合了后面的双引号,又对username和password的值进行了覆盖,那么findOne最终相当于执行:
db.news.findOne({"username":"123","password:"111",username":"admin","password":{"$ne":"tr1ple"}}),这样自然会返回admin的那条记录(ps:这里$ne表达式即不等于)。
  在本地测试两次赋值查询结果如下图:
image.png
  index.php未认证情况下显示的只有news这个集合的文档title域
image.png
  所以注入password进行绕过
image.png
image.png
  登陆成功,接下来有两个选项供选择,根据题目意思也就是根据我们所选择的域用来进行分组,根据题目逻辑,也就是根据我们提供的filter参数来进行分组:
image.png
image.png
image.png
  可以明显看到当选择$category当做分组标准时,可以看到输出中出现了flags,那么很明显以$category域为分组标准是接下来解题的目标,回去看看这里进行查询的逻辑:
image.png
  这里就用到了刚才我们上面所说的MongoDB聚合管道,这里可以理解成先用$pipeline所定义的条件过滤一次,然后再用$filter所定义的条件过滤一次,最终得到数据。
  先看看$pipeline的查询逻辑,可以理解为类比SQL:
  select category,count(*) from news group by category desc limit5
image.png
  然后再和$filter定义的条件进行过滤,这里用到了$project,也就是我们通过它可以修改输入文档的结构,我们的可控变量$filter也在这里,接下来用php的array_merge()函数首先将数组$pipeline和数组$filter进行合并,然后使用mongodb的聚合函数aggregate来获得最终的数据集合。而重点是这里的category实际上是可以替换的,而替换的内容又是我们可以控制的,而通过$project我们可以修改返回的域,这里主键可以是前面说的category,也可以是publicity,当然也可以是我们想要恶意构造的值,若为publicity,即为:
  select publicity,count(*) from news group by publicity desc limit5
image.png
  当然因为$filter是可以控制的,并且我们知道category里面有flags这个键,并且之前通过$news['title'],我们知道集合news里面的文档存在域title,那么flags目录对应的文档也有title域,所以我们直接通过if判断来得到当前category为flags时的title,这里要用到if:then:else:判断块,还是看官方文档吧(https://docs.mongodb.com/manual/reference/operator/aggregation/project/index.html), 其中有解释对$project的用法,有个例子里面就有对if的使用:
image.png
  而通过mongodb的官方对$project的例子,我们可以看到可以使用$cond来进行if条件判断,此时我们在filter变量处注入一个$cond判断条件,参考文档中例子的写法,我们要注入的filter肯定是一个数组,其只有一个键$cond,而键值为由if、then、else构成的一个数组,并且if条件对应的键值也是一个二维数组,因为前面说了要用到表达式就要把它放在花括号里,那么这里用到$eq,肯定要把它放在一个数组里,而键$eq的键值又由等号两边的值构成一个数组,数组的内容即flags和category,(即当category为flags时)。接下来是then,根据示例代码我们之前已经可以知道要查的集合是news,所以我们猜测每一条记录都有一个titile值,那我们是不是可以尝试先找到flags category所对应的title的值,所以构造payload:
  filter[$cond][if][$eq][]=flags&filter[$cond][if][$eq][]=$category&filter[$cond][then]=$title&filter[$cond][else]=$category
  其中else我们可以任意构造即可
image.png
  可以由返回的信息看到这里明显返回的有一个提示,this is a flag text,所以此时我们猜测这里是提示我们flag是文本索引的,所以通过$text我们可以获得category为flags的文本(即flag),所以构造:
filter[$cond][if][$eq][]=flags&filter[$cond][if][$eq][]=$category&filter[$cond][then]=$text&filter[$cond][else]=$category
image.png
  当然还有其它的payload可以用,impakho师傅博客里用的payload是:
filter[$cond][if][$eq][][$strLenBytes]=$title&filter[$cond][if][$eq][][$toInt]=19&filter[$cond][then]=$text&filter[$cond][else]=12
  其实原理都差不多,这里是直接利用给的title提示来拿到flag,this is a flag text,刚好19个字符长度,又因为由上面的图已经知道以title为分组依据时this is a flag text返回 1 news,即它的文本域肯定就是flag了,只要结合一下$strLenBytes和$toInt表达式判断一下当前title的长度即可。

总结

  说实话MongoDB的语句和Mysql比起来,刚开始看还是有点绕,可能是我太菜了==。这个题目还是不错的,遇到不会的知识点,查官方文档的确是事半功倍的好办法,当然这种类比着理解的方法我觉得也挺有用。

参考

https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/
https://blog.csdn.net/newbie_907486852/article/details/82502815
https://www.runoob.com/mongodb/mongodb-databases-documents-collections.html
https://impakho.com/post/cybrics-ctf-2019-writeup#toc-7

posted @ 2019-08-06 16:25  tr1ple  阅读(350)  评论(0编辑  收藏  举报