Представим себе ситуацию, когда клиенту нужно предоставить возможность изменять часть содержимого веб-сайта — например, последние новости об их компании. Одним из способов сделать это состоит в разрешении клиенту загружать текстовые файлы с информацией. Затем эти файлы будут доступны на сайте через шаблон, разработанный на PHP.
Для этого разберемся, как происходит загрузка файлов на сервер.
Загрузка файлов на сервер
В PHP доступна очень полезная функциональность — поддержка загрузки файлов через HTTP-протокол. Вместо того, чтобы принимать файлы через HTTP-протокол с сервера в браузер, мы пересылаем их в обратном направлении, то есть с браузера на сервер. Обычно для этого применяются HTML-формы. Форму, которую будем использовать мы, показана на рисунке.
Как видно на рисунке, форма содержит поле ввода, в котором пользователь может ввести имя файла, или щелкнув по кнопке "Обзор", чтобы выбрать файлы, доступные на своей локальной машине. Возможно, вы ранее не видели форму загрузки файлов. Ниже мы покажем как ее реализовать.
После ввода имени файла пользователь может щелкнуть по кнопке "Послать файл", и файл загрузится на сервер, где его ожидает PHP-сценарий.
Для реализации загрузки файлов на сервер применяются некоторые конструкции языка HTML, специально предназначенные для этой цели.
upload.html — HTML-форма для загрузки файлов
<html>
<head>
<title>Администрирование - загрузка новых файлов</title>
</head>
<body>
<h1>Загрузка новых файлов с новостями</h1>
<form action="upload.php" method="post" enctype="multipart/form-data" target="_blank">
<input name="MAX_FILE_SIZE" type="hidden" value="1000000">
Загрузить файл <input name="userfile" type="file">
<input name="" type="submit" value="Послать файл">
</form>
</body>
</html>
Обратите внимание, что в этой форме используется метод POST. Загрузку файлов можно осуществлять с помощью метода PUT, поддерживаемого инструментами Netscape Composer и Amaya, однако при этом придется внести существенные изменения в код. Упомянутые инструменты не поддерживают метод GET.
Рассмотрим особенности этой формы.
- В дескрипторе <form> необходимо установить атрибут enctype="multipart/form-data" для извещения сервера, что вместе с обычной информацией формы посылается и файл.
- Форма должна содержать поле, в котором задан максимальный размер загружаемого файла. Это скрытое поле, и оно представлено с помощью HTML-дескриптора:
<input name="MAX_FILE_SIZE" type="hidden" value="1000000">
Именем этого поля формы должно быть MAX_FILE_SIZE. Его значение —это максимальный размер (в байтах) файлов, разрешенный для загрузки. Здесь он установлен 100000 байт (1 Мбайт). В нашем приложении его можно сделать меньшим или большим. - В форме должно присутствовать поле ввода с типом file, у нас оно задано следующим образом:
<input name="userfile" type="file">
Имя поля ввода можно выбрать любое, надо лишь помнить его, так как это имя будет использовано в принимающем PHP-сценарии для доступа к файлу.
Прежде чем двигаться дальше, стоит напомнить, что у некоторых версий PHP в коде загрузки присутствуют бреши в безопасности. Если вы решите пользоваться загрузкой файлов на свой рабочий сервер, нужно удостовериться, что у вас установлена самая последняя версия PHP, и следить за выходом правок и обновлений.
Это не должно стать причиной отказа от такой полезной технологии, но при написании кода нужно проявлять осторожность и стараться ограничить доступ всем, кроме администратора сайта и менеджеров содержимого.
Сценарий для работы с файлами
PHP-код загрузки файлов очень прост, однако он зависит от используемой версии PHP и настроек конфигурации. Имена функций и переменных изменяются от версии к версии и зависят от того, включена ли настройка register_globals. Представленный код не требует register_globals, но требует импользования версии PHP не ниже 4.1.
Файл при загрузке помещается в место, отведенное на веб-сервере для временных файлов. По умолчанию это главный временный каталог веб-сервера. Если файл не переименовывать и не переместить до окончания выполнения сценария, он будет уничтожен.
Данные, которые должны обрабатываться в нашем PHP-сценарии, храняться в суперглобальном массиве $_FILES. Если register_globals включен, к данным возможен и непосредственный доступ через имена переменных. Однако здесь, пожалуй, как раз то место, где лучше отключить register_globals и работать с данными через суперглобальный массив.
Элементы в массиве $_FILES будут сохранены с именем дескриптора <file> из вашей HTML-формы. Поскольку элемент формы имеет имя userfile, содержимое массива выглядит следующим образом:
- Значение, хранимое в $_FILES ['userfile']['tmp_name'], представляет собой место временного хранения файла на веб-сервере.
- Значение, хранимое в $_FILES ['userfile']['name'], является имнем файла в системе пользователя.
- Значение, хранимое в $_FILES ['userfile']['size'], указывает размер файла в байтах.
- Значение, хранимое в $_FILES ['userfile']['type'], содержить MIME-тип файла, например, text/plain или image/gif.
- Значение, хранимое в $_FILES ['userfile']['error'], будет содержать код ошибки, возникшей во время загрузки файла.
Теперь, когда известно, где находится файл и как он называется, можно скопировать его в более полезное место. Временный файл по окончании выполнения сценария будет удален. Значит, если требуется сохранить файл, его надо переместить или переименовать.
В нашем пимере предполагается, что загружаемые файлы представляют статьи с новостями, поэтому нужно удалить из них все возможные HTML-дескрипторы и перенести в более подходящий каталог.
upload.php — PHP-сценарий приема файла от HTML-формы
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Загрузка . . .</title>
</head>
<body>
<h1>Загрузка файла . . . </h1>
<?php
if ($_FILES['userfile']['error'] > 0)
{
echo 'Проблема: ';
switch ($_FILES['userfile']['error'])
{
case 1: echo 'размер файла больше uoload_max_filesize' ; break;
case 2: echo 'размер файла больше max_file_size'; break;
case 3: echo 'загружена только часть файла'; break;
case 4: echo 'файл не загружен'; break;
}
exit;
}
// Проверка, имеет ли файл правильный MIME-тип
if ($_FILES ['userfile']['type'] != 'text/plain')
{
echo 'Проблема: файл не является текстовым';
exit;
}
// Помещаем файл туда, куда нужно
$upfile = '/uploads/'.$_FILES['userfile']['name'];
if ($_FILES['userfile']['tmp_name'])
{
if (!move_uploaded_file($_FILES['userfile']['tmp_name'], $upfile))
{
echo 'Проблема: невозможно переместить файл в каталог назначения';
exit;
}
}
else
{
echo 'Проблема: возможна атака через загрузку файла. Файл: ';
echo $_FILES['userfile']['name'];
exit;
}
echo 'Файл успешно загружен. <br><br>';
// Переформатирование содержимого файла
$fp = fopen($upfile, 'r');
$contents = fread ($fp, filesize ($upfile));
fclose ($fp);
$contents = strip_tags ($contents);
$fp = fopen ($upfile, 'w');
fwrite ($fp, $contents);
fclose($fp);
// Вывод загружаемого файла
echo 'Предварительный просмотр содержимого загруженного файла: <br><hr>';
echo $contents;
echo '<br><hr>';
?>
</body>
</html>
Большую часть сценария составляют проверки на предмен возникновения ошибок. Загрузка файла сопряжена с потенциальным риском нарушения безопасности, и этот риск должен быть сведен к минимуму. Нужно как можно более тщательно проверить файл, чтобы убедиться в безопасности его отображения посетителю.
Посмотрим, какие основные части содержит сценарий.
Сначала проверяется код ошибки, возвращаемый в $_FILES['userfile']['error'] . Каждому коду ошибки соответствует специальная константа. Возможные константы и их значения перечислены ниже:
- UPLOAD_ERROR_OK — равна 0, означает, что ошибок не было.
- UPLOAD_ERR_INI_SIZE —равна 1, означает, что размер загружаемого файла превышает максимальное значение, заданное в файле php.ini директивой up_load_max_filesize.
- UPLOAD_ERR_FORM_SIZE —равна 2, означает, что размер загружаемого файла превышает максимальное значение, заданное в HTML-форме элементом MAX_FILE_SIZE.
- UPLOAD_ERR_PARTIAL —равна 3, означает, что загружена только часть файла.
- UPLOAD_ERR_NO_FILE —равна 4, означает, что файл не загружен.
Далее мы проверяем MIME-тип. В данном случае мы решили, что будем загружать только текстовые файлы, поэтому MIME-тип контролируется путем сравнения $_FILES ['userfile']['type'] со строкой 'text/plain'. Это только проверка на предмет ошибки, а не проверка, связанная с безопасностью. MIME-тип определяется браузером пользователя на основе расширения файла, а затем передается серверу. Поскольку достаточно несложно передать ложный MIME-тип, злоумышленники вполне могут воспользоваться этим.
Затем мы проверяем, что файл действительно загружен и не является локальным файлом вроде /etc/passwd. Несколько позже мы вернемся к этому вопросу.
Если все нормально, то мы копируем файл в предназначенный для него каталог. В данном примере это каталог /uploads/ —он находится за пределами дерева веб-документов и потому удобен для помещения в него тех файлов, которые впоследствии будут куда-нибудь включаться.
Затем мы обрабатываем файл, удаляем из него все случайные HTML и PHP-дескрипторы с помощью функции strip_tags() и записываем файл обратно.
И наконец содержимое файла отображается на экране, чтобы пользователь убедился, что загрузка файла успешно завершена.
Результат успешного выполнения сценария выглядит так:
В сентябре 2000 г. появилось сообщение о разработке, при помощи которой взломщики могут переключать сценарий загрузки на обработку локального файла вместо загруженного. Разработка задокументирована в списке рассылки BUGTRAQ. Официальные рекомендации по обеспечению безопасности опубликованы во множестве архивов BUGTRAQ, в частности, на странице http://seclists.org/bugtraq/2000/Sep/index.html.
С целью проверки успешного завершения загрузки файла и гарантии того, что мы имеем дело не с локальным файлом наподобие /etc/passwd, была применена функция move_uploaded_file() и также можно использовать is_uploaded_file().
Небрежно написанный сценарий загрузки может позволить посетителю с недобрыми намерениями создать временный файл с определенным именем и заставить сценарий обрабатывать его как загруженный. А поскольку многие сценарии загрузки возвращают пользователю копию загруженного файла либо размещают в месте, из которого ее можно загрузить, у пользователей появляется возможность доступа к любому файлу, доступному для считывания веб-сервером. Среди этих файлов могут оказаться файлы с конфиденциальной информацией — например, /etc/passwd или файл исходного РНР-кода, содержащий пароли доступа в базу данных.
Часто встречающиеся проблемы
При загрузке файлов на сервер следует принимать во внимание несколько важных моментов.
- В предыдущем примере предполагается наличие проверки прав доступа пользователей. Нельзя разрешать загрузку файлов на сервер кому угодно.
- Если разрешить загрузку файлов на сервер без проверки прав доступа либо пользователям, не заслуживающим доверия, возникают очень веские основания для беспокойства за содержимое этих файлов. Загрузка и запуск сценария, способного нанести вред — события крайне нежелательные. Поэтому следует проверять не только тип и содержимое файла, как в приведенном примере, но также и его имя. Очень неплохая мысль — присваивать загруженным файлам "безопасные" имена.
- Если ваша машина работает под управлением Windows, то везде в пути к файлу вместо \ нужно использовать \\ или /.
- Использование имен файлов, введенных пользователями, как это было рассмотрено в сценарии, чревато возникновением разнообразных проблем. Наиболее очевидная из них связана с риском случайной перезаписи существующих файлов при совпадении имен. Менее очевидная проблема состоит в том, что различные операционные системы и даже языковые настройки разрешают использовать различающиеся наборы символов в именах файлов. Загружаемый файл может иметь имя, которое содержит символы, недопустимые в вашей системе.
- Если возникают затруднения с запуском этой схемы, проверьте файл php.ini. Он должен содержать директиву upload_tmp_dir, задающую каталог, к которому необходимо получить доступ. Для загрузки файлов больших объемов может понадобиться также настройка директивы memory_limit, которая определяет максимально допустимые размеры загружаемых файлов. В Apache присутствуют еще и конфигурируемые времена тайм-аута и пределы размера транзакции, на которые также следует обратить внимание в случае возникновения проблем.
Комментарии(0)
Для добавления комментариев надо войти в систему и авторизоватьсяКомментирование статей доступно только для зарегистрированных пользователей:Зарегистрироваться