Безопасность при загрузке файлов на сервер в PHP
В прошлой статье мы с Вами разбирали загрузку файлов на сервер в PHP. Однако, я Вам уже сказал, что использовать код, который там был рассмотрен, категорически нельзя! И в этой статье мы поговорим о безопасности при загрузке файлов на сервер в PHP.
Давайте напомню код, который мы вчера рассматривали:
<?php
$uploadfile = "images/".$_FILES['somename']['name'];
move_uploaded_file($_FILES['somename']['tmp_name'], $uploadfile);
?>
Фактически, на данный момент может быть загружено абсолютно всё, что угодно: любые исполняемые файлы, скрипты, HTML-страницы и другие весьма опасные вещи. Поэтому обязательно надо проверять загружаемые файлы очень тщательно. И сейчас мы с Вами займёмся их тщательной проверкой.
Поскольку различных вариантов задач может быть очень много, мы рассмотрим вариант с загрузкой простого изображения, на которые должны накладываться следующие ограничения:
- Тип - только jpg (jpeg).
- Размер - менее 100 КБ.
Теперь реализуем скрипт "loading.php" в соответствии с этими требованиями:
<?php
$blacklist = array(".php", ".phtml", ".php3", ".php4", ".html", ".htm");
foreach ($blacklist as $item)
if(preg_match("/$item\$/i", $_FILES['somename']['name'])) exit;
$type = $_FILES['somename']['type'];
$size = $_FILES['somename']['size'];
if (($type != "image/jpg") && ($type != "image/jpeg")) exit;
if ($size > 102400) exit;
$uploadfile = "images/".$_FILES['somename']['name'];
move_uploaded_file($_FILES['somename']['tmp_name'], $uploadfile);
?>
Теперь давайте подробно поясню, что здесь происходит. Первым делом мы проверяем на расширение загружаемого файла. Если оно представляет собой PHP-скрипт, то мы такой файл просто не пропускаем. Дальше мы получаем MIME-type и размер. Проверяем их на удовлетворение нашим условиям. Если всё хорошо, то мы загружаем файл.
Вы, наверное, можете спросить: "А зачем надо проверять и расширение, и MIME-type?". Тут очень важно понимать, что это далеко не одно и то же. Если злоумышленник попытается отправить PHP-файл через браузер, то и одной проверки MIME-type хватит, чтобы его попытка провалилась. А вот если он напишет какой-нибудь скрипт, который будет формировать запрос и отсылать вредосный файл, то этого не хватит. Почему? А потому, что MIME-type задаётся клиентом, а не сервером! И фактически, злоумышленник может поставить любой MIME-type (и картинки тоже), но при этом отсылать PHP-скрипт. И вот именно такую хитрую попытку мы и ломаем, проверяя на расширение файла.
Я сразу скажу, что данный код далеко не 100% защита (100% просто не существует), однако, взломать такой код будет очень и очень тяжело, поэтому можете смело утверждать, что Вы обеспечили высокую безопасность при загрузке файлов на сервер через PHP.
-
- Михаил Русаков
Комментарии (47):
Столкнулся с такой проблемой. Если попытаться загрузить файл больше, чем "выдерживает" сервер (у меня на Denwer'e - 10Mb), то массив $_FILES вообще не заполняется, и проверить размер файла через $_FILES['somename']['size'] становится невозможным. Как тогда защитить свою форму от таких попыток?
Ответить
Никак. Это и не важно, так как никакой нагрузки на сервер всё равно не идёт. Такой файл просто даже не рассматривается, поэтому бояться не надо. Главное написать пользователям о максимальном размере файла для загрузки.
Ответить
А как сделать чтобы не только jpg принимался?
Ответить
Надо добавить при проверке mime-type и другие тоже. Например, image/png и/или image/gif.
Ответить
<span><span>То есть вот так?: <span id="dtx-highlighting-item" dtx-highlight-backgroundcolor="yellow"> if </span><span>(($type<span id="dtx-highlighting-item" dtx-highlight-backgroundcolor="cyan"> != </span>"</span></span><span id="dtx-highlighting-item" dtx-highlight-backgroundcolor="lime">image/jpg</span><span>,g<span id="dtx-highlighting-item" dtx-highlight-backgroundcolor="yellow">if</span><span><span>,bmp,png")<span id="dtx-highlighting-item" dtx-highlight-backgroundcolor="lime"> && </span>($type</span><span id="dtx-highlighting-item" dtx-highlight-backgroundcolor="cyan"> != </span>"image/jpeg,g</span><span id="dtx-highlighting-item" dtx-highlight-backgroundcolor="yellow">if</span>,bmp,png")) exit; Или как?</span></span> P.S. Только к gif png bmp привязать image/ .
Ответить
Нет. if ($type != "image/jpg") && ($type != "image/png") && ($type != "image/gif") и так далее
Ответить
Регулярные выражения очень медленные и лучше избегать ситуация использования их в цикле. поэтому предлагаю взять расширение регулярным выражением и потом просто прогнать через in_array где будет массив расширений. Так будет гораздо лаконичнее
Ответить
Здравствуйте,Михаил! Такая проблема:я поменял в Вашем скрипте путь сохранения файла с images на images_gallery,но загружаемые картинки всё равно упорно сохраняются в папку images... // <? $blacklist = array(".php", ".phtml", ".php3", ".php4", ".html", ".htm"); foreach ($blacklist as $item) if(preg_match("/$item$/i", $_FILES['somename']['name'])) exit; $type = $_FILES['somename']['type']; $size = $_FILES['somename']['size']; if (($type != "image/jpg") && ($type != "image/jpeg")) exit; if ($size > 102400) exit; $uploadfile = 'images_gallery/'.$_FILES['somename']['name']; move_uploaded_file($_FILES['somename']['tmp_name'], $uploadfile); ?> Подскажите,что я не так делаю? Заранее спасибо! p.s.Всё заработало!Я не понял,в чем прикол был....наверно,магнитные бури!))))
Ответить
таким способом $blacklist = array(".php", ".phtml", ".php3", ".php4", ".html", ".htm"); foreach ($blacklist as $item) if(preg_match("/$item\$/i", $_FILES['somename']['name'])) exit; мы защищаемся от дос атак
Ответить
Михаил,а как подставить редактор tiny MCE в свою админку?
Ответить
На официальном сайте есть подробная инструкция http://www.tinymce.com/wiki.php/Installation Если плохо знаете английский, то есть сайт http://translate.google.com
Ответить
Михаил,в меня токой вопрос,а в курсе с нуля до гуру Вы будите какой движок делать?
Ответить
Похожий по функциональности на этот сайт. Но задача курса не дать какие-то шаблоны, десяток скриптов и прочего, задача курса, чтобы Вы самостоятельно научились всё это делать. Например, в курсе не разобрано, как создать социальную сеть, но после курса Вы сможете её сделать.
Ответить
Михаил,я перечислил деньги за курс.Там проверьте свою почту там есть время оплаты и номер квитанции.Спасибо.
Ответить
Михаил,а после перечисление денег за курс Вам указывать что надо??
Ответить
Зависит от способа оплаты.
Ответить
Михаил,а как установить апачь сервер и создать свой сервер из компа?
Ответить
http://myrusakov.ru/php-make-server.html
Ответить
С момента написания статьи прошло больше года. Сейчас ты используешь дополнительную защиту для загрузки изображений? Какую? Можно пример :)
Ответить
Не использую.
Ответить
У меня папка для фото: foto Проверку mime пытаюсь выполнить так: $imageinfo = getimagesize($_FILES['somename']['tmp_name']); if($imageinfo["mime"] != "foto/gif" && $imageinfo["mime"] != "foto/GIF" && $imageinfo["mime"] != "foto/jpeg" ... Или нужно обязательно вот так: if($imageinfo["mime"] != "image/gif" && $imageinfo["mime"] != "image/GIF" && $imageinfo["mime"] != "image/jpeg" ...
Ответить
Mime-type foto не существует, поэтому только второй вариант.
Ответить
Понял. А что конкретно значит эта строка: $uploadfile = "images/".$_FILES['somename']['name']; move_uploaded_file($_FILES['somename']['tmp_name'], $uploadfile);
Ответить
Первая строка - это имя будущего файла, а вторая строка - это перемещение данных из временного файла в этот новый файл.
Ответить
Но это ещё не сохранение? Потому что у меня для этого существует свой каталог. Эти строки его нигде не скопируют?
Ответить
Это уже сохранение.
Ответить
Круто все объясняешь, молодец и спасибо тебе за это. Прочитал уже статьи про регулярные выражения, только не пойму $item\$ - это строки, которые заканчиваются на $item. а зачем тогда обратный слэш перед мета $? это мы его экранируем что ли? после переменных всегда так делается?
Ответить
Здравствуйте! Скрипт хорошо работает, спасибо вам. Только есть одно но... Если загружать файлы больше 100 кб, то экран становится белым и ничего там нет! Можно ли сделать вывод надписи вроде "Ошибка размер больше 100 кб!" или что-нибудь похожее?
Ответить
Можно, например, вместо exit можно поставить echo с соответствующим сообщением.
Ответить
Спасибо работает! А как сделать чтобы было сообщение о загрузке изображении. То есть если ошибка то пусть пишет "Error" а если загрузил то пусть пишет "ОК". Я полный Чайник в php, сижу с утра и не могу сделать это сам.
Ответить
Нужно освоить для начала оператор if, а затем move_uploaded_file($_FILES['somename']['tmp_name'], $uploadfile) - вот это поместить внутрь if, и если функция вернула true, значит, вывести, что файл загружен, иначе вывести ошибку.
Ответить
Спасибо большое все работает!
Ответить
$size > 102400 Здесь размер в битах показано?
Ответить
В байтах.
Ответить
Здравствуйте Михаил. У меня массив $_FILES пустой. В какой момент он заполняется и почему он может быть пустым ?
Ответить
Заполняется при отправке. Возможно, не указан атрибут enctype у формы.
Ответить
Спасибо, всё получилось. В enctype была опечатка.
Ответить
Здравствуйте Михаил. При попытке загрузить файл получаю сообщение: "Warning: POST Content-Length of 9956836 bytes exceeds the limit of 8388608 bytes in Unknown on line 0" Это ограничение метода POST или результат настроек сервера ?
Ответить
Это настройки сервера: http://myrusakov.ru/php-load-bigfile.html
Ответить
Привет, Михаил! Скрипт полезный весьма благодарин вам за это. Есть вопрос: Как сделать так чтоб все картинки при загрузке переименовывались в уникальные названия например вот этим способом: $token = md5(uniqid("")); $better_token = md5(uniqid(rand(),1)); Подскажите пожалуйста, куда его нужно прописать, чтоб применить этот способ вот в этот скрипт? Суть проблемы такова что при загрузке картинок сохроняются русские символы и при совпадении названия перезаписываются сами изображения
Ответить
Всё я сам решил! Добавил просто в скрипт следующий код: $uploadfile = "./uzer/images/".$_FILES['somename']['name']; $newfile = "./uzer/images/".$_FILES = md5(uniqid(rand(),1)).'.jpg'; rename($uploadfile, $newfile) or die("Unable to rename $uploadfile to $newfile.");
Ответить
Здравствуйте Михаил! У меня такой вопрос,можно ли при загрузке сделать чтобы файл переименовался? у меня не получается код вот такой: $type = $_FILES['filename']['type']; $size = $_FILES['filename']['size']; if ($size > 4194304) { echo("<b>Размер файла превышает четыре мегабайта</b><br>"); exit; } if (($type != "image/jpg") && ($type != "image/jpeg")&& ($type != "image/JPG")&& ($type != "image/JPEG")){ echo("<b>Вы можете загружать только jpg и jpeg</b><br>"); exit; } $uploaddir = './img/'; $file = $uploaddir . basename($_FILES['filename']['name']); if (move_uploaded_file($_FILES['filename']['tmp_name'], $file)) echo "<b>Файл загружен</b><br>"; else { echo "<b>Ошибка загрузки файла</b><br>";exit; }
Ответить
Можно, в move_uploaded_file() сразу и передаете нужное имя.
Ответить
Михаил, здравствуйте! Создавал хостинг изображений по бонусу из вашего курса PHP и MySQL с Нуля до Гуру, но возникла проблема: на странице вывода изображений выводится такая ошибка - "Fatal error: Call to private method Image::loadImage() from context '' in V:\home\localhost\www\myproject\show_image.php on line 3". 3 ряд совпадет с Вашим в обучающем видео. Как решить эту проблему?
Ответить
Ошибка может быть не обязательно в третьем ряду. Вообще интерпретатор Вам поясняет : Что Вы не можете получить доступ к защищенным методам из экземпляра класса. Чтобы получить доступ к защищенному методу родительского класса из экземпляра подкласса Вы должны объявить открытый метод в подклассе, а затем вызвать защищенный метод родительского класса от открытого метода подкласса.
Ответить
Почему пропускает файл mp4 после чего выводится 2 ошибки: 1 - Warning: POST Content-Length of 130113051 bytes exceeds the limit of 8388608 bytes in Unknown on line 0 2 - Warning: session_start() [function.session-start]: Cannot send session cache limiter - headers already sent in Z:\home\**********\www\*********\controller.php on line 6 Как этого избежать?
Ответить
У меня работает этот код и мне все в нем понятно, кроме одного. Мой вопрос, наверно, покажется странным - вполне возможно, ответ на него был сказан в статьях раньше, но я по невнимательности его пропустила. Скажите, почему написано /$item\$/i, а не /$item/i? Я немного почитала про регулярные выражения на других ресурсах, но мне как-то трудно дается эта тема.
Ответить
Для добавления комментариев надо войти в систему.
Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.