При создании потрясающего веб-приложения или веб-сайта мы иногда хотим, чтобы люди могли встраивать части нашего веб-приложения / веб-сайта в свои собственные. Это может быть iframe с кнопкой «Мне нравится», простое изображение, которое они хотят использовать повторно, или даже все наше приложение, встроенное в iframe.
Но как мы можем контролировать, кто имеет доступ, кому разрешено использовать нашу пропускную способность и запрашивать наш сервис?
Мы определяем проблему как контроль доступа к активам
Под активами мы подразумеваем: все, что можно запросить с нашего сайта.
Ограничение доступа: разрешить некоторые, заблокировать все
Говоря об управлении доступом, мы входим в область безопасности. И когда речь идет о безопасности, белый список должен быть подход, используемый для решения проблемы. Проще контролировать, кому разрешен доступ к вашим активам, чем контролировать, кому нет. Просто невозможно узнать всех монстров-буги в интернете.
Чтобы защитить наши активы , мы нанимаем привратника, чтобы позволить только тем, кому мы доверяем. После найма мы даем ему доступ к белому списку, который мы контролируем, и позволяем ему выполнять всю тяжелую работу. Проблема решена. Но как поднять привратник?
Подъемная тактика
В зависимости от того, насколько безопасным вы хотите, чтобы был привратник, и тем, что просил клиент, можно использовать разные тактики.
Обычный подход — проверка заголовка Referer. Этот метод имеет 3 больших недостатка:
- Реферер также устанавливается, когда люди заходят на ваш сайт по ссылке
- Реферер отправляется на ваш сервер клиентом и может быть изменен
- Реферер может быть не установлен вообще
Однако для статических ресурсов, таких как изображения, js и css, эти недостатки не являются проблемой. Ваши активы должны загружаться только тогда, когда пользователи посещают наш веб-сайт напрямую (или с доверенного сайта). Общая идея состоит в том, чтобы блокировать других, ссылаясь на них. Таким образом, рефери всегда будет в вашем белом списке. Если вы не доверяете себе — но тогда у вас есть большие проблемы.
Братан, ты вообще поднимаешь?
В зависимости от используемой настройки сделанный запрос проходит через серию шлюзов . Простая настройка: клиент -> HTTP-сервер -> код приложения
Так где же сидит ваш привратник? Клиент де-факто не хочет контролировать доступ, потому что он ненадежный обманщик. HTTP-сервер и код приложения, с другой стороны, являются полезными опциями. Оба дают нам мощные инструменты для проверки HTTP_HOST
.
HTTP-серверы знают, как поднять
Сила того, чтобы ваш HTTP-сервер управлял вашим контролем доступа — это скорость. Нет необходимости запускать код приложения для каждого запроса. Это может значительно повысить производительность, поскольку нам не нужно загружать весь стек / поток приложения (например, mod_php) в память.
В зависимости от вашего HTTP-сервера, доступны разные решения.
апаш
В Apache есть два разных метода. Мы можем использовать mod_rewrite или Allow / Deny.
Метод mod_rewrite:
# Turn mod_rewrite on RewriteEngine On # if it is not trusted.domain.tld block it RewriteCond %{HTTP_REFERER} !^trusted\.domain\.tld$ [NC] RewriteCond %{HTTP_REFERER} !^trusted\.tld$ [NC] RewriteRule ^ - [F]
mod_rewrite поддерживается большинством хостинг-провайдеров.
Метод Разрешить / Запретить:
#specify the files to guard, block all the assets <files "*.*"> #block everyone Deny from all #allow trusted ones Allow from trusted.tld trusted.domain.tld </files>
Не все хосты поддерживают эти настройки.
Nginx
Модуль HttpRefererModule в nginx дает нам действительно классные valid_referers
: так что все, что нам нужно сделать, это вернуть http-код 444, когда valid_referers
домен пытается получить доступ к нашим ресурсам:
… Нестандартный код 444 закрывает соединение без отправки каких-либо заголовков…
location / { valid_referers trusted.tld trusted.domain.tld; if ($invalid_referer) { return 444; } }
HTTP-серверы не думают
Большая проблема здесь — это масштабируемость: что если у нас есть 1000 доменов, которые должны иметь доступ к нашим ресурсам? Что делать, если список доменов часто меняется?
Для каждого небольшого редактирования нам нужно погрузиться в наши файлы конфигурации — и чем больше вы изменяете вручную, тем больше может идти не так.
Код приложения знает, что делать
Наличие контроля доступа на уровне кода приложения означает большую гибкость. Его привратник мог бы заработать в кратчайшие сроки:
<?php //the whitelist we control $whitelist = array( 'trusted.tld', 'trusted.domain.tld' ); //the referer $referer = parse_url($_SERVER["HTTP_REFERER"], PHP_URL_HOST); //the gatekeeper if ( !in_array($referer, $whitelist) ) { throw new GateKeeperDoesNotApprove; }
Как насчет этих фреймов?
Как уже упоминалось, полагаться на реферера не всегда хорошая идея. Это не только данные от нашего ненадежного человека, но и не дает нам понять, находимся ли мы в iframe или нет. Там просто нет возможности узнать.
Однако мы могли бы нанять киллера, чтобы помочь нашему GateKeeper. Наш киллер будет отправлен людям, которые выглядят подозрительно (например, с ненадежным реферером). Наемный убийца будет использовать JS в качестве своего оружия:
document.getElementById('container').innerHTML = ''; alert('You just got killed');
К сожалению, кто-то, пришедший из ненадежного домена, имеет того же реферера, что и кто-то другой, кто обращается к нам с помощью iframe из этого ненадежного домена. Активам, однако, будет присвоено значение referer для нашего домена (даже в ситуации iframe), поэтому отправка киллера здесь излишняя. Достаточно просто запретить доступ — или вы можете отправить случайное изображение котенка .
Вот почему у нас есть проверка наемного убийцы, если мы находимся в iframe. Если так, мы заставим его убить нашу цель:
if (top.location.href != self.location.href) { //kill the target }
Единственное, что нам нужно знать, — это чтобы наш GateKeeper добавил киллера к полезной нагрузке, отправленной клиенту. Легко!
//template.tpl If ( Gatekeeper::doesNotApprove() ) { Gatekeeper::sendHitman(); } //gatekeeper class Gatekeeper { private static $whitelist = array(); public static function doesNotApprove() { return !in_array( parse_url($_SERVER["HTTP_REFERER"], PHP_URL_HOST), self::$whitelist ); } public static function sendHitman() { print '<script>if (top.location.href != self.location.href) { document.getElementById('container').innerHTML = '';alert('You just got killed');}</script>'; } }
Этот код не является производственным доказательством. Это служит примером.
Как насчет реальной безопасности?
Решения, представленные здесь, защитят вас от большинства монстров буги-вуги. Но оба решения не являются надежными. Первый использует данные от клиента, второй — это JavaScript, который запускается клиентом.
Безопасным способом является использование основанного на токене GateKeeper. OAuth, вероятно, тот парень, которого вы хотите найти здесь, но это выходит за рамки этой статьи.