Как я учился защищать изображения

в 13:33, , рубрики: Amazon Web Services, images, Веб-разработка, метки:

Как я учился защищать изображения

Изображение защиты

В этой статье хочу изложить нелёгкий путь, который я прошёл «защищая» изображения в вебе. Перед тем, как мы начнём это увлекательное путешествие, хочу обозначить два подхода в деле защиты изображений:

  1. ограничение/запрет постинга прямых ссылок на оригиналы изображений
  2. вы параноик и пытаетесь ограничить распростронение копий изображений

Ограничиваем копии: мой детский велосипед

Вначале моего пути традиционно был велосипед. Много лет тому назад я разрабатывал один замечательный проект. Там было очень много чудесных фотографий животных и природы. Именно эти фотографии (а точнее их полноразмерный вариант) надо было защищать со всей силой. Клиент хотел не просто запретить прямые ссылки на файлы изображений, а лишить пользователя возможнсти скачать эти самые изображения. При этом накладывать водяные знаки не желал.

Мы уже читали о том, что программисты всё время врут. Поэтому пришлось делать то, чего хотел клиент. Решение оказалось вполне даже симпотичным. При запросе страницы с фотографией, мы генерируем некий $secretKey и сохраняем в сессию под этим ключём путь к полноразмерной копии изоражения:

public function actionView()
{
    // ...
    $_SESSION['protected-photos'][$secretKey]['file'] = $photoPath;
    // ...
}

Во вьюшке же указываем путь к фотографии в следующем виде:

<img src="/photo/source/{secretKey}" />

Теперь в actionSource мы получаем из сессии путь к полноразмерной копии фото, отправляем её с правильными заголовками и очищаем путь к полноразмерному файлу:

public function actionSource()
{
    $secretKey= $_GET('key');
    $session &= $_SESSION['protected-photos'];
    $file = $session[$secretKey]['file'];
    if (is_file($file)) {
        header('Content-type: image/jpeg');
        echo file_get_contents($file);
    }
    $session[$secretKey]['file'] = '';
}

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

Важно: Слабое место такого подхода довольно очевидно: если страницу с фотографией запросить не из браузера, а скажем через wget. В этом случае тег img не сделает запрос /photo/source/{secretKey}. Таким образом он будет содержать полноразмерную копию фотографии.

Ограничиваем прямые ссылки: .htaccess

Позже я узнал, что самый простой и распространённый способ защиты изображений — это настроить соответствующим образом .htaccess. Можно не только запретить прямые ссылки на изображения, но и указать заглушку, которая будет отображаться на сторонних ресурсах вместо оригинальных изображений с вашего сайта. Вот пример такой конфигурации:

RewriteEngine On
RewriteCond %{HTTP_REFERER} !^http://(.+.)?mysite.com/ [NC]
RewriteCond %{HTTP_REFERER} !^$
RewriteRule .*.(jpe?g|gif|png)$ http://i.imgur.com/qX4w7.gif [L]

Первая строка содержит директиву, которая включает работу механизма преобразований. Здесь всё просто. Второй строкой мы блокируем любые сайты, кроме нашего собственного mysite.com. Код [NC] означает «без вариантов», иными словами регистронезависимое соответствие URL. Третьей строкой мы разрешаем пустые рефералы. И, наконец, последняя строка мачит все файлы с расширением JPEG, JPG, GIF или PNG и заменяет их изображением qX4w7.gif с сервера imgur.com.

При необходимости можно поступть иначе: запретить прямые ссылки на изображения для конкретных доменов.

RewriteEngine On
RewriteCond %{HTTP_REFERER} ^http://(.+.)?myspace.com/ [NC,OR]
RewriteCond %{HTTP_REFERER} ^http://(.+.)?blogspot.com/ [NC,OR]
RewriteCond %{HTTP_REFERER} ^http://(.+.)?livejournal.com/ [NC]
RewriteRule .*.(jpe?g|gif|png)$ http://i.imgur.com/qX4w7.gif [L]

Каждый RewriteCond, кроме последнего, должен содержать код [NC, OR]. OR означает «или следующий», т.е. совпадение с текущим доменом или следующим.

Также вместо изображения-заглушки можно вернуть HTTP ошибку с кодом 403:

RewriteRule .*.(jpe?g|gif|png)$ - [F]

