最新消息:图 床

ThinkerPHP後台遠程任意代碼執行漏洞分析

COOL IAM 55浏览 0评论

來源:先知安全技術社區
作者:FaIth4444

漏洞描述

ThinkerPHP,由 thinker 開發維護。基於 thinkphp3.2 開發的一款部分開源的cms系統,前期是仿的phpcms系統,后在在模仿基礎上對界面等做了優化。

thinkphp3.2 的優勢在於相對應 phpcms 用更少的代碼實現更多的功能, 基於命名空間的相對較新的架構以及擁有更好的底層擴展性。ThinkerPHP希望融合phpcms和thinkphp3.2的優點並志在收穫一個擴展性好、開發效率高、用戶體驗佳、底層擴展性好的快速開發系統。在開發過程中作者一直秉承專註、專業、專心的精神,不斷完善。

ThinkerCMS1.4 (最新版)InputController.class.php 頁面由於對 $_POST 等參數沒有進行有效的判斷和過濾,導致存在任意代碼執行漏洞,允許攻擊者利用漏洞全完獲取Webshell權限。

溯源發現危險代碼塊

① 漏洞觸發位置

文件位置: D:/WWW/Modules/Plug/Controller/InputController.class.php (67行)

觸發函數: public function cropzoomUpload()

public function cropzoomUpload()
        {
                if(session("userinfo")==NULL)E('沒有登陸!');
                load('@.cropzoom');
                list($width, $height) = getimagesize($_POST["imageSource"]);
                $viewPortW = $_POST["viewPortW"];
                $viewPortH = $_POST["viewPortH"];

                $pWidth = $_POST["imageW"];
                $pHeight =  $_POST["imageH"];
                $ext = end(explode(".",$_POST["imageSource"]));
                $function = returnCorrectFunction($ext);
                $image = $function($_POST["imageSource"]);

                $width = imagesx($image);
                $height = imagesy($image);

                // Resample
                $image_p = imagecreatetruecolor($pWidth, $pHeight);
                setTransparency($image,$image_p,$ext);
                imagecopyresampled($image_p, $image, 0, 0, 0, 0, $pWidth, $pHeight, $width, $height);
                imagedestroy($image);
                $widthR = imagesx($image_p);
                $hegihtR = imagesy($image_p);

                $selectorX = $_POST["selectorX"];
                $selectorY = $_POST["selectorY"];

                if($_POST["imageRotate"]){
                        $angle = 360 - $_POST["imageRotate"];
                        $image_p = imagerotate($image_p,$angle,0);

                        $pWidth = imagesx($image_p);
                        $pHeight = imagesy($image_p);

                        //print $pWidth."---".$pHeight;

                        $diffW = abs($pWidth - $widthR) / 2;
                        $diffH = abs($pHeight - $hegihtR) / 2;

                        $_POST["imageX"] = ($pWidth > $widthR ? $_POST["imageX"] - $diffW : $_POST["imageX"] + $diffW);
                        $_POST["imageY"] = ($pHeight > $hegihtR ? $_POST["imageY"] - $diffH : $_POST["imageY"] + $diffH);

                }

                $dst_x = $src_x = $dst_y = $src_y = 0;

                if($_POST["imageX"] > 0){
                        $dst_x = abs($_POST["imageX"]);
                }else{
                        $src_x = abs($_POST["imageX"]);
                }
                if($_POST["imageY"] > 0){
                        $dst_y = abs($_POST["imageY"]);
                }else{
                        $src_y = abs($_POST["imageY"]);
                }

                $viewport = imagecreatetruecolor($_POST["viewPortW"],$_POST["viewPortH"]);
                setTransparency($image_p,$viewport,$ext);

                imagecopy($viewport, $image_p, $dst_x, $dst_y, $src_x, $src_y, $pWidth, $pHeight);

                imagedestroy($image_p);

                $selector = imagecreatetruecolor($_POST["selectorW"],$_POST["selectorH"]);

                setTransparency($viewport,$selector,$ext);
                imagecopy($selector, $viewport, 0, 0, $selectorX, $selectorY,$_POST["viewPortW"],$_POST["viewPortH"]);

                //獲取圖片內容
        //var_dump($_POST);
                ob_start();
                parseImage($ext,$selector);
                $img = ob_get_contents();
                ob_end_clean();

                if(filter_var($_POST["imageSource"], FILTER_VALIDATE_URL))
                {
                        $urlinfo=parse_url($_POST["imageSource"]);
                        $path=$urlinfo['path'];
                        $pathinfo=pathinfo($path);

                }
                else
                {
                        $path=$_POST["imageSource"];
                        $pathinfo=pathinfo($_POST["imageSource"]);

                }
                $file_name=$pathinfo['filename'].'_crop.'.$pathinfo['extension'];//剪切后的圖片名稱
                $file_path='.'.$pathinfo['dirname'].'/'.$file_name;

                file_put_contents($file_path, $img);
                echo C('upload_host').$pathinfo['dirname'].'/'.$file_name;
                imagedestroy($viewport);
        }

