Главная | Контакты



Главная > Программирование > PHP

Защита файлов на сервере от прямых ссылок (antileech)

Защита от скачивания файлов по прямым ссылкам, или как ее еще называют "антилич-защита" - задача не менее важная, чем защита остального контента сайта. Грамотно сделанная система защиты от прямых ссылок позволит разграничивать доступ к файлам разным категориям пользователей, вести подробную статистику скачиваний, скрывать истинное место размещения файлов, а также привлечет новых посетителей на ваш сайт. А то обычно получается так, что сторонние сайты публикуют прямую ссылку на файл, размещенный на вашем сервере, они получают посетителей, рейтинги, стригут купоны с рекламы, а вы получаете только счета за трафик. Справедливое разделение? Нет. В последнее время появилось огромное количество файлообменных сервисов, и на каждом из них используется своя система антилич-защиты, где-то более удачно реализованная, где-то менее. За счет этого они имеют возможность регулировать скорость отдачи файлов премиум-пользователям и халявщиками, определять лимит на скачивание по времени, поддерживать или не поддерживать докачку и т.д. В этой статье я расскажу о самом принципе построения антилич-системы, а также приведу пример простейшего, но вполне работоспособного скрипта.

Сперва начнем с изучения предмета. Посмотрим лог работы какого-нибудь менеджера закачки, который скачивает обычный файл с обычного web-сервера. Конкретно нас интересуют служебные заголовки, которые сервер отдает менеджеру закачки или браузеру:

HTTP/1.1 200 OK
Date: Sat, 15 Aug 2009 17:24:40 GMT
Server: Apache/1.3.41 (Win32)
Connection: close
Content-Type: application/octet-stream
Accept-Ranges: bytes
Content-Disposition: Attachment; filename=filename.rar
Content-Length: 2676708

В заголовках есть вся нужная информация: тип файла, его имя, размер файла. После этого идет непосредственно содержимое файла. Если сервер отдает такие заголовки, значит их точно так же может отдавать и наш PHP-скрипт через функцию header.

Напишем простейший скрипт и попробуем открыть его в браузере или добавим ссылку на него в менеджер закачек:

<?
Header("HTTP/1.1 200 OK");
Header("Connection: close");
Header("Content-Type: application/octet-stream");
Header("Accept-Ranges: bytes");
Header("Content-Disposition: Attachment; filename=filename.dat");
Header("Content-Length: 50000");
?>
В любом случае начнется скачивание файла filename.dat размером 50000 байт, состоящего из восклицательных знаков. Мы только что "на лету" сгенерировали файл, которого физически на сервере не существует. Таким образом мы можем генерировать "виртуальные" файлы и отдавать их пользователю. Это могут быть, например, созданные скриптами CSV-файлы, картинки, архивы, сгенерированные PDF-файлы и другой контент. Тут главное правильно указывать имя файла и размер передаваемых данных, тип рекомендуется всегда оставлять application/octet-stream. Главный недостаток этого способа в том, что не поддерживается докачка в случае обрыва соединения, но он лучше всего подходит для небольших, до нескольких сот килобайт, файлов.

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

<?
# $fname=$_GET['fname'];
# $fsize=filesize('secret_data/'.$fname);
#  
# Header("HTTP/1.1 200 OK");
# Header("Connection: close");
# Header("Content-Type: application/octet-stream");
# Header("Accept-Ranges: bytes");
# Header("Content-Disposition: Attachment; filename=".$fname);
# Header("Content-Length: ".$fsize);
#  
# // Открыть файл для чтения и отдавать его частями
# $f=fopen('secret_data/'.$fname,'r');
# while (!feof($f)) {
#   // Если соединение оборвано, то остановить скрипт
#   if (connection_aborted()) {
#     fclose($f);
#     break;
#   }
#   echo fread($f,10000);
#   // Пазуа в 1 секунду. Скорость отдачи 10000 байт/сек
#   sleep(1);
# }
# fclose($f); 
?>

Ссылка на закачку в этом случае будет выглядеть примерно так:

http://server/getfile.php?fname=secret_file.rar

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

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

