說明:任意文件上傳漏洞,很多PHP開發者也會做一些簡單的防護,但是這個防護有被繞過的可能。 原生漏洞PHP示例代碼: $file = $_FILES['file'] ?? []; //檢測文件類型 $allow_mime = ['image/jpg', 'image/jpeg', 'image/pn ...
說明:任意文件上傳漏洞,很多PHP開發者也會做一些簡單的防護,但是這個防護有被繞過的可能。
原生漏洞PHP示例代碼:
$file = $_FILES['file'] ?? [];
//檢測文件類型
$allow_mime = ['image/jpg', 'image/jpeg', 'image/png', 'image/gif'];
if(! in_array($file['type'], $allow_mime)) {
echo json_encode(['code' => 1, 'msg' => "文件類型錯誤"], JSON_UNESCAPED_UNICODE);
return;
}
print_r($file);
上傳一個PHP文件,提示文件類型錯誤,使用ApiPost修改上傳的Content-Type,把原先的application/x-httpd-php修改為image/png,則可繞過。
因為:$_FILES['type']是根據上傳文件的content-type獲取的,並文件本身的mime-type,而content-type又可以被篡改。
原生漏洞PHP漏洞優化意見(獲取臨時文件的真實類型):
$file = $_FILES['file'] ?? [];
//檢測文件類型
$allow_mime = ['image/jpg', 'image/jpeg', 'image/png', 'image/gif'];
if(! in_array((new \finfo(\FILEINFO_MIME_TYPE))->file($file['tmp_name']), $allow_mime)) {
echo json_encode(['code' => 1, 'msg' => "文件類型錯誤"], JSON_UNESCAPED_UNICODE);
return;
}
print_r($file);
對Laravel框架,也有同樣的問題,別用錯函數:
$file->getClientMimeType(); //相當於$_FILES['file']['type'];
$file->getMimeType(); //相當於(new \finfo(\FILEINFO_MIME_TYPE))->file($_FILES['file']['tmp_name'])
說話得有依據,經過反覆的追Laravel的源碼:
底層對getClientMimeType()的實現:
是在vendor/symfony/http-foundation/Request.php的createFromGlobals()中,基於$_FILES做的封裝。
底層對getMimeType()的實現:
是在vendor/symfony/mime/FileinfoMimeTypeGuesser.php的guessMimeType()中,利用finfo的內置PHP類實現的。
對Laravel任意文件上傳漏洞優化意見(獲取臨時文件的真實類型):
使用getMimeType函數。
整體修複意見:
先判斷文件尾碼,在判斷臨時文件的mime類型屬性,不要根據請求頭判斷。
擴展:
mime_content_type函數與(new \finfo(\FILEINFO_MIME_TYPE))->file('file_path')的區別?
檢測文件mime類型,還有一個mime_content_type();
- mime_content_type()獲取的mime類型,會與操作系統的mime類型有映射,意味著不同的系統可能存在一些小差別。
- finfo類使用了 PHP 的 FileInfo 擴展。FileInfo 擴展利用了文件的特征簽名(或稱為魔術數字)來檢測文件的實際類型,並根據文件的內容進行精確的 MIME 類型推斷。
雖然兩者相差不大,但是推薦用(new \finfo(\FILEINFO_MIME_TYPE))->file('file_path');
(new \finfo(\FILEINFO_MIME_TYPE))->file('file_path')與finfo_file()的區別?
使用finfo_file()也可以獲取文件的mime類型。
$mime = finfo_file(finfo_open(FILEINFO_MIME_TYPE), $file['tmp_name']);
$mime = (new \finfo(FILEINFO_MIME_TYPE))->file($file['tmp_name']);
兩者底層對獲取mime類型的實現無差別,展示寫法不同。
什麼是文件的魔術數字?
文件的魔術數字是文件頭部的一段特定的位元組序列,用來描述文件的類型或格式,一般用16進位表示。
文件的魔術數字一般包含一些特殊的字元和數字組成的固定長度的位元組串,不同類型的文件具有不同的魔術數字。例如,PNG 圖像文件的魔術數字為 89 50 4E 47 0D 0A 1A 0A,而 JPG 圖像文件的魔術數字為 FF D8 FF E0 00 10 4A 46 49 46 00 01。
PHP獲取魔術數字實現方案:
$fileHandle = fopen($_FILES['file']['tmp_name'], 'rb');
$hex = '';
while (! feof($fileHandle)) {
$byte = fread($fileHandle, 1);
$hex .= sprintf("%02X ", ord($byte));
}
fclose($fileHandle);
echo $hex;