Форумы / National / Russian / Закрытие файлов

dervan
#8338 19.02.2009 11:33
Ratibor, разбирался я как-то с этим вопросом.

Про реализацию на основе ndl-2003-05-19_10.zip. Подход верный, но контроль допустимости ссылки на закрытый файл сделан через REFERER - это неправильно, из-за этого неизбежны дыры.

Попытки ограничить доступ к файлам проверкой REFERER ненадёжны и годятся разве что для простого anti-hotlinking'а, который можно сделать в .htaccess средствами mod_rewrite. Во-первых, REFERER легко подделать. Во-вторых, HTTP-запрос с пустым REFERER нельзя отвергать, но если его не отвергать - опять же получится дыра.

Пример подделки REFERER.
В ReGet Deluxe Personal добавляем закачку: на закладке "Общие" в поле "URL:" вбиваем косвенную ссылку "http://www.my_site.ru/my_files/examples/example02/get.php?file=test", на закладке "HTTP" в поле "Ссылка" вбиваем её же. После этого файл будет успешно скачан, несмотря на отсутствие прямой ссылки на него.

Пример с пустым REFERER.
Браузер посетителя - Opera 9 со снятым чекбоксом: Tools -> Preferences... -> Advanced -> Network -> Send referrer information (Инструменты -> Настройки... -> Дополнительно -> Сеть -> Отправлять данные о ссылающейся странице). Браузер не будет отправлять REFERER, и посетитель не сможет скачивать файлы, хотя и будет идти по ссылкам со страницы http://www.my_site.ru/my_files/examples/examples.html.

Реализация надёжного упрятывания файлов.

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

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

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

Что сделано в приведённой тобой реализации.

1.
Этот пункт реализован, но усложнённо - применение mod_rewrite здесь избыточно. Можно реализовать проще: завести каталог для закрытых файлов, например indirect, и положить в него блокирующий .htaccess:
<Files ~ "*">
order allow,deny
deny from all
</Files>

2.
Этот пункт тоже реализован. Отправку файла по протоколу HTTP делает класс NDL (файл ndl.class.php). Посмотрел, как формируются HTTP-заголовки, видно что реализация упрощённая - например, не формируется рекомендованный заголовок ETag. Может, и будет нормально работать, но IMHO лучше прикрутить PEAR HTTP_Download.

3.
Привязки к заходу на страницу нет вообще. Сделана проверка REFERER классом ANDL (файл andl.class.php), но такая проверка - дырявая.

Вместо этого можно сделать реализацию через сессию.

1) Реализация при отключенном кешировании страниц.

Путь к закрытому файлу содержится на нераспарсенной странице в специальном bbcode, например с префиксом @:
[url=@indirect/test.zip]test.zip[/url]

При заходе на страницу парсер bbcode получает из пути ключ. С этим ключом парсер создаёт переменные в сессии:
$sesskey = md5('indirect/test.zip');
$_SESSION['indirect'][$sesskey]['fspec'] = 'indirect/test.zip';
$_SESSION['indirect'][$sesskey]['uid'] = $usr['id'];
и помещает на страницу ссылку на скрипт indirect.php, отправляющий файл:
$body .= "<a href=\"indirect.php?$sesskey\">test.zip</a>";

Действия скрипта indirect.php:
< ? php
if (!isset($_REQUEST[session_name()]))
{
	exit; // невозможно возобновить сессию
}
session_start();
if (empty($_SERVER['QUERY_STRING'])
	|| !isset($_SESSION['indirect'][$_SERVER['QUERY_STRING']]) // данные не сформированы, не было захода на страницу со ссылкой
	|| !file_exists($fspec = $_SESSION['indirect'][$_SERVER['QUERY_STRING']]['fspec'])
	|| $_SESSION['indirect'][$_SERVER['QUERY_STRING']]['uid'] < 1 // закачка разрешена только зарегестрированным посетителям
	)
{
	exit;
}
// отправка файла $fspec - PEAR HTTP_Download, либо класс NDL
...

2) Реализация при включенном кешировании страниц.

Надо ввести специальную страницу download.php, которая никогда не кешируется. Ссылки с кешированных страниц будут идти на неё, и при заходе на эту страницу download.php в сессии будут создаваться переменные для indirect.php, а на самой странице download.php будет помещена ссылка на скрипт indirect.php, отправляющий файл:
$body .= "<a href=\"indirect.php?$sesskey\">test.zip</a>";