Загрузка файлов на сервер с помощью PHP. Основные уязвимости и способы их избежать.
В прошлой статье я рассказал Вам о механизме загрузки файлов на сервер при помощи PHP. В той статье я лишь раскрыл сам процесс загрузки и не касался вопросов безопасности.
Часто загрузка файлов без обеспечения надлежащего контроля безопасности приводит к образованию уязвимостей, которые, как показывает практика, стали настоящей проблемой в веб-приложениях на PHP.
Если Вы не обеспечите необходимый уровень безопасности, то злоумышленник сможет закачать произвольный файл на сервер, например, php-скрипт, при помощи которого он сможет просмотреть любой файл на сервере или что еще хуже выполнить произвольный код!
Поэтому в этой статье я постараюсь рассказать об основных уязвимостях веб-приложений по загрузке файлов на сервер и способах их избежать.
Итак, приступим. Первое что приходит в голову каждому разработчику это проверять Content-Type файлов. Другими словами — разрешить загрузку файлов строго определенного типа. Давайте взглянем на код:
<?php if(isset($_POST['upload'])){ if($_FILES['uploadFile']['type'] != "image/gif") { echo "Ошибка, Вы можете загружать только gif картинки"; exit; } $folder = 'path/to/folder/'; $uploadedFile = $folder.basename($_FILES['uploadFile']['name']); if(is_uploaded_file($_FILES['uploadFile']['tmp_name'])){ if(move_uploaded_file($_FILES['uploadFile']['tmp_name'], $uploadedFile)){ echo Файл загружен; } else { echo "Во время загрузки файла произошла ошибка"; } } else { echo "Файл не загружен"; } } ?>
Если обычный пользователь попытается загрузить любой другой файл, кроме gif-картинки, то ему будет выдано предупреждение!Но злоумышленник не будет использовать веб-форму на Вашем сайте.
Он может написать небольшой Perl-скрипт(возможно на любом языке), который будет эмулировать действия пользователя по загрузке файлов, дабы изменить отправляемые данные на свое усмотрение.Так как проверяемый MIME-тип приходит вместе с запросом, то ничего не мешает злоумышленнику установить его в «image/gif», поскольку с помощью эмуляции клиента он полностью управляет запросом, который посылает.
Если вы загружаете только изображения,то не стоит доверять заголовку Content-Type, а лучше проверить фактическое содержание загруженного файла, чтобы удостовериться что это действительно изображение. Для этого в РНР очень часто используют функцию getimagesize().
Функция getimagesize() определяет размер изображения GIF, JPG, PNG, SWF, PSD, TIFF или BMP и возвращает размеры, тип файла и высоту/ширину текстовой строки, используемой внутри нормального HTML-тэга IMG.
Давайте посмотрим, как можно использовать эту функцию в нашем скрипте:
<?php if(isset($_POST['upload'])){ $imageinfo = getimagesize($_FILES['uploadFile']['tmp_name']); if($imageinfo['mime'] != 'image/gif' && $imageinfo['mime'] != 'image/jpeg'){ echo "Вы можете загружать только gif и jpeg картинки!"; exit; } $folder = 'path/to/folder/'; $uploadedFile = $folder.basename($_FILES['uploadFile']['name']); if(is_uploaded_file($_FILES['uploadFile']['tmp_name'])){ if(move_uploaded_file($_FILES['uploadFile']['tmp_name'], $uploadedFile)){ echo Файл загружен; } else { echo "Во время загрузки файла произошла ошибка"; } } else { echo "Файл не загружен"; } } ?>
Можно подумать, что теперь мы можем пребывать в уверенности, что будут загружаться только файлы GIF или JPEG. К сожалению, это не так. Файл может быть действительно в формате GIF или JPEG, и в то же время PHP-скриптом. Большинство форматов изображения позволяет внести в изображение текстовые метаданные. Возможно создать совершенно корректное изображение, которое содержит некоторый код PHP в этих метаданных. Когда getimagesize() смотрит на файл, он воспримет это как корректный GIF или JPEG. Когда транслятор PHP смотрит на файл, он видит выполнимый код PHP в некотором двоичном «мусоре», который будет игнорирован.
Вы наверное спросите, а почему бы не проверять просто расширение файла? Если мы не позволим загружать файлы *.php, то сервер никогда не сможет выполнить этот файл как скрипт. Давайте рассмотрим и этот подход.
Вы можете составить белый список расширений и проверять имя загружаемого файла на соответствие белому списку.
<?php if(isset($_POST['upload'])){ $whitelist = array(".gif", ".jpeg", ".png"); $error = true; //Проверяем разрешение файла foreach ($whitelist as $item) { if(preg_match("/$item\$/i",$_FILES['userfile']['name'])) $error = false; } if($error) die("Ошибка, Вы можете загружать только gif,jpeg,png картинки"); $folder = 'path/to/folder/'; $uploadedFile = $folder.basename($_FILES['uploadFile']['name']); if(is_uploaded_file($_FILES['uploadFile']['tmp_name'])){ if(move_uploaded_file($_FILES['uploadFile']['tmp_name'], $uploadedFile)){ echo Файл загружен; } else { echo "Во время загрузки файла произошла ошибка"; } } else { echo "Файл не загружен"; } } ?>
Выражение !preg_match ("/$item\$/i", $_FILES['uploadFile']['name']) проверяет соответствие имени файла, определенному пользователем в массиве белого списка. Модификатор «i» говорит, что наше выражение регистронезависимое. Если расширение файла соответствует одному из пунктов в белом списке, файл будет загружен, иначе скрипт выдаст ошибку!
Как видите доверять расширению и Content-Type файла нельзя. Поэтому необходимо в каталог, куда закачиваются все Ваши файлы добавить файл .htaccess куда прописать следующие строки:
RemoveHandler .php .php5 .php4 .php3 .phtml .pl AddType text/plain .php .php .htm .html .phtml .pl
Данные строки в .htaccess заставит сервер не исполнять php и др. файлы, а выводить их содержимое на экран.
На этом все! Удачи!
-
Комментарии (6)
- Сайт