在這裡我們可以觀察發現 public function cropzoomUpload() 函數的大概操作流程:

1.接受了包括 $_POST["viewPortW"]$_POST["viewPortH"]$_POST["imageSource"]等一系列的圖片剪切的參數

2.使用這些參數,並調用php-GD庫對圖片進行渲染和處理

3.將處理后的圖片輸出到緩衝區,將緩衝區作為圖片的內容

4.然後將再根據$_POST["imageSource"]參數進行pathinfo處理,將結果存到$pathinfo,並組合成為寫文件的路徑$file_path

5.將緩衝區內容通過file_put_contents寫入指定的$file_path(此處直接寫入Webshell,獲取Web權限)

② ByPass (繞過文件後綴名檢測,繞過php-GD對圖片的渲染和處理導致webshell代碼錯位失效)

繞過文件後綴名檢測
cropzoom 圖片剪切相關的函數

文件位置: D:/WWW/Modules/Plug/Common/cropzoom.php

<?php
/*
* cropzoom 圖片剪切相關的函數
*/
function determineImageScale($sourceWidth, $sourceHeight, $targetWidth, $targetHeight) {
        $scalex =  $targetWidth / $sourceWidth;
        $scaley =  $targetHeight / $sourceHeight;
        return min($scalex, $scaley);
}

function returnCorrectFunction($ext){
        $function = "";
        switch($ext){
                case "png":
                        $function = "imagecreatefrompng";
                        break;
                case "jpeg":
                        $function = "imagecreatefromjpeg";
                        break;
                case "jpg":
                        $function = "imagecreatefromjpeg";
                        break;
                case "gif":
                        $function = "imagecreatefromgif";
                        break;
        }
        return $function;
}

function parseImage($ext,$img){
        switch($ext){
                case "png":
                        return imagepng($img);
                        break;
                case "jpeg":
                        return imagejpeg($img);
                        break;
                case "jpg":
                        return imagejpeg($img);
                        break;
                case "gif":
                        return imagegif($img);
                        break;
        }
}

function setTransparency($imgSrc,$imgDest,$ext){

        if($ext == "png" || $ext == "gif"){
                $trnprt_indx = imagecolortransparent($imgSrc);
                // If we have a specific transparent color
                if ($trnprt_indx >= 0) {
                        // Get the original image's transparent color's RGB values
                        $trnprt_color    = imagecolorsforindex($imgSrc, $trnprt_indx);
                        // Allocate the same color in the new image resource
                        $trnprt_indx    = imagecolorallocate($imgDest, $trnprt_color['red'], $trnprt_color['green'], $trnprt_color['blue']);
                        // Completely fill the background of the new image with allocated color.
                        imagefill($imgDest, 0, 0, $trnprt_indx);
                        // Set the background color for new image to transparent
                        imagecolortransparent($imgDest, $trnprt_indx);
                }
                // Always make a transparent background color for PNGs that don't have one allocated already
                elseif ($ext == "png") {
                        // Turn off transparency blending (temporarily)
                        imagealphablending($imgDest, true);
                        // Create a new transparent color for image
                        $color = imagecolorallocatealpha($imgDest, 0, 0, 0, 127);
                        // Completely fill the background of the new image with allocated color.
                        imagefill($imgDest, 0, 0, $color);
                        // Restore transparency blending
                        imagesavealpha($imgDest, true);
                }

        }
}

