在我們日常的開發中,經常需要用到判斷圖片是否存在,存在則顯示,不存在則顯示默認圖片,那么我們用到的判斷有哪些呢?今天我們就來看下幾個常用的方法:
1、getimagesize()函數
getimagesize
函數並不屬於 GD 擴展的部分,標准安裝的 PHP 都可以使用這個函數。可以先看看這個函數的文檔描述:http://php.net/manual/zh/function.getimagesize.php
如果指定的文件如果不是有效的圖像,會返回 false,返回數據中也有表示文檔類型的字段。如果不用來獲取文件的大小而是使用它來判斷上傳文件是否是圖片文件,看起來似乎是個很不錯的方案,當然這需要屏蔽掉可能產生的警告,比如代碼這樣寫:
<?php $filesize = @getimagesize('/path/to/image.png'); if ($filesize) { do_upload(); } # 另外需要注意的是,你不可以像下面這樣寫: # if ($filesize[2] == 0) # 因為 $filesize[2] 可能是 1 到 16 之間的整數,但卻絕對不對是0。
但是如果你僅僅是做了這樣的驗證,那么很不幸,你成功的在代碼里種下了一個 webshell 的隱患。
要分析這個問題,我們先來看一下這個函數的原型:
static void php_getimagesize_from_stream(php_stream *stream, zval **info, INTERNAL_FUNCTION_PARAMETERS) { ... itype = php_getimagetype(stream, NULL TSRMLS_CC); switch( itype) { ... } ... } static void php_getimagesize_from_any(INTERNAL_FUNCTION_PARAMETERS, int mode) { ... php_getimagesize_from_stream(stream, info, INTERNAL_FUNCTION_PARAM_PASSTHRU); php_stream_close(stream); } PHP_FUNCTION(getimagesize) { php_getimagesize_from_any(INTERNAL_FUNCTION_PARAM_PASSTHRU, FROM_PATH); }
限於篇幅上面隱藏了一些細節,現在從上面的代碼中我們知道兩件事情就夠了:
-
最終處理的函數是
php_getimagesize_from_stream
-
負責判斷文件類型的函數是
php_getimagetype
接下來看一下 php_getimagetype
的實現:
PHPAPI int php_getimagetype(php_stream * stream, char *filetype TSRMLS_DC) { ... if (!memcmp(filetype, php_sig_gif, 3)) { return IMAGE_FILETYPE_GIF; } else if (!memcmp(filetype, php_sig_jpg, 3)) { return IMAGE_FILETYPE_JPEG; } else if (!memcmp(filetype, php_sig_png, 3)) { ... } }
去掉了一些細節,php_sig_gif
php_sig_png
等是在文件頭部定義的:
PHPAPI const char php_sig_gif[3] = {'G', 'I', 'F'}; ... PHPAPI const char php_sig_png[8] = {(char) 0x89, (char) 0x50, (char) 0x4e, (char) 0x47, (char) 0x0d, (char) 0x0a, (char) 0x1a, (char) 0x0a};
可以看出來 image type 是根據文件流的前幾個字節(文件頭)來判斷的。那么既然如此,我們可不可以構造一個特殊的 PHP 文件來繞過這個判斷呢?不如來嘗試一下。
找一個十六進制編輯器來寫一個的 PHP 語句,比如:
<?php phpinfo(); ?>
這幾個字符的十六進制編碼(UTF-8)是這樣的:
3C3F 7068 7020 7068 7069 6E66 6F28 293B 203F 3E
我們構造一下,把 PNG 文件的頭字節加在前面變成這樣的:
8950 4E47 0D0A 1A0A 3C3F 7068 7020 7068 7069 6E66 6F28 293B 203F 3E
最后保存成 .php
后綴的文件(注意上面是文件的十六進制值),比如 test.php。執行一下 php test.php
你會發現完全可以執行成功。那么能用 getimagesize
讀取它的文件信息嗎?新建一個文件寫入代碼試一下:
<?php print_r(getimagesize('test.php'));
執行結果:
Array ( [0] => 1885957734 [1] => 1864902971 [2] => 3 [3] => width="1885957734" height="1864902971" [bits] => 32 [mime] => image/png )
成功讀取出來,並且文件也被正常識別為 PNG 文件,雖然寬和高的值都大的有點離譜。
現在你應該明白為什么上文說這里留下了一個 webshell 的隱患的吧。如果這里只有這樣的上傳判斷,而且上傳之后的文件是可以訪問的,就可以通過這個入口注入任意代碼執行了。
那么為什么上面的文件可以 PHP 是可以正常執行的呢?用 token_get_all 函數來看一下這個文件:
<?php print_r(token_get_all(file_get_contents('test.php')));
如果顯示正常的話你能看到輸出數組的第一個元素的解析器代號是 312,通過 token_name 獲取到的名稱會是 T_INLINE_HTML,也就是說文件頭部的信息被當成正常的內嵌的 HTML 代碼被忽略掉了。
至於為什么會有一個大的離譜的寬和高,看一下 php_handle_png
函數的實現就能知道,這些信息也是通過讀取特定的文件頭的位來獲取的。
所以,對於正常的圖片文件,getimagesize 完全可以勝任,但是對於一些有心構造的文件結構卻不行。
在處理用戶上傳的文件時,先簡單粗暴的判斷文件擴展名並對文件名做一下處理,保證在服務器上不是 php 文件都不能直接執行也是一種有效的方式。然后可以使用 getimagesize 做一些輔助處理。
2、file_exists()函數
file_exists() 函數檢查文件或目錄是否存在。
如果指定的文件或目錄存在則返回 true,否則返回 false。
eg: file_exists(path);其中的參數path必須是路徑,不能是url不然會一直返回false;
注意:
1、文件的任何上級目錄,只有寫權限時報文件不存在;
2、文件的任何上級目錄,只有讀權限時也報文件不存在;
3、而當所有上級目錄都有執行權限的時候,報文件是存在的,一切都正常。
說明file_exists()在判斷文件是否存在的時候是遞歸判斷每個目錄是不是有執行權限。
3、file_get_contents()函數
file_get_contents — 將整個文件讀入一個字符串
如果失敗,file_get_contents() 將返回 FALSE
。
果要打開有特殊字符的 URL (比如說有空格),就需要使用 urlencode() 進行 URL 編碼。
但是此函數如果請求比較多,文件比較大,那么可能會超時未響應,導致服務器掛掉
要設置file_get_contents函數的超時時間,可以用resource $context的timeout參數,代碼如下:
$opts = array( 'http'=>array( 'method'=>"GET", 'timeout'=>10, ) ); $context = stream_context_create($opts); $html =file_get_contents('http://www.example.com', false, $context); echo $html;
4、curl方法
實現的功能:
1、實現遠程獲取和采集內容
2、實現PHP 網頁版的FTP上傳下載
3、實現模擬登陸:去一個郵件系統,curl可以模擬cookies
4、實現接口對接(API),數據傳輸等:通過一個平台發送短信啊,抓取和傳遞所傳輸的信息。
5、實現模擬Cookie等:登陸的狀態下才可以操作一些屬性。
如何使用CURL功能:
默認情況加PHP是不支持CURL的,需要在php.ini中開啟該功能
;extension=php_curl.dll前面的分號去掉
1 整個操作過程中第一步是用cur_init()函數進行初始化
2.用curl_setopt()函數進行設置選項。
3.設置后,進行執行事務 curl_exec($curl);
4 最后關閉curl_close();
兼容get和post方法的curl;
function curl($url, $type = 'get', $post_data = null, $second = 30) { $ch = curl_init(); //設置超時 curl_setopt($ch, CURLOPT_TIMEOUT, $second); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // //設置header curl_setopt($ch, CURLOPT_HEADER, false); //要求結果為字符串且輸出到屏幕上 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); if ('post' == $type) { curl_setopt($ch, CURLOPT_POST, 1); //開啟POST curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); //POST數據 } $output = curl_exec($ch); curl_close($ch); return $output; //返回或者顯示結果 }