1. 程式人生 > 實用技巧 >漏洞重溫之檔案上傳(FUZZ)

漏洞重溫之檔案上傳(FUZZ)

檔案上傳FUZZ思路通關upload-labs


Pass-16

黑盒階段

進入第十六關,首先我們能看到,該頁面的上傳點為圖片上傳。

首先,先把對方想的簡單一點,這裡雖然是上傳圖片,但是可能只是前端js驗證,我們只需要先將指令碼字尾改為圖片格式,然後抓包修改後綴,就有可能上傳成功。

上傳失敗,可以得知,該網站對於上傳檔案的驗證存在於後端。

從報錯結果,我們可以得知,該位置採用的是白名單過濾,因為黑名單過濾一般不會告訴你該位置可以上傳的檔案型別。

既然得知該位置使用白名單對字尾進行過濾,那麼我們可以使用的手段就只剩下00截斷。

再由第二個圖片的請求包資訊,我們可以得知請求是以POST請求傳送的。

繞過失敗,該處無法通過00截斷進行繞過。

一般情況下,在測試到該位置的時候,如果我們拿不到原始碼,黑盒測試就可以結束了。

白盒階段

點選關卡檢視原始碼屬性,因為程式碼很長,就不截圖了。程式碼如下。

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])){
    // 獲得上傳檔案的基本資訊,檔名,型別,大小,臨時檔案路徑
    $filename = $_FILES['upload_file']['name'];
    $filetype = $_FILES['upload_file']['type'];
    $tmpname = $_FILES['upload_file']['tmp_name'];

    $target_path=UPLOAD_PATH.'/'.basename($filename);

    // 獲得上傳檔案的副檔名
    $fileext= substr(strrchr($filename,"."),1);

    //判斷檔案字尾與型別,合法才進行上傳操作
    if(($fileext == "jpg") && ($filetype=="image/jpeg")){
        if(move_uploaded_file($tmpname,$target_path)){
            //使用上傳的圖片生成新的圖片
            $im = imagecreatefromjpeg($target_path);

            if($im == false){
                $msg = "該檔案不是jpg格式的圖片!";
                @unlink($target_path);
            }else{
                //給新圖片指定檔名
                srand(time());
                $newfilename = strval(rand()).".jpg";
                //顯示二次渲染後的圖片(使用使用者上傳圖片生成的新圖片)
                $img_path = UPLOAD_PATH.'/'.$newfilename;
                imagejpeg($im,$img_path);
                @unlink($target_path);
                $is_upload = true;
            }
        } else {
            $msg = "上傳出錯!";
        }

    }else if(($fileext == "png") && ($filetype=="image/png")){
        if(move_uploaded_file($tmpname,$target_path)){
            //使用上傳的圖片生成新的圖片
            $im = imagecreatefrompng($target_path);

            if($im == false){
                $msg = "該檔案不是png格式的圖片!";
                @unlink($target_path);
            }else{
                 //給新圖片指定檔名
                srand(time());
                $newfilename = strval(rand()).".png";
                //顯示二次渲染後的圖片(使用使用者上傳圖片生成的新圖片)
                $img_path = UPLOAD_PATH.'/'.$newfilename;
                imagepng($im,$img_path);

                @unlink($target_path);
                $is_upload = true;               
            }
        } else {
            $msg = "上傳出錯!";
        }

    }else if(($fileext == "gif") && ($filetype=="image/gif")){
        if(move_uploaded_file($tmpname,$target_path)){
            //使用上傳的圖片生成新的圖片
            $im = imagecreatefromgif($target_path);
            if($im == false){
                $msg = "該檔案不是gif格式的圖片!";
                @unlink($target_path);
            }else{
                //給新圖片指定檔名
                srand(time());
                $newfilename = strval(rand()).".gif";
                //顯示二次渲染後的圖片(使用使用者上傳圖片生成的新圖片)
                $img_path = UPLOAD_PATH.'/'.$newfilename;
                imagegif($im,$img_path);

                @unlink($target_path);
                $is_upload = true;
            }
        } else {
            $msg = "上傳出錯!";
        }
    }else{
        $msg = "只允許上傳字尾為.jpg|.png|.gif的圖片檔案!";
    }
}