GET /filename.rar HTTP/1.0
User-Agent: Download Master
Accept: */*
Referer: http://server.com/
Range: bytes=2730210-
Pragma: no-cache
Cache-Control: no-cache
Host: server.com

В поле Range передается смещение от начала файла, с которого начинается или продолжается закачка. На сервере это поле преобразуется в переменную окружения HTTP_RANGE. Чтобы скрипт поддерживал докачку, надо проверять установлена ли эта переменная, и если она имеет непустое значение, то читать файл и отдавать данные с указанной позиции. В этом случае заголовки немного изменятся:

HTTP/1.1 206
Date: Sat, 15 Aug 2009 20:41:27 GMT
Server: Apache/1.3.41 (Win32)
Accept-Ranges: bytes
Connection: close
Content-Disposition: Attachment; filename=filename.rar
Content-Range: bytes 9902214-13807860/13807861
Content-Length: 3905647
Content-Type: application/octet-stream

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

<?
# $fname=$_GET['fname'];
# $fsize=filesize('secret_data/'.$fname);
# $fdown='secret_data/'.$fname;
#  
# // Установлена или нет переменная HTTP_RANGE
# if (getenv('HTTP_RANGE')=="") {
#   // Читать и отдавать файл от самого начала
#   $f=fopen($fdown, 'r');
#  
#   header("HTTP/1.1 200 OK");
#   header("Connection: close");
#   header("Content-Type: application/octet-stream");
#   header("Accept-Ranges: bytes");
#   header("Content-Disposition: Attachment; filename=".$fname);
#   header("Content-Length: ".$fsize); 
#  
#   while (!feof($f)) {
#     if (connection_aborted()) {
#       fclose($f);
#       break;
#     }
#     echo fread($f, 10000);
#     sleep(1);
#   }
#   fclose($f);
# }
# else {
#   // Получить значение переменной HTTP_RANGE
#   preg_match ("/bytes=(\d+)-/", getenv('HTTP_RANGE'), $m);
#   $csize=$fsize-$m[1];  // Размер фрагмента
#   $p1=$fsize-$csize;    // Позиция, с которой начинать чтение файла
#   $p2=$fsize-1;         // Конец фрагмента
#  
#   // Установить позицию чтения в файле
#   $f=fopen($fdown, 'r');
#   fseek ($f, $p1);
#  
#   header("HTTP/1.1 206 Partial Content");
#   header("Connection: close");
#   header("Content-Type: application/octet-stream");
#   header("Accept-Ranges: bytes");
#   header("Content-Disposition: Attachment; filename=".$fname);
#   header("Content-Range: bytes ".$p1."-".$p2."/".$fsize);
#   header("Content-Length: ".$csize);
#  
#   while (!feof($f)) {
#     if (connection_aborted()) {
#       fclose($f);
#       break;
#     }
#     echo fread($f, 10000);
#     sleep(1);
#   }
#   fclose($f);
# } 
?>

Теперь скрипт поддерживает докачку в случае обрыва связи и закачку файла в несколько потоков. Можно начинать добавлять к нему всякие навороты: например, проверять регистрацию или права доступа пользователя, регулировать количество соединений с одного ip-адреса, поддерживать докачку, изменять скорость отдачи для премиум-пользователей, проверять cookies после посещения основного сайта и все, что только можно придумать. Это я оставляю целиком на вашу фантазию. Последний штрих - преобразование ссылок через файл .htaccess и модуль Apache mod_rewrite. Например, ссылка будет иметь вид:

http://server/download/secret_file.rar

В файле .htaccess прописано правило для mod_rewrite:

RewriteEngine on
Options +FollowSymlinks
RewriteRule ^download\/(.*)$ getfile.php?fname=$1 [L]

И ссылка преобразуется в уже знакомую нам

http://server/getfile.php?fname=secret_file.rar

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

Options -Indexes
Deny from all

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

В приложении рабочий скрипт антилича из этой статьи с настроенной структурой папок. Запускать его, естественно, надо только на локальном или удаленном web-сервере с поддержкой mod_rewrite и .htaccess


Antileech.Demo.zip (59,564 bytes)

Материал взят с сайта: http://www.manhunter.ru/webmaster/179_zaschita_faylov_na_servere_ot_pryamih_ssilok_antileech.html

Главная > Программирование > PHP