?>

對文件後綴名的處理包括主要通過 $_POST["imageSource"] 這個變量的值,包括兩部分

1.獲取 $_POST["imageSource"] 的值,使用 end 和 explode 獲得路徑的後綴,根據路徑後綴使用對應的 php-GD 庫函數進行處理

                $ext = end(explode(".",$_POST["imageSource"]));
                $function = returnCorrectFunction($ext);
                $image = $function($_POST["imageSource"]);

2.同樣是根據的 $_POST["imageSource"] 值進行判斷進入不同的分支,然後組合成為 $file_path (file_put_contents 的路徑參數)

if(filter_var($_POST["imageSource"], FILTER_VALIDATE_URL))
                {
                        $urlinfo=parse_url($_POST["imageSource"]);
                        $path=$urlinfo['path'];
                        $pathinfo=pathinfo($path);

                }
                else
                {
                        $path=$_POST["imageSource"];
                        $pathinfo=pathinfo($_POST["imageSource"]);

                }
                $file_name=$pathinfo['filename'].'_crop.'.$pathinfo['extension'];//剪切后的圖片名稱
                $file_path='.'.$pathinfo['dirname'].'/'.$file_name;

                file_put_contents($file_path, $img);

繞過辦法,令 $_POST["imageSource"] 為“

1.使用end函數 所以加入使用 ?1.jpg 作為請求的參數進行繞過,不然會因為找不到函數報錯終止,因為程序會調用 returnCorrectFunction() 函數根據後綴(此處為JPG)進行調用其他php-GD函數

2.因為使用的 pathinfo() 處理 $_POST["imageSource"],所以 前半部分為 payload_faith4444_crop.php

至此,成功繞過文件後綴名檢測

繞過php-GD對圖片的渲染和處理導致webshell代碼錯位失效(此處參考索馬里海盜方法)

圖片會經過 php-GD 處理,會導致 webshell 語句錯位失效,如何在處理后仍然保留 shell 語句呢?

在正常圖片中插入shell並無視GD圖像庫的處理,常規方法有兩種

1.對比兩張經過 php-gd 庫轉換過的 gif 圖片,如果其中存在相同之處,這就證明這部分圖片數據不會經過轉換。然後我可以注入代碼到這部分圖片文件中,最終實現遠程代碼執行

2.利用 php-gd 算法上的問題進行繞過

這裡我們選擇第二種,使用腳本進行處理圖片並繞過

1、上傳一張jpg圖片,然後把網站處理完的圖片再下回來 比如x.jpg

2、執行圖片處理腳本腳本進行處理 php jpg_payload.php x.jpg

3、如果沒出錯的話,新生成的文件再次經過gd庫處理后,仍然能保留 webshell 代碼語句

tips:

1、圖片找的稍微大一點 成功率更高

2、shell 語句越短成功率越高

3、一張圖片不行就換一張 不要死磕

圖片處理腳本,還有具體操作會在驗證部分詳細寫出!!!

漏洞攻擊與利用

漏洞復現材料(cms源碼,攻擊腳本,攻擊圖片) :鏈接:http://pan.baidu.com/s/1eSmtiSE 密碼:tsna

(自己的php-web環境的vps上,一定要是phpweb環境(並開啟短標籤),phpweb環境(並開啟短標籤),其他環境也可,但需要自行構造payload所需的圖片)

本地驗證

① 首先登陸後台

转载请注明:IAMCOOL » ThinkerPHP後台遠程任意代碼執行漏洞分析

0 0 vote
Article Rating
Subscribe
Notify of
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x