Защита видео и изображений от хотлинка

12
K5
На сайте с 13.07.2014
Offline
49
2620

Недавно обнаружил, что контент моего сайта(изображения и видео) во всю используют сторонние сайты и начал борьбу с этим явлением средствами nginx.

Для начала прописал белый список referrer для медиа-файлов:

            valid_referers none blocked server_names ~(yandex|google|mail|yahoo|rambler|bing);

if ($invalid_referer) {
return 403;
}

Контент на некоторых сайтах отвалился после этой процедуры, но некоторые сайты скрывают referer. Я хотел заблокировать запросы с пустым referer, но потом подумал, что поисковые боты не смогут просканировать такой контент. У них тоже нет referer.

Есть интересное решение для защиты видео ngx_http_secure_link_module, но оно имеет свои недостатки. Во-первых, потребуется изменить формат ссылки на видео, что означает вмешательство в код плагина CMS, во-вторых, совершенно непонятно как с такой защитой предлагать свою видео в сервис яндекс.видео. Ведь ссылки в микроразметке будут временными и привязанными к определенным ip-адресам.

Заголовок X-Frame-Options SAMEORIGIN тоже не помог, потому что видео открывается не во фрейме, а в обычном теге video без всяких оберток.

Я также пробовал использовать заголовки, которые должны были решить все проблемы


add_header Access-Control-Allow-Origin https://mysite.com;
add_header Vary Origin;

Но браузер преспокойно грузит мои видео на стороннем сайте не обращая внимания на эти заголовки. Почему?

Оптимизайка
На сайте с 11.03.2012
Offline
396
#1
karpo518:
Я хотел заблокировать запросы с пустым referer, но потом подумал, что поисковые боты не смогут просканировать такой контент. У них тоже нет referer.

Так разрешите для поисковых ботов (с соотв. user agent и соотв. ip-подсетей).

Я также пробовал использовать заголовки, которые должны были решить все проблемы. Но браузер преспокойно грузит мои видео на стороннем сайте не обращая внимания на эти заголовки. Почему?

CORS не действует для видео и картинок.