這裡,跟大家說一些可能會有幫助的點。

對於一些漏洞初學者來說,想要吃透一種型別的漏洞,在沒有程式碼或者網路基礎的情況下是很困難的事情,或者說至少在白盒情況下,是很難深入的發現一些問題的。

例如上面的程式碼,如果沒有程式碼基礎,想要理解程式碼的意思不是一個簡單的事情。

但是,說到底,我們看程式碼只是為了知道我們的攻擊為什麼無法實現,或者說看對於該功能點,網站的防禦如何,針對於這些防禦,我們可以採用什麼方法進行繞過。

例如上面的程式碼,我們雖然看不懂具體每行程式碼的含義,但是我們可以根據程式碼塊上面的註釋理解該位置的程式碼究竟發揮了什麼作用。

以此為標準,我們可以看到,在程式碼開始,先是獲取了上傳檔案的字尾,然後判斷後綴是否合法,合法才進行上傳。

然後,網站會使用上傳的圖片商城新的圖片,並且給圖片指定指定檔名。

從這個位置的程式碼,我們可以看到,其中一個判斷流程的報錯資訊我們之前看到過。

也就是說,我們第二次採用00隔斷進行上傳的時候,其實繞過了第一層防禦,因為給出的報錯資訊是在給圖片進行二次渲染的地方的報錯資訊。

如果該網站並沒有給圖片進行二次渲染,可能我們的上傳已經成功了。

但是,從此處,我們也可以瞭解到,哪怕我們該位置上傳的是圖片馬,那麼我們的圖片馬中插入的程式碼也會被二次渲染修改,導致無法正常使用。

既然我們已經清楚該位置上傳失敗是源於圖片二次渲染,那麼就有必要了解一下二次渲染是什麼。

二次渲染:就是根據使用者上傳的圖片,新生成一個圖片,將原始圖片刪除,將新圖片新增到資料庫中。比如一些網站根據使用者上傳的頭像生成大中小不同尺寸的影象。

這裡,也就是說,我們上傳的東西,會被網站作為樣品再生成一個新的,並且將我們原本上傳的檔案刪除。所以,想要繞過二次渲染,我們就需要知道,新生成的圖片,跟我們原本的圖片有什麼不同,在相同處插入我們需要插入的程式碼或許就可以繞過。

首先,我們上傳一個gif圖片,因為圖片的二次渲染,會利用我們的圖片生成一個新圖片,所以我們需要將上傳的圖片下載下來,通過16進位制編輯器對比兩張圖片,可以發現,有一個地方是沒有被修改的,所以我們只需要將php程式碼插入到沒有被修改過的地方,就可以完成圖片馬的上傳。

將程式碼寫到藍色範圍之內,就可以獲得一張可以繞過網站二次渲染的gif圖片。

利用這個圖片再次上傳,獲取路徑。

利用檔案包含訪問該檔案,檢查我們的程式碼是否可以正常執行。

第十六關,通關。

當然,面對圖片二次渲染,針對png和jpg兩種格式,還有兩種不同的應對方法,只是那兩種我自己並沒有成功,所以並沒有在這裡總結。

Pass-17

黑盒階段

第十七關,可以看到,該處還是一個圖片上傳點,貫徹我們的思路,先把對方當成憨批,猜測他的防禦只建立在前端。

跟第一關同樣的思路,通過這裡的報錯資訊,我們可以判斷出,該位置採用白名單過濾。並且,請求包是POST請求,對付白名單過濾,我們可以採用00截斷進行繞過。

可以看到,這裡我們已經上傳成功了,訪問該檔案。

第十七關,通關。

Pass-18

黑盒階段

跟第十七關類似,上傳點依然提示我們這裡是一個圖片上傳點。針對上傳,我的思路就是先把對面當憨批,跟xss一樣,先輸入一個測試馬,成功了,那麼對方的防禦確實憨批,如果沒成功,通過返回的報錯,我們可以大致瞭解他們的防禦情況。

可以看到,這裡的報錯資訊和之前不同,猜測該位置網頁採用的是黑名單過濾。

對於黑名單過濾,我們可以採用的方式就比白名單多了,簡單來說,有以下幾種:

1.上傳特殊可解析字尾

