0x01 起因
有天在群里說起上傳的%00截斷的一些問題,就想起之前自己在這個問題踩過坑,想起了自己曾經的flag說要寫文章,一直沒寫,現在來填坑了。
0x02 經過
源碼理解1
2
3
4//test.php
include "1.txt\000.jpg";
?>
1
2
3
4//1.txt
echo 'helloworld';
?>
上面的示例代碼在 php版本小于5.3.4 的情況下回輸出 helloworld 。從php的內核執行過程來看,PHP通過 php_execute_script 來執行PHP的腳本,這里選取部分有關代碼,具體可以看這里:
在 第10行 我們看到,他調用 zend_execute_scripts 來針對腳本進行解析,而這個函數是在Zend/zend.c里面,截取部分相關代碼如下:
從PHP內核開來實際上是分為兩塊部分,一個是 compile編譯過程 ,另一個是execute執行過程。
第一部分:compile編譯過程
我們可以看到這里的代碼邏輯通過 zend_compile_file 獲取文件的內容,zend_compile_file是一個函數指針,其聲明在/Zend/zend_compile.c中
1ZEND_API zend_op_array *(*zend_compile_file)(zend_file_handle *file_handle, int type TSRMLS_DC);
在引擎初始化的時候,會將 compile_file 函數的地址賦值給 zend_compile_file 。
簡單總結一下上面部分代碼的邏輯:
zend_compile_file 函數首先調用 open_file_for_scanning 去讀取文件,然后通過 第17行的zendparse 去進行語法和詞法解析。而 zendparse 是通過 lex_scan 去掃描出token并進行語法分析。
第二部分:execute執行過程
zend_execute 也是一個函數指針,其聲明在/Zend/zend_execute.h中。
1ZEND_API extern void (*zend_execute)(zend_op_array *op_array TSRMLS_DC);
在引擎初始化的時候,會將 execute 函數的地址賦值給 zend_execute 。
根據我們的了解,zend_execute 通過 ZEND_INCLUDE_OR_EVAL_SPEC_CONST_HANDLER 函數來進行include的實際處理,即包含要包含的文件。
對比修復代碼找到漏洞觸發點:
摘出部分修復代碼:
我看下存在漏洞的調試運行結果:
修復代碼的 Z_STRVAL_P(inc_filename) 即上圖中的val,即”1.txt”,strlen取得長度為5,而 Z_STRLEN_P(inc_filename) 即上圖中的len即10。這里實際上解析到的文件名是1.txt。
不存在漏洞的調試運行結果:
一旦出現%00截斷,include的文件名經過url轉碼由”1.txt%00.jpg”變為”1.txt\000.jpg”,進入php語法詞法分析器解析后會將這個字符串解析成一個字符串,并使用 zend_scan_escape_string 進行字符串轉碼,如圖,進入 zend_scan_escape_string 的內容為:
只要比較發現文件名的strlen長度和語法分析出來的長度不一樣,就說明內部存在截斷的字符,因此輸出了打開文件失敗的信息。
利用方式
劃重點 PHP版本低于5.3.4
%00截斷有這么2種利用狀況
在burpsuite的16進制編輯工具將”shell.php .jpg”(帶空格的)中間的空格由20改成00
在1中,url中的%00(形如%xx),web server會把它當作十六進制處理,然后將該十六進制數據hex(00)“翻譯”成統一的ascii碼值“NUL(null)”,實現了截斷。
在2中,burpsuite用burp自帶的十六進制編輯工具將”shell.php .jpg”(中間有空格)中的空格由20改成00,如果burp中有二進制編輯工具。
延伸一下
其實關于截斷相關問題,還有個很有趣的函數, iconv() 函數:
在了解 iconv() 函數漏洞之前,可能需要一點前置知識
在php中,所有的字符都是二進制的串,PHP本身并不認識任何編碼,只是根據編碼來顯示內容。PHP中的chr() 函數從指定的 ASCII 值返回字符。ASCII 值可被指定為十進制值、八進制值或十六進制值。八進制值被定義為帶前置 0,而十六進制值被定義為帶前置 0x。
而在php5.4之前, iconv() 函數在轉換編碼的時候,遇到不合法的字符串的時候會將其截斷。
1
2
3
4
5
6
7<?php
for($k=0;$k<=255;$k++)
{
$a='shell.php'.chr($k)."1.jpg";
echo 'k:'.$k.' '.'$a:'.$a.' '.'iconv("UTF-8","gbk",$a):'.iconv("UTF-8","gbk",$a)."\n";
}
?>
通過fuzz發現,其中 iconv(“UTF-8”,”gbk”,$a) 或是 iconv(“UTF-8”,”gb2313”,$a) 都會在chr(128)到chr(255)之間截斷,使結果為shell.php
在php5.4.0版本的時候就不存在這個問題了。
實際例子
關于我們剛剛說的 iconv() 截斷的問題,其實sitestar pro就是個典型例子,我們看個例子:
漏洞觸發點在 module/mod_tool.php 中的 img_create() 函數,截取部分代碼如下:
這部分中有段代碼吸引了我的注意力
1
2
3
4if(!preg_match('/\.('.PIC_ALLOW_EXT.')$/i', $file_info["name"])) {
Notice::set('mod_marquee/msg', __('File type error!'));
Content::redirect(Html::uriquery('mod_marquee', 'upload_img'));
}
跟進一下這個 PIC_ALLOW_EXT 參數,發現其在數據庫中寫死了,只允許 gif|jpg|png|bmp 這類文件。即如果文件名最后不是 gif|jpg|png|bmp ,則提示文件類型錯誤。
所以這部分的正則表達式的功能應該是針對文件后綴名進行檢查。
繼續跟進這部分代碼,有一串代碼如下:
1
2
3
4if (!$this->_savelinkimg($file_info)) {
Notice::set('mod_marquee/msg', __('Link image upload failed!'));
Content::redirect(Html::uriquery('mod_marquee', 'upload_img'));
}
這部分代碼應該是進行文件保存的功能,這個有個核心函數 _savelinkimg ,跟進這個函數。
第二行問題出現了
1$struct_file['name'] = iconv("UTF-8", "gb2312", $struct_file['name']);
在 iconv 轉碼的過程中, utf->gb2312 (其他部分編碼之間轉換同樣存在這個問題)會導致字符串被截斷,如:$filename="shell.php(hex).jpg; (hex為0x80-0x99),經過 iconv 轉碼后會變成 $filename="shell.php" ;
0x03 結果
總結一下截斷大概可以在以下情況適用
include(require)
file_get_contents
file_exists
所有url中參數可以用%00控制
0x04 參考文獻
潛伏在PHP Manual背后的特性及漏洞(看雪峰會議題)
wooyun-2014-048293