⭐ BotGuard (https://botguard.net) ⭐ — защита вашего сайта от вредоносных ботов, воровства контента, клонирования, спама и хакерских атак!
D
На сайте с 28.06.2008
Offline
1101
#2

Используйте геоайпи + map

вот немного примеров, там несложно все подогнать под ваши нужды

geo $whitelist {
default 0;
# ip server
тут-айпи-вашего-сервера;
# боты google
64.68.80.0/21 1;
64.233.0.0/16 1;
66.102.0.0/20 1;
72.14.192.0/18 1;
209.85.128.0/17 1;
216.239.32.0/19 1;
66.249.0.0/16 1;
104.154.0.0/16 1;
# боты yandex
77.88.0.0/18 1;
87.250.224.0/19 1;
84.201.158.0/24 1;
93.158.0.0/16 1;
95.108.128.0/17 1;
213.180.192.0/19 1;
141.8.0.0/16 1;
130.193.0.0/16 1;
5.255.0.0/16 1;
178.154.0.0/16 1;
37.9.0.0/16 1;
37.140.0.0/16 1;
5.45.207.0/24 1;
# mail.ru
217.69.0.0/16 1;
94.100.0.0/16 1;
# bingbot-msn
40.77.0.0/16 1;
207.46.0.0/16 1;
65.52.0.0/14 1;
157.55.0.0/16 1;
131.253.0.0/16 1;
# Yahoo
68.180.0.0/16 1;
67.195.0.0/16 1;
69.147.64.0/18 1;
72.30.0.0/16 1;
74.6.0.0/16 1;
63.250.0.0/16 1;
98.139.0.0/16 1;
206.190.34.0/24 1;
# sputnik
5.143.0.0/16 1;
# opera-mini.net
82.145.0.0/16 1;
107.167.96.0/19 1;
185.26.180.0/23 1;
37.228.104.0/22 1;
37.228.111.128/26 1;
141.0.0.0/16 1;
# TelegramBot
149.154.167.0/24 1;
# Applebot
17.142.0.0/24 1;

}
geoip_country /usr/share/GeoIP/GeoIP.dat;
map $geoip_country_code:$whitelist:$server_protocol $allowed_country {
default yes;
"DE:0:HTTP/1.1" no;
"NL:0:HTTP/1.1" no;
"FR:0:HTTP/1.1" no;
"CA:0:HTTP/1.1" no;
"US:0:HTTP/1.1" no;
"GB:0:HTTP/1.1" no;
}

map "$whitelist:$server_protocol" $limit1 {
default "";
"1:HTTP/1.0" "";
"1:HTTP/1.1" "";
"1:HTTP/2.0" "";
"0:HTTP/1.1" "$binary_remote_addr";
}
K5
На сайте с 13.07.2014
Offline
49
#3

Я всё-таки решил изменить одну строку в плагине и включить secure_link с игнором проверки для referer яндекса. Но никак не могу корректно настроить nginx. Он отдает мне 403-ий статус, то есть проверка соответствия хешей выполняется неудачно.

        location ~ \.mp4$ {
mp4;
mp4_buffer_size 1m;
mp4_max_buffer_size 5m;

secure_link $arg_md5,$arg_expires;
secure_link_md5 "test";

if ($secure_link = "") { return 403; }
if ($secure_link = "0") { return 410; }
}

function buildSecureLink($url, $secret, $ttl = 10000)
{

$userIp = $_SERVER['REMOTE_ADDR'];


$parts = parse_url($url);
$path = $parts['path'];

$expires = time() + $ttl;
$md5 = md5("test",true);
$md5 = base64_encode($md5);
$md5 = strtr($md5, '+/', '-_');
$md5 = str_replace('=', '', $md5);

if(strpos($url, '?') !== false)
{
return $url . '&md5=' . $md5 . '&expires=' . $expires;
}

return $url . '?md5=' . $md5 . '&expires=' . $expires;
}

Можете подсказать, как увидеть глазами сравниваемые строки, чтобы их изучить? Получаю хеш из nginx командой

echo -n 'test' |     openssl md5 -binary | openssl base64 | tr +/ -_ | tr -d =

И вижу, что он идентичен тому, что в ссылке. Почему же отклик 403?

neoks
На сайте с 17.03.2010
Offline
152
#4

karpo518, Я клиенту делал когда-то подобное, но так как это было давно и с php я уже не дружу, то просто приведу рабочий пример с архива.

PHP

// Шаблон для токена
$secret_word = "Tfv9WLsWbf"; // Секретное слово
$uri_prefix = "http://domain.ua/video/"; // Начало ссылки
$file = '/piknik.mp4'; // Файл
$time = time()+10800; // Ссылка будет действительна в течении 3х часов

// Генерируем token
$secret = md5($time.$file.$_SERVER["REMOTE_ADDR"].' '.$secret_word, true); // Хешируем в md5 bin.
$secret = base64_encode($secret); // Encode base64 url
$secret = strtr($secret, '+/', '-_'); // Редактирует ссылку
$secret = str_replace( '=', '', $secret); // Редактирует ссылку

// Формируем ссылку
$hash_url = '"'.$uri_prefix.$secret.'/'.$time.$file;

NGINX

location ~ ^/video/.+\.(flv|mp4)$ {
valid_referers none blocked .domain.ua;
if ($invalid_referer) {
return 403;
}

if ($uri ~* ^/video/([^/]+)/([0-9]+)/(.+\.(flv|mp4))$) {
set $md5 $1;
set $expires $2;
set $myuri $3;
}

secure_link $md5,$expires;
secure_link_md5 "$secure_link_expires/$myuri$remote_addr Tfv9WLsWbf";
if ($secure_link = "") { return 403; }
if ($secure_link = "0") { return 410; }
rewrite "^/video/([^/]+)/([0-9]+)/(.+)$" /video/$3 last;
}

P.S: Хеш nginx можно вывести через "return 200 $secure_link;"

K5
На сайте с 13.07.2014
Offline
49
#5

neoks, спасибо за ответ. Обратите внимание на свой код. Вы сравниваете $secure_link с пустой строкой и с 0. В этой переменной не может лежать хеш nginx. В ней лежит результат сравнения хешей.

Кроме того, указанным вами способом нельзя вывести строку при запросе media-файла. Заголовки mp4 не позволяют браузеру показать строку. Он выводит пустой видео плеер.

neoks
На сайте с 17.03.2010
Offline
152
#6

karpo518, Так и нужно, если верить документации http://nginx.org/ru/docs/http/ngx_http_secure_link_module.html

В моем примере файл отдается по другому url, "/video/md5/expires/piknik.mp4" вместо "/video/piknik.mp4" - (у заказчика была цель именно таких ссылок).

В вашем случаи правильные настройки выглядят так

location ~ \.mp4$ {
mp4;
mp4_buffer_size 1m;
mp4_max_buffer_size 5m;

secure_link $arg_md5,$arg_expires;
secure_link_md5 "$secure_link_expires$uri$remote_addr secret";

if ($secure_link = "") { return 403; }
if ($secure_link = "0") { return 410; }
}

PHP

function buildSecureLink($url, $secret, $ttl = 10000)
{

$userIp = $_SERVER['REMOTE_ADDR'];


$parts = parse_url($url);
$path = $parts['path'];

$expires = time() + $ttl;
$md5 = md5($expires.explode('?', $url)[0].$userIp.' '.$secret, true);
$md5 = base64_encode($md5);
$md5 = strtr($md5, '+/', '-_');
$md5 = str_replace('=', '', $md5);

if(strpos($url, '?') !== false)
{
return $url . '&md5=' . $md5 . '&expires=' . $expires;
}

return $url . '?md5=' . $md5 . '&expires=' . $expires;
}

Ссылки соответственно идут так "/Natalia.mp4?args=s1&md5=YvLrbogLjUmjx1KOBIBGEQ&expires=1528733288"

P.S: Не забудьте указать рандомый текст вместо "secret".

K5
На сайте с 13.07.2014
Offline
49
#7

neoks, мой изначальный вопрос

Можете подсказать, как увидеть глазами сравниваемые строки, чтобы их изучить?

Задаю его неспроста. Как вы думаете, если secure_link_md5 "test" приводит к 403-ему отклику, то ваш secure_link_md5 "$secure_link_expires$uri$remote_addr secret"; как-то улучшит ситуацию?

Я предоставил вам тестовый пример, который объясняет, что в модуле проблема со сравнением строк на самом примитивном уровне. Хеш от "test" не совпадает. Зачем пробовать более сложные варианты, если даже такой простой не работает.

neoks
На сайте с 17.03.2010
Offline
152
#8
karpo518:
Можете подсказать, как увидеть глазами сравниваемые строки, чтобы их изучить?

1) secure_link_md5 вывести нельзя, вы можете вывести только переменные которые туда задаете, а потом привести в md5, как выводить переменные я уже писал выше.