Важно: не пытайтесь вернуть вместо изображений HTML страницу. Вы можете вернуть либо другое изображение, либоHTTP-ошибку.

Ограничиваем прямые ссылки: nginx

Для nginx всё аналогично:

location ~* .(jpe?g|gif|png)$ {
        set $bad_ref "N";
        if ($http_referer !~ ^(http://(.+.)?myspace.com|http://(.+.)?blogspot.com|http://(.+.)?livejournal.com)) {
           set $bad_ref "Y";
        }
        if ($bad_ref = "Y") {
           return 444;
        }
}

Ограничиваем прямые ссылки: Amazon CloudFront Signed URLs

Amazon CloudFront является одним из лучших вариантов доставки контента пользователям. Помимо своих прямых обязанностей рядового CDN'а, он также даёт возможность генерировать подписанные ссылки. Такие ссылки дают возможность ограничить доступ к файлу по временному диапазону, а также по IP. Таким образом, например, можно указать, что изображение будет доступно в течении 10 минут. Или 7 дней начиная с завтрашнего.

Всреднем, ссылка на файл имеет следующий вид:

1http://d111111abcdef8.cloudfront.net/image.jpg?2color=red&size=medium3&Policy=eyANCiAgICEXAMPLEW1lbnQiOiBbeyANCiAgICAgICJSZXNvdXJjZSI6Imh0dHA 6Ly9kemJlc3FtN3VuMW0wLmNsb3VkZnJvbnQubmV0L2RlbW8ucGhwIiwgDQogICAgICAiQ 29uZGl0aW9uIjp7IA0KICAgICAgICAgIklwQWRkcmVzcyI6eyJBV1M6U291cmNlSXAiOiI yMDcuMTcxLjE4MC4xMDEvMzIifSwNCiAgICAgICAgICJEYXRlR3JlYXRlclRoYW4iOnsiQ VdTOkVwb2NoVGltZSI6MTI5Njg2MDE3Nn0sDQogICAgICAgICAiRGF0ZUxlc3NUaGFuIjp 7IkFXUzpFcG9jaFRpbWUiOjEyOTY4NjAyMjZ9DQogICAgICB9IA0KICAgfV0gDQp9DQo4&Signature=nitfHRCrtziwO2HwPfWw~yYDhUF5EwRunQA-j19DzZrvDh6hQ73lDx~ -ar3UocvvRQVw6EkC~GdpGQyyOSKQim-TxAnW7d8F5Kkai9HVx0FIu-5jcQb0UEmat EXAMPLE3ReXySpLSMj0yCd3ZAB4UcBCAqEijkytL6f3fVYNGQI65&Key-Pair-Id=APKA9ONS7QCOWEXAMPLE

А теперь по пунктам:

  1. Базовая ссылка на ваше изображение. Это ссылка, которую вы использовали для доступа к изображению и ранее, до подписанных ссылок.
  2. Произвольные параметры запроса, которые обычно используются для логирования доступа к изображениям. CloudFront позволяет передавать, кэшировать и логировать эти параметры. Важно: имя параметров не должно совпадать с зарезервированными самим CloudFront: Expires, Key-Pair-Id, Policy, Signature. Лучше всего добавлять к вашим параметрам префикс x-. Это будет особенно полезно, если ваши изображения хранятся на Amazon S3.
  3. Правила доступа к изображениб в JSON-формате и без пробелов (детали).
  4. Хэшированная и подписанная версия правил доступа из предыдущего пункта (детали).
  5. Ключ подписи (детали).

Важно: CloudFront не поддерживает CNAMEs с HTTPS. Т.е. вы не сможете заменить https://d111111abcdef8.cloudfront.net на https://images.example.com. Есть два варианта решений проблемы:

  1. Вернуть использование домена https://*.cloudfront.com для изображений.
  2. Оставить домен images.example.com, но использовать его через протокол HTTP.

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

Эпилог

Надеюсь описанные выше подходы помогут вам быстрее сориентироваться в нелёгком деле защиты изображений в вебе. И немного полезных ссылок по теме:

  1. Hotlinking: Генератор .htaccess
  2. Hotlinking: Конфигурация .htaccess
  3. Hotlinking: Пример настройки nginx
  4. Hotlinking: Проверка
  5. Amazon CloudFront Signed URLs

Автор: uaoleg

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js