來源:先知安全技術社區
作者: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後台遠程任意代碼執行漏洞分析