2) Что-бы посмотреть ответ в браузере, просто используйте "view-source:http://domain.ua/file.mp4"

karpo518:
Как вы думаете, если secure_link_md5 "test" приводит к 403-ему отклику, то ваш secure_link_md5 "$secure_link_expires$uri$remote_addr secret"; как-то улучшит ситуацию?

Да, хотя-бы потому что $secure_link_expires является обязательным полем в secure_link_md5.

karpo518:
Я предоставил вам тестовый пример, который объясняет, что в модуле проблема со сравнением строк на самом примитивном уровне.

Проблема в неправильном secure_link_md5, а точнее в неправильной комбинации secure_link и secure_link_md5.

karpo518:
Зачем пробовать более сложные варианты, если даже такой простой не работает.

В последнем посте я дал вам полностью рабочий конфиг nginx и php (из вашего примера).

S
На сайте с 23.05.2004
Offline
316
#9

Гораздо веселее не запрещать хотлинк, а подсовывать другой контент.

Это просто подпись.
K5
На сайте с 13.07.2014
Offline
49
#10

neoks, я только что попробовал ваш вариант как есть и быстро всё откатил. 403-й отклик для всех видео. Для чистоты эксперимента отключил VPN, хотя вообще влиять не должно. Как продебажить проблему?

Изначально я использовал пример отсюда https://gist.github.com/bftanase/cbae1f9fc69bb4f9cb86, поэтому и к вашему скептически отнесся. Не работает, как ни крути

nginx/1.13.12
built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9)
built with OpenSSL 1.0.2g 1 Mar 2016
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-compat --with-file-aio --with-threads --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-mail --with-mail_ssl_module --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-cc-opt='-g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fPIC' --with-ld-opt='-Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -Wl,--as-needed -pie'
12

Авторизуйтесь или зарегистрируйтесь, чтобы оставить комментарий