php imagecreatefrom* 系列函式之 png
0x00 簡介
這篇文章主要分析 php 使用 GD 庫的 imagecreatefrompng() 函式重建 png 圖片可能導致的本地檔案包含漏洞。
當系統存在檔案包含的點,能包含圖片檔案; 另外系統存在圖片上傳,上傳的圖片使用 imagecreatefrompng() 函式重建圖片並儲存在本地,則很可能出現檔案包含的漏洞。
通常,系統在實現圖片上傳功能時,為了防範使用者上傳含有惡意 php 程式碼的圖片,可採用 gd 庫重建圖片,gd 庫重建圖片的一系列函式 imagecreatefrom*,會檢查圖片規範,驗證圖片合法性,以此抵禦圖片中含有惡意 php 程式碼的攻擊。
那麼, imagecreatefrom* 系列函式是否能完全抵禦圖片中插入 php 程式碼的攻擊呢,本文以 imagecreatefrompng() 函式作為研究物件,探討實現重建 png 格式的圖片中包含惡意 php 程式碼的可能性,以及所需要滿足的條件。
png 檔案格式, imagecreatefrompng 函式解析, 修改圖片, 上傳, 檔案包含 ...
0x01 png 圖片格式
要實現重建的 png 圖片中仍包含有惡意的 php 程式碼, 首先要對 png 圖片格式有基本的瞭解。png 支援三種影像型別:索引彩色影像(index-color images),灰度影像(grayscale images),真彩色影像(true-color images), 其中索引彩色影像也稱為基於調色盤影像(Palette-based images)。
標準的 png 檔案結構由一個 png 標識頭連線多個 png 資料塊組成,如: png signature | png chunk | png chunk | ... | png chunk
.
png 標識
png 標識作為 png 圖片的頭部,為固定的 8 位元組,如下
89 50 4E 47 OD 0A 1A 0A
png 資料塊
png 定義了兩種型別的資料塊,一種是稱為關鍵資料塊(critical chunk),標準的資料塊; 另一種叫做輔助資料塊(ancillary chunks),可選的資料塊。關鍵資料塊定義了3個標準資料塊,每個 png 檔案都必須包含它們。3個標準資料塊為: IHDR, IDAT, IEND
.
這裡介紹4個資料塊:IHDR, PLTE, IDAT, IEND
png 資料塊結構
png 檔案中,每個資料塊由4個部分組成 length | type(name) | data | CRC
, 說明如下
length: 4 bytes, just length of the data, not include type and CRC
type: 4 bytes, ASCII letters([A-Z,a-z])
CRC: 4bytes
CRC(cyclic redundancy check)域中的值是對Chunk Type Code域和Chunk Data域中的資料進行計算得到的。CRC具體演算法定義在ISO 3309和ITU-T V.42中,其值按下面的CRC碼生成多項式進行計算: x+x+x+x+x+x+x+x+x+x+x+x+x+x+1
- IHDR
檔案頭資料塊IHDR(header chunk):它包含有PNG檔案中儲存的影像資料的基本資訊,並要作為第一個資料塊出現在PNG資料流中,而且一個PNG資料流中只能有一個檔案頭資料塊。
檔案頭資料塊由13位元組組成,它的如下所示
域的名稱 | 位元組數 | 說明 |
---|---|---|
Width | 4 bytes | 影像寬度,以畫素為單位 |
Height | 4 bytes | 影像高度,以畫素為單位 |
Bit depth | 1 byte | 影像深度. 索引彩色影像: 1,2,4或8 灰度影像: 1,2,4,8或16 真彩色影像: 8或16 |
ColorType | 1 byte | 顏色型別. 0:灰度影像, 1,2,4,8或16 2:真彩色影像,8或16 3:索引彩色影像,1,2,4或8 4:帶α通道資料的灰度影像,8或16 6:帶α通道資料的真彩色影像,8或16 |
Compression method | 1 byte | 壓縮方法(LZ77派生演算法) |
Filter method | 1 byte | 濾波器方法 |
Interlace method | 1 byte | 隔行掃描方法. 0:非隔行掃描 1: Adam7(由Adam M. Costello開發的7遍隔行掃描方法) |
- PLTE
調色盤資料塊PLTE(palette chunk)包含有與索引彩色影像(indexed-color image)相關的彩色變換資料,它僅與索引彩色影像有關,而且要放在影像資料塊(image data chunk)之前。
PLTE資料塊是定義影像的調色盤資訊,PLTE可以包含1~256個調色盤資訊,每一個調色盤資訊由3個位元組組成:
顏色 | 位元組 | 意義 |
---|---|---|
Red | 1 byte | 0 = 黑色, 255 = 紅 |
Green | 1 byte | 0 = 黑色, 255 = 綠色 |
Blue | 1 byte | 0 = 黑色, 255 = 藍色 |
因此,調色盤的長度應該是3的倍數,否則,這將是一個非法的調色盤。顏色數 = length/3
對於索引影像,調色盤資訊是必須的,調色盤的顏色索引從0開始編號,然後是1、2……,調色盤的顏色數不能超過色深中規定的顏色數(如影像色深為4的時候,調色盤中的顏色數不可以超過2^4=16),否則,這將導致PNG影像不合法。
- IDAT
影像資料塊IDAT(image data chunk):它儲存實際的資料,在資料流中可包含多個連續順序的影像資料塊。IDAT存放著影像真正的資料資訊
- IEND
影像結束資料IEND(image trailer chunk):它用來標記PNG檔案或者資料流已經結束,並且必須要放在檔案的尾部。
正常情況下, png 檔案的結尾為如下12個字元:
00 00 00 00 49 45 4E 44 AE 42 60 82
由於資料塊結構的定義,IEND資料塊的長度總是0(00 00 00 00,除非人為加入資訊),資料標識總是IEND(49 45 4E 44),因此,CRC碼也總是AE 42 60 82
0x02 php imagecreatefrompng() 函式
有了對 png 圖片格式的基本瞭解,可以幫助我們更好的理解 imagecreatefrompng() 函式的底層實現。分析 php 原始碼(php 5.6.20)可知, php imagecreatefrompng() 函式實現重建圖片,核心是 gd 庫的 gdImageCreateFromPngCtx() 函式。
分析 gd 庫中的 gdImageCreateFromPngCtx() 函式可知,函式首先會檢測 png signature, 不合法則返回NULL。然後會讀原始的 png 圖片檔案給 png_ptr, 再從 png_ptr 中讀圖片資訊到 info_ptr,再之後就是獲取 IHDR 資訊,讀 IDAT 資料等,這裡不一一討論。這裡僅討論 png_read_info() 函式中對讀 PLTE 資料庫的驗證處理。
#!c
gd_png.c/gdImageCreateFromPngCtx
{
...
if (png_sig_cmp(sig, 0, 8) != 0) { /* bad signature */
return NULL; /* bad signature */
}
...
png_set_read_fn (png_ptr, (void *) infile, gdPngReadData);
png_read_info (png_ptr, info_ptr); /* read all PNG info up to image data */
...
}
要了解 png_read_info() 的內部實現,可以透過讀 libpng 的原始碼(libpng 1.6.21)進行了解。當圖片型別是索引影像時,png_read_info() 讀到 PLTE chunk 時會呼叫 png_handle_PLTE 函式進行 CRC 校驗
#!c
pngread.c/png_read_info
{
...
else if (chunk_name == png_PLTE)
png_handle_PLTE(png_ptr, info_ptr, length);
...
}
pngrutil.c/png_handle_PLTE
{
...
#ifndef PNG_READ_OPT_PLTE_SUPPORTED
if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE)
#endif
{
png_crc_finish(png_ptr, (int) length - num * 3);
}
...
}
分析底層原始碼可知, png signature 是不可能插入 php 程式碼的; IHDR 儲存的是 png 的圖片資訊,有固定的長度和格式,程式會提取圖片資訊資料進行驗證,很難插入 php 程式碼;而 PLTE 主要進行了 CRC 校驗和顏色數合法性校驗等簡單的校驗,那麼很可能在 data 域插入 php 程式碼。
從對 PLTE chunk 驗證的分析可知, 當原始圖片格式給索引圖片時,PLTE 資料塊在滿足 png 格式規範的情況下,程式還會進行 CRC 校驗。因此,要將 PHP 程式碼寫入 PLTE 資料塊,不僅要修改 data 域的內容為php程式碼,然後修改 CRC 為正確的 CRC 校驗值,當要填充的程式碼過長時,可以改變 length 域的數值,滿足 length 為3的倍數, 且顏色數不超過色深中規定的顏色數。例如: IHDR 資料塊中 Bit depth 為 08, 則最大的顏色數為 2^8=256, 那麼 PLTE 資料塊 data 的長度不超過 3*256=0x300。 這個長度對寫入 php 一句話木馬或者建立後門檔案足夠了。
那麼是不是所有 png 圖片都可以在 PLTE 資料塊插入 php 程式碼呢?下面透過實驗予以說明。
0x03 實驗驗證
png 支援索引彩色影像(index-color images),灰度影像(grayscale images),真彩色影像(true-color images)三種型別的圖片,而 PLTE 資料塊是索引影像所必須的,因此索引影像極有可能在 PLTE 資料塊插入 php 程式碼。
下面摘錄 gd 庫中 gdImageCreateFromPng() 函式的一段說明
If the PNG image being loaded is a truecolor image, the resulting
gdImagePtr will refer to a truecolor image. If the PNG image being
loaded is a palette or grayscale image, the resulting gdImagePtr
will refer to a palette image.
函式將索引彩色影像和灰度影像轉換為索引彩色影像, 將真彩色影像轉換為真彩色影像。下面分別轉換這三種型別的圖片,測試圖片地址: 圖片 . php程式碼如下
#!php
<?php
$pngfile = 'test.png';
$newpngfile = 'new.png';
$im = imagecreatefrompng($pngfile);
imagepng($im,$newpngfile);
?>
- 索引影像
讀 IHDR 資料塊資訊,色深為8bits, color type=0x03, 為索引影像型別,改變其 PLTE 資料塊如下,修改資料為 <?php phpinfo();?>
計算 CRC 過程如下
imagecreatefrompng() 重建的圖片如下
可以看出重建的圖片中 PLTE 資料塊保留了 php 程式碼,重建也增加了 pHYs 資料塊,對我們所關心的結果並沒有影響。說明插入 php 程式碼成功。
- 灰度影像
原始影像, 不含 PLTE 資料塊, 如下所示
插入 PLTE 資料塊,並寫入 php 程式碼
重建後的圖片如下
可以看出重建的圖片轉換為 索引影像型別,並且重寫了 PLTE 資料塊,寫入 php 程式碼失敗
- 真彩色影像
原始影像, 不含 PLTE 資料塊, 如下所示
插入 PLTE 資料塊,並寫入 php 程式碼
重建後的圖片如下
可以看出真彩色型別圖片重建後的圖片不含 PLTE 資料塊,寫入 php 程式碼失敗
0x04 總結
透過以上分析和實驗可知, imagecreatefrompng() 函式並不能完全防止圖片中插入 php 程式碼, 當圖片型別為索引影像時, 在 PLTE chunk 可以成功插入 php 程式碼, 而另外其他型別的圖片並不能實現 PLTE chunk 中插入 php 程式碼。最後, 一併感謝下面參考資料對我研究的幫助。
附,修改索引影像插入 php 程式碼的地址 github
所附程式碼實現了當 payload 長度大於 PLTE 資料長度時, 會重寫 PLTE 資料塊。然而 在實驗過程中發現,imagecreatepng()函式重建的圖片 PLTE 資料塊的長度仍為原始的長度,即並不能隨意擴充 PLTE 資料塊的長度,具體原因還需深入分析原始碼, 也就是說要載入的 payload 不能超過 PLTE 資料塊所給的長度。
通常情況下, PLTE 資料塊所給的長度可以滿足我們插入基本的 php 後門程式碼,存在了那麼一個點,是不是可以撬動地球了呢
當然本文還有許多不足之處,望大家批評指正
0x05 資料參考
4.png book
相關文章
- PHP入門之函式2020-07-29PHP函式
- PHP函式漏洞審計之addslashes函式-2021-03-22PHP函式
- php底層原理之函式2019-04-14PHP函式
- PHP之string之ord()函式使用2019-02-16PHP函式
- PHP函式之parse_str()和parse_url()函式2019-02-16PHP函式
- php之正規表示式函式總結2019-02-16PHP函式
- PHP 學習總結之函式2019-02-16PHP函式
- 非同步操作系列之Generator函式與Async函式2018-11-20非同步函式
- php函式2020-03-23PHP函式
- PHP 函式2021-03-23PHP函式
- php 函式2018-05-25PHP函式
- PHP之string之str_split()函式使用2019-02-16PHP函式
- PHP 7.4 新特性之箭頭函式2019-09-06PHP函式
- PHP新特性之閉包、匿名函式2019-03-04PHP函式
- PHP 列印函式之 print print_r2016-03-30PHP函式
- PHP字串函式之 strstr stristr strchr strrchr2016-03-25PHP字串函式
- Matplotlib 系列之【繪製函式影像】2019-03-04函式
- Matplotlib 系列之【繪製函式影象】2018-08-02函式
- dart系列之:dart語言中的函式2021-11-10Dart函式
- ES6 系列之箭頭函式2018-06-04函式
- 類函式和物件函式 PHP2016-11-20函式物件PHP
- PHP之string之str_pad()函式使用2019-02-16PHP函式
- PHP基礎系列之正規表示式(一)2019-02-16PHP
- PHP 函式庫精講之類與物件2020-02-07PHP函式物件
- PHP 常用函式2019-05-22PHP函式
- PHP匿名函式2021-09-09PHP函式
- PHP常用函式2021-09-09PHP函式
- PHP函式大全2021-05-20PHP函式
- PHP字串函式2021-01-20PHP字串函式
- php函式案例2017-11-12PHP函式
- PHP extract() 函式2014-11-15PHP函式
- [php]unset函式2014-12-08PHP函式
- php’sfopen()函式2013-04-20PHP函式
- ord函式-php2016-03-09函式PHP
- php’sexplode()函式2013-04-20PHP函式
- 密碼學系列之:memory-hard函式2021-05-26密碼學函式
- 密碼學系列之:memory-bound函式2021-07-14密碼學函式
- 稀裡糊塗系列之list函式妙用2018-10-25函式