2.配合解析漏洞

3.字尾大小寫繞過

4.點繞過

5.空格繞過

6.::$DATA繞過

7.雙字尾名繞過

8.00截斷

因為這裡我們並無法得知網站針對這些繞過方式做了什麼防護,所以這裡我們只能挨個嘗試。

1.上傳特殊可解析字尾

失敗!

2.配合解析漏洞上傳

失敗!

3.字尾大小寫繞過

失敗!

4.點繞過

失敗!

5.空格繞過

失敗!

6.::$DATA繞過

失敗!

7.雙寫字尾繞過

失敗!

8.00截斷

上傳成功!

利用檔案包含漏洞,測試檔案裡面程式碼是否可執行。

第十八關,通關。

Pass-19

黑盒階段

進入十九關,可以看到,一個很明顯的區別就是這裡多了一個儲存名稱的選項,猜測,可能我們無論上傳什麼,都會被重新命名為這個名稱進行儲存。所以我們可以嘗試接著上傳我們的測試圖片馬,然後將儲存名稱的字尾修改為php,嘗試上傳。

可以發現,在我們將儲存檔案的字尾修改之後,系統給出了報錯,那麼我們基本可以放棄繞過黑名單的幾種方法了。

因為我們就算可以繞過網頁的防禦,儲存的檔案的名稱依然是被修改掉了,所以在這上面花費的功夫基本等於無用功。

所以我們直接使用00過濾。

可以發現,檔案已經上傳成功。

訪問檔案,檢查程式碼是否依然可執行。

第十九關,通關!

Pass-20

可以看到,在上傳點下面也有一個儲存名稱框,跟之前一樣,先測試一下是否可以通過直接修改儲存名稱的字尾來直接將我們上傳的檔案修改為php檔案。

可以發現,在這裡修改不行,同上一關的思路,我們沒必要測試黑名單繞過方式,直接測試白名單繞過方式,也就是00截斷。

可以發現,00截斷失敗了,一般在黑盒情況下,做到這裡就可以結束了。

白盒階段

檢視原始碼,找到失敗的原因。程式碼如下:

$is_upload = false;
$msg = null;
if(!empty($_FILES['upload_file'])){
    //檢查MIME
    $allow_type = array('image/jpeg','image/png','image/gif');
    if(!in_array($_FILES['upload_file']['type'],$allow_type)){
        $msg = "禁止上傳該型別檔案!";
    }else{
        //檢查檔名
        $file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];
        if (!is_array($file)) {
            $file = explode('.', strtolower($file));
        }

        $ext = end($file);
        $allow_suffix = array('jpg','png','gif');
        if (!in_array($ext, $allow_suffix)) {
            $msg = "禁止上傳該字尾檔案!";
        }else{
            $file_name = reset($file) . '.' . $file[count($file) - 1];
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH . '/' .$file_name;
            if (move_uploaded_file($temp_file, $img_path)) {
                $msg = "檔案上傳成功!";
                $is_upload = true;
            } else {
                $msg = "檔案上傳失敗!";
            }
        }
    }
}else{
    $msg = "請選擇要上傳的檔案!";
}

可以發現$file_name經過reset($file) . '.' . $file[count($file) - 1];處理。

如果上傳的是陣列的話,會跳過$file = explode('.', strtolower($file));。並且字尾有白名單過濾

$ext = end($file);
$allow_suffix = array('jpg','png','gif');

而最終的檔名字尾取的是$file[count($file) - 1],因此我們可以讓$file為陣列。$file[0]smi1e.php/,也就是reset($file),然後再令$file[2]為白名單中的jpg。此時end($file)等於jpg,$file[count($file) - 1]為空。而 $file_name = reset($file) . '.' . $file[count($file) - 1];,也就是smi1e.php/.,最終move_uploaded_file會忽略掉/.,最終上傳smi1e.php

上面是我從部落格裡面看到的大佬的思路,大概就是說,因為正常情況下程式碼裡面的有行程式碼無法繞過,所以在黑盒測試階段我們的00階段失效,但如果上傳的是陣列的話,就可以跳過這行程式碼。

{{uploading-image-428631.png(uploading...)}}

第二十關,未通關,需要再多研究。