Сколько дискуссий было на Хабре по поводу опасности заливки файлов, обсуждались возможные уязвимости при заливке файлов и.т.д. Я тоже решил попробовать помочь в этом вопросе Вам, уважаемые читатели.
Как Вы все уже хорошо знаете, самый надежный способ защиты при заливке файлов, это:
1. Переименовывать файлы в имена и расширения файлов независимо от входящего имени файла.
2. Отключить в папке, куда заливаются файлы, выполнение скриптов.
Этот вариант отлично подходит для большинства случаев, когда заливаемый файл нужно использовать на сайте (например: аватар пользователя) и он требует записи в БД для привязки. Реализуется легко и об этом уже на Хабре есть статьи.
Мы же с Вами рассмотрим ситуацию, когда нужно сделать безопасную заливку файлов, без использования БД, с сохранением оригинальных имен и с возможностью заливать что угодно (без каких либо ограничений) с последующей возможностью скачивания данных файлов по URL.
Для лучшего восприятия сейчас реализуем примитивный файлообменник. Для этого нам понадобится: папка, назовем ее, например «files», и 2 скрипта «upload.php» и «get.php».
В скрипте «upload.php» реализуем заливку файла с преобразованием имени в hex-код. Т.е. например, если пользователь решил залить файл «index.php» то он нормально зальется в папку, но с именем «696e6465782e706870» что полностью безопасно для сервера и не выполнится PHP-обработчиком.
Вот скрипт «upload.php»:
<?
$uploaddir = "files/";
function StringToHex($s)
{
$hex = "";
for ($i=0;$i<strlen($s);$i++) $hex=$hex.dechex(ord($s[$i]));
return $hex;
}
if (isset($_FILES['userfile']))
{
// Преобразуем имя файла в hex
$new_name = StringToHex(strtolower($_FILES['userfile']['name']));
if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploaddir . $new_name))
{
// Ok...
} else {
// Error...
}
}
?>
<html>
<body>
<form enctype="multipart/form-data" method="post">
<input name="userfile" type="file" /><input type="submit" value="Сохранить" />
</form>
</body>
</html>
Сейчас займемся папкой, куда будут попадать файлы. Первым делом поставим ей права 777 и создадим в ней файл «.htaccess»:
RewriteEngine on
RewriteRule (.*) get.php?file=$1 [L,QSA]
Ну и осталось добавить в папку «files» скрипт «get.php», который будет отдавать файлы пользователю, для наглядности я в него добавил возможность просмотра файлов в папке с их реальными именами (ответ для тех читателей, которые спросят типа «а зачем все это было, если можно было имена шифровать в md5» и.т.д.).
<?
function StringToHex($s)
{
$h = "";
for ($i=0;$i<strlen($s);$i++) $h=$h.dechex(ord($s[$i]));
return $h;
}
function HexToString($h)
{
$s = "";
for ($i=0;$i<strlen($h)-1;$i+=2) $s=$s.chr(hexdec($h[$i].$h[$i+1]));
return $s;
}
if (empty($_GET["file"]))
{
// Для наглядности (вывод содержимого папки)
if ($dh = opendir("."))
while (($file = readdir($dh)) !== false)
{
if (preg_match("/^[a-z0-9]+$/", $file))
echo '<a href="',HexToString($file),'">',HexToString($file),'</a><br />';
}
closedir($dh);
exit;
}
$file_to_user = $_GET["file"];
$file_name = StringToHex(strtolower($_GET["file"]));
if (file_exists($file_name))
{
// Если имя файла поле преобразования совпало с существующим файлом - отдадим его пользователю на скачивание
if (ob_get_level()) ob_end_clean();
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.basename($file_to_user));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: '.filesize($file_name));
readfile($file_name);
}
?>
Таким образом, для пользователей сайта всегда будут доступны файлы (на скачивание), залитые в папку по нормальным ссылкам:
http://site.xx/files/img.gif
http://site.xx/files/index.php
http://site.xx/files/jquery.js
http://site.xx/files/readme.txt
В реальности же в папке «files» будут файлы с именами:
696e6465782e706870
696d672e676966
6a71756572792e6a73
726561646d652e747874
Ну, вот собственно и все. Естественно есть куча других вариантов реализации защищенной заливки, особенно красиво все можно реализовать с использованием БД, я же решил предоставить свой метод приема/отдачи файлов в их естественном виде, без запретов на скрипты, и при этом с безопасностью для сайта. Для более высокой защиты (например: от повторных имен файлов, и.т.д.) скрипты нужно улучшать, тут только базовая идея реализована.
Автор: bmmshayan