Защита от хотлинкинга с помощью настройки nginx
Потихоньку видяшки из моих мультосайтов начали гулять по инету… Простая защита от дурака, предоставляемая флеш-плеером uppod.ru, отсеивала большинство школьников, а против мало-мальски подготовленного кулхацкера, конечно, не спасла. Ведь на уровне заголовков все обращения к файлам сервера видны как на ладони, и ничто не мешает стырить прямой линк на файл. Пришлось лазить по разным сайтам и собирать инфу по защите моих файлов от хотлинкинга (эт када пиздят линки, ага).
Буквально за минуту я наткнулся на простое решение, связка nginx+простой_скрипт_на_php. Суть в том, что в ссылку на файл добавлялся хэш ИПа клиента + некий пароль, при обращении к nginx клиент редиректился на пхп-скрипт, который проверял ИП клиента и пароль, и в случае когда ИП и пароль совпадал, редиректил запрос обратно nginx’у, который выдавал файл на скачку. В случае, когда либо ИП не подходил (это когда юзер получил ссылку и пытается ее открыть на машине с другим ИПом ), либо пароль (а это когда уже кто-то прямо пытается подобрать пароль к расшифровке..), в этих случаях в конфиге nginx’а можно задать действие - либо нах слать (403, 404), либо подсовывать заглушку (fuck_you.flv).
На такой вариант я наткнулся тут, и наверняка бы сделал его, будь у меня на серверах PHP :) Дело в том, что все видяшки у меня лежат на разных серверах, на которых крутится только правильно настроенный nginx. И ставить туда PHP ради одного маленького скриптика мне не хотелось, и я продолжил поиски.
Спустя еще минут 10 поковыряв архивы конференций по nginx, а также блоги, вылезевшие из серпа по запросу “Защита видеоконтента”, я обнаружил, что в самом nginx’е есть нативный модуль, который позволяет делать все тоже самое! И даже больше, чем пример с ПХПой :) Называется он Http_Secure_Link_Module. Он позволяет во1, генерировать для каждого IP уникальную ссылку, во2, задавать пароль, предотвращающий создание базы из ссылок для всех возможных IP-адресов, и в3х, задавать время жизни такой ссылки. Все эти плюшки доступны для nginx версии с 0.8.50, и, как говорится в документации, этот модуль по дефолту не ставится вместе с nginx, так что надо его дополнительно указать при сборке. Тем не менее, у меня nginx установился вместе с этим модулем (видимо, подцепился собственный репозитарий хостера, где своя сборка nginx). Чтобы посмотреть, есть ли у вас этот модуль, надо набрать:
nginx -V
В документации все расписано, но для наглядности я покажу что получилось у меня. В директиве server файла nginx.conf (конечно, все кастомные названия изменены):
location /video/ {
rewrite /video/([a-zA-Z0-9_\-]*)/([0-9]*)/(.*)\.flv$ /realvideo/$3.flv?secl=$1§=$2;
}
location /realvideo/ {
secure_link $arg_secl,$arg_sect;
secure_link_md5 mysecretword$uri$arg_sect$remote_addr;
if ($secure_link = "") { return 403; }
if ($secure_link = "0") { return 404; }
rewrite ^/realvideo/(.*)$ /realvideo/$1 break;
root /var/www/;
flv;
}
}
Первый локейшн просто переписывает ссылки вида:
http://site.ru/video/98gh12b9v7g112su6755f/198267424/mult.flv
на:
http://site.ru/realvideo/mult.flv?secl=98gh12b9v7g112su6755f§=198267424
Вся магия происходит во втором локейшене, директиве secure_link мы указываем наш хэш (secl) и время жизни ссылки (sect), которые были заданы клиентом при загрузки страницы (код см.ниже). Директива secure_link_md5 вычисляет хэш и сверяет время уже на стороне сервера. И если все гуд, то переменной $secure_link присваивается значение 1 и выдается нужная видяшка. Если скажем время вышло, тогда $secure_link=0 и клиенту возвращается 404 ошибка. Если хэш неправильный - возвращается 403 ошибка.
А вот как генерируются ссылки вида, который я упомянул чуть выше (http://site.ru/video/98gh12b9v7g112su6755f/198267424/mult.flv ):
$name = "mult.flv";
$secret = 'mysecretword';
$time = time() + 10800; //ссылка будет рабочей три часа
$key = str_replace("=", "", strtr(base64_encode(md5($secret.'/realvideo/'.$name.$time.getenv("REMOTE_ADDR"), TRUE)), "+/", "-_"));
$encoded_url = "http://site.ru/video/$key/$time/$name";
Тут кодируется урл, секретное слово, время жизни ссылки, и IP. Вроде должно быть понятно.
Думая, что вот оно щастье, я зафигачил это все дело у себя. И оно нифига не заработало :) Дело в том, что вышеприведенным PHP-кодом ссылки надо кодировать непосредственно перед выводом их посетителю. А я их кодировал прямо в БД, тем самым прописав свой IP во все ссылки :) Так что пришлось в БД оставить прямые ссылки и сделать небольшой плагин для Wordpress, который содержит вышеприведенный код и перехватывает вывод содержания поста, меняет прямую линку на кодированную. А потом уже выдает это все посетителю.
Проверка работоспособности защиты:
- попробовать с разных IP запустить видяшки (должны проигрываться)
- отловить перехватчиком заголовков урлы, по которым обращается браузер для запуска видяхи и попробовать открыть их в системе с другим IP (должна 403 ошибка показываца)
- открыть этот урл на этом же компе спустя 3 часа после его получения (должна 404 ошибка показываца).
Ура, я прошел проверки! :)
как данный код применить для пути /var/www/uploads/files
по аналогии
передача start= не меняет хэш? будет ли работать перемотка с первоначальным шифрованным урлом?
дело в том, что урл шифруется каждый раз заново при каждом обращении. То есть, при передаче временной метки будет сформирован новый хэш. Это никак не отразится на работоспособности скрипта.
Сейчас появились плееры, которые открывают страницу у тебя в браузере и выпарсеривают на лету ссылку.
Да. Но это никак не поможет в плане <a href="http://ru.wikipedia.org/wiki/Хотлинкинг" title="хотлинкинга" target="_blank" rel="nofollow">хотлинкинга.
А если помимо flv ещё и mp4 нужно шифровать как это сделать?
В строке реврайта добавить расширение:
rewrite /video/([a-zA-Z0-9_\-]*)/([0-9]*)/(.*)\.[flv|mp4]$ /realvideo/$3.[flv|mp4]?secl=$1§=$2;
вопрос можна?
давно искал тему подскажите по мимо flv,mp4 , мне нужно для плейлистов и для php файла в определенной папке по вашему принцыпу что бы вот так было
files/98gh12b9v7g112su6755f/files.php -с определенной папки
и такжк и сдесь
pl/98gh12b9v7g112su6755f/video.txt
в строке реврайта добавить [flv|mp4|txt|php]
или для .php нужно отдельный локейшен, если до то как написать еге
подскажите пожалуста!
а как написать локейшен который делаtт обратно . с длинной ссылки в короткую ?
пример http://muk1.myhost.ru/stream?url=http%3A%2F%2Ffs1.myhost.ru%2F123.mp4%3Fc%3Dcode в http://fs1.myhost.ru/123.mp4
Спасибо, помог!
Только у меня чуть сложнее конструкция, у меня построено на нескольких серверах, есть основной сайт http://site.tv, на котором страницы с плеерами, в них сделал генерацию URL'а на промежуточный сервер вида http://master.site.tv/video/8I7hHgrG_WHKzAYxZS41xA/1367578293/serials/serial1/season_1/10.flv, который отвечает за балансировку нагрузки и заодно ищет на каком именно сервере находится данный ролик (на серверах не все видяшки идентичны), он предварительно тоже проверяет валидность запросов, конфига у него
location /video/ {
rewrite /video/([a-zA-Z0-9_\-]*)/([0-9]*)/(.*)\.flv$ /index.php?secl=$1&sect=$2&file=$3.flv&addr=$remote_addr;
}
скрипт index.php:
-------------------------------------------------------------------------------------------
$secl = isset($_GET['secl']) ? $_GET['secl'] : null;
$sect = isset($_GET['sect']) ? $_GET['sect'] : null;
$file = isset($_GET['file']) ? $_GET['file'] : null;
$addr = isset($_GET['addr']) ? $_GET['addr'] : null;
if (!$secl || !$sect || $file || $addr) exit();
$secret = 'ea243e95aac99e4508f6bb6b064503f3';
$key = str_replace("=", "", strtr(base64_encode(md5($secret.'/video/'.$file.$sect.$addr, TRUE)), "+/", "-_"));
if ($key != $secl)
{
header("Status: 404 Not Found");
exit();
}
header("Location: http://v1-de.site.tv/get/$secl/$sect/$file");
-------------------------------------------------------------------------------------------
Хотя тут можно и не передавать адрес nginx'ом, а брать внутри пыха, ну это без разницы.
Ну вот а уже этот сервер, в зависимости от нагрузки и наличия видео, передает на конкретный сервер с видеоконтентом, в данном случае http://v1-de.site.tv, его конфига
location /get/ {
rewrite /get/([a-zA-Z0-9_\-]*)/([0-9]*)/(.*)\.flv$ /video/$3.flv?secl=$1&sect=$2;
}
location /video/ {
secure_link $arg_secl,$arg_sect;
secure_link_md5 ea243e95aac99e4508f6bb6b064503f3$uri$arg_sect$remote_addr;
if ($secure_link = "") { return 403; }
if ($secure_link = "0") { return 404; }
rewrite ^/video/(.*)$ /video/$1 break;
root /var/www/vstore/;
flv;
}
Надо будет еще для mp4 файлов и модуля ngx_http_mp4_module тоже самое прописать.
Еще раз спасибо, за то что не пришлось изобретать колесо.
Pavel, можно поподробнее конфиги - у меня подобно, тоже 2 сайта один со статикой, на втором видео хранится. Реализовать все никак не получается
Да вроде подробно написал. Ну смотри, у меня схема из следующих серверов: основной сайт с CMS'кой, серверы со статикой, сервер балансировщик нагрузки, серверы с видео. Если у тебя один сервер с видео, то балансировщик можно выкинуть. Конфиг основного сайта тут неважен, он затачивается под конкретную CMS. На этом сайте ссылка на видео генерируется как описано в этой статье:
$name = $movie['temp_file'];
$secret = 'ea243e95aac99e4508f6bb6b064503f3';
$time = time() + 10800; //ссылка будет рабочей три часа
$key = str_replace("=", "", strtr(base64_encode(md5($secret.'/get/'.$name.$time.getenv("REMOTE_ADDR"), TRUE)), "+/", "-_"));
$movie['path'] = "http://v1-de.site.tv/get/$key/$time/$name";
У меня она относительная, вида "serials/serial1/season_1/10.flv", на целевых серверах она потом просто подставится в конструкцию "/home/user/video/serials/serial1/season_1/10.flv". Все это кодируется в строку вида http://v1-de.site.tv/get/98gh12b9v7g112su6755f/198267424/serials/serial1/season_1/10.flv"
На сервере с видео конфиг такой http://pastebin.com/wizAcbA4
У меня там немного не так сделано, как в статье, где просто идет редирект с
http://site.ru/video/98gh12b9v7g112su6755f/198267424/mult.flv
на
http://site.ru/realvideo/mult.flv?secl=98gh12b9v7g112su6755f&sect=198267424
У меня есть два типа видео: flv и mp4, а для них надо включать разные модули стриминга nginx'а и я ничего лучше не придумал как разделить их редиректами на два локейшена
rewrite /get/([a-zA-Z0-9_\-]*)/([0-9]*)/(.*)\.flv$ /video/$3.flv?secl=$1&sect=$2;
rewrite /get/([a-zA-Z0-9_\-]*)/([0-9]*)/(.*)\.mp4$ /video/$3.mp4?secl=$1&sect=$2;
location ~ /video/(.*)\.flv$
и
location ~ /video/(.*)\.mp4$ {
Остальное там это тюнинг, например, limit_rate 90k; это уже смотрите сами, какое у вас там качество видео и какой лимит ему выставлять (если надо). Лимит хорош, когда надо защититься от скачивальщиков чужого контента. Согласитесь, нет смысла отдавать видео со скоростью, скажем, 5 Мбит/с, это уже явно не просмотр, а закачка, которая сильно будет нагружать сервер.
Если еще вопросы есть, пиши лучше на мыло mailpashka собака mail.ру, а то тут долго сообщения модерируются.
Здравствуйте у Вас в посте написано,
надо кодировать непосредственно перед выводом их посетителю, то есть получается нужно ставить прямую ссылку на файл по типу http://site.ru/video/mult.flv, а скрипт ее потом переведет в http://site.ru/video/98gh12b9v7g112su6755f/198267424/mult.flv
правильно?
Здравствуйте. Очень полезная статья, но у нас, что-то не получается, мы готовы заплатить, за вашу помощь в объяснении конкретно нашего случая.