Frame Buster Buster ... необходим код ошибки

Допустим, вы не хотите, чтобы другие сайты "создавали" ваш сайт в <iframe>:

<iframe src="http://example.org"></iframe>

Таким образом, вы вставляете антикадровый код JavaScript во все свои страницы:

/* break us out of any containing iframes */
if (top != self) { top.location.replace(self.location.href); }

Отлично! Теперь вы автоматически "перебираете" или выбрасываете любой содержащий iframe. За исключением одной маленькой проблемы.

Как выясняется, ваш код перебора кадров может быть отключен, как показано здесь:

<script type="text/javascript">
    var prevent_bust = 0  
    window.onbeforeunload = function() { prevent_bust++ }  
    setInterval(function() {  
      if (prevent_bust > 0) {  
        prevent_bust -= 2  
        window.top.location = 'http://example.org/page-which-responds-with-204'  
      }  
    }, 1)  
</script>

Этот код делает следующее:

  • увеличивает счетчик каждый раз, когда браузер пытается отойти от текущей страницы, через window.onbeforeunload обработчик события
  • устанавливает таймер, который срабатывает каждую миллисекунду через setInterval()и, если он видит счетчик увеличенным, то меняет текущее местоположение на сервер под контролем злоумышленника
  • этот сервер обслуживает страницу с кодом состояния HTTP 204, что не заставляет браузер перемещаться в любом месте

Мой вопрос - а это скорее головоломка JavaScript, чем реальная проблема - как вы можете победить разрушителя фреймов?

У меня было несколько мыслей, но в моем тестировании ничего не получалось:

  • пытаясь очистить onbeforeunload событие через onbeforeunload = null не имел никакого эффекта
  • добавив alert() остановил процесс, чтобы пользователь знал, что это происходит, но никак не влиял на код; нажатие кнопки OK позволяет продолжить процесс как обычно
  • Я не могу придумать способ очистить setInterval() таймер

Я не очень-то программист на JavaScript, поэтому вот мой вызов для вас: эй, бастер, можешь ли ты уничтожить бастера?

19 ответов

Решение

Я не уверен, является ли это жизнеспособным или нет - но если вы не можете разбить кадр, почему бы просто не отобразить предупреждение. Например, если ваша страница не является "верхней страницей", создайте метод setInterval, который пытается разбить фрейм. Если после 3 или 4 попыток ваша страница все еще не является верхней страницей - создайте элемент div, который покрывает всю страницу (модальное поле) сообщением и ссылкой, например...

Вы просматриваете эту страницу в неавторизованном рамочном окне - (бла-бла... потенциальная проблема безопасности)

нажмите на эту ссылку, чтобы решить эту проблему

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

Мы использовали следующий подход на одном из наших веб-сайтов по http://seclab.stanford.edu/websec/framebusting/framebust.pdf

<style>
 body { 
 display : none   
}
</style>
<script>
if(self == top) {
document.getElementsByTagName("body")[0].style.display = 'block';
}
else{
top.location = self.location;
}
</script>

Придумал это, и, похоже, работает, по крайней мере, в Firefox и браузере Opera.

if(top != self) {
 top.onbeforeunload = function() {};
 top.location.replace(self.location.href);
}

Принимая во внимание текущий стандарт HTML5, который ввел "песочницу" для iframe, все коды сброса фреймов, представленные на этой странице, могут быть отключены, когда злоумышленник использует "песочницу", потому что он ограничивает iframe следующим:

allow-forms: Allow form submissions.
allow-popups: Allow opening popup windows.
allow-pointer-lock: Allow access to pointer movement and pointer lock.
allow-same-origin: Allow access to DOM objects when the iframe loaded form same origin
allow-scripts: Allow executing scripts inside iframe
allow-top-navigation: Allow navigation to top level window

Пожалуйста, смотрите: http://www.whatwg.org/specs/web-apps/current-work/multipage/the-iframe-element.html

Теперь предположим, что злоумышленник использовал следующий код для размещения вашего сайта в iframe:

<iframe src="URI" sandbox></iframe>

Тогда весь код разрыва фрейма JavaScript потерпит неудачу.

После проверки всех кадровых кодов кадров только эта защита работает во всех случаях:

<style id="antiClickjack">body{display:none !important;}</style>
<script type="text/javascript">
   if (self === top) {
       var antiClickjack = document.getElementById("antiClickjack");
       antiClickjack.parentNode.removeChild(antiClickjack);
   } else {
       top.location = self.location;
   }
</script>

первоначально предложенные Густавом Ридстедтом, Эли Бурштейном, Даном Бонех и Коллином Джексоном (2010)

Подумав немного, я верю, что это покажет им, кто в доме хозяин...

if(top != self) {
  window.open(location.href, '_top');
}

С помощью _top в качестве целевого параметра для window.open() запустит его в том же окне.

Начиная с 2015 года вы должны использовать CSP2 frame-ancestors Директива для этого. Это реализовано через заголовок ответа HTTP.

например

Content-Security-Policy: frame-ancestors 'none'

Конечно, не многие браузеры поддерживают CSP2, поэтому стоит включить старый X-Frame-Options заголовок:

X-Frame-Options: DENY

В любом случае, я бы посоветовал включить оба варианта, иначе ваш сайт будет по-прежнему уязвим к атакам Clickjacking в старых браузерах, и, конечно, вы получите нежелательные фреймы даже без злонамеренного намерения. В наши дни большинство браузеров обновляются автоматически, однако корпоративные пользователи по-прежнему склонны зацикливаться на старых версиях Internet Explorer по причинам совместимости с устаревшими приложениями.

if (top != self) {
  top.location.replace(location);
  location.replace("about:blank"); // want me framed? no way!
}

Все предложенные решения напрямую форсируют изменение местоположения верхнего окна. Что, если пользователь хочет, чтобы кадр был там? Например, верхний фрейм в изображении результатов поисковых систем.

Я написал прототип, в котором по умолчанию все входы (ссылки, формы и элементы ввода) отключены и / или ничего не делают при активации.

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

Если содержащий кадр не обнаружен, входы активируются.

Вот код Вам необходимо установить стандартные атрибуты HTML на безопасные значения и добавить дополнительные атрибуты, которые содержат фактические значения. Вероятно, он неполон, и для полной безопасности дополнительные атрибуты (я имею в виду обработчики событий), вероятно, придется обрабатывать аналогичным образом:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html>
  <head>
    <title></title>
    <script><!--
      function replaceAttributeValuesWithActualOnes( array, attributeName, actualValueAttributeName, additionalProcessor ) {
        for ( var elementIndex = 0; elementIndex < array.length; elementIndex += 1 ) {
          var element = array[ elementIndex ];
          var actualValue = element.getAttribute( actualValueAttributeName );
          if ( actualValue != null ) {
            element[ attributeName ] = actualValue;
          }

          if ( additionalProcessor != null ) {
            additionalProcessor( element );
          }
        }
      }

      function detectFraming() {
        if ( top != self ) {
          document.getElementById( "framingWarning" ).style.display = "block";
        } else {
          replaceAttributeValuesWithActualOnes( document.links, "href", "acme:href" );

          replaceAttributeValuesWithActualOnes( document.forms, "action", "acme:action", function ( form ) {
            replaceAttributeValuesWithActualOnes( form.elements, "disabled", "acme:disabled" );
          });
        }
      }
      // -->
    </script>
  </head>
  <body onload="detectFraming()">
    <div id="framingWarning" style="display: none; border-style: solid; border-width: 4px; border-color: #F00; padding: 6px; background-color: #FFF; color: #F00;">
      <div>
        <b>SECURITY WARNING</b>: Acme App is displayed inside another page.
        To make sure your data is safe this page has been disabled.<br>
        <a href="framing-detection.html" target="_blank" style="color: #090">Continue working safely in a new tab/window</a>
      </div>
    </div>
    <p>
      Content. <a href="#" acme:href="javascript:window.alert( 'Action performed' );">Do something</a>
    </p>
    <form name="acmeForm" action="#" acme:action="real-action.html">
      <p>Name: <input type="text" name="name" value="" disabled="disabled" acme:disabled=""></p>
      <p><input type="submit" name="save" value="Save" disabled="disabled" acme:disabled=""></p>
    </form>
  </body>
</html>

Я собираюсь быть смелым и бросить свою шляпу в кольцо на этом (старое как оно есть), посмотрите, сколько отрицательных голосов я могу собрать.

Вот моя попытка, которая, кажется, работает везде, где я ее тестировал (Chrome20, IE8 и FF14):

(function() {
    if (top == self) {
        return;
    }

    setInterval(function() {
        top.location.replace(document.location);
        setTimeout(function() {
            var xhr = new XMLHttpRequest();
            xhr.open(
                'get',
                'http://mysite.tld/page-that-takes-a-while-to-load',
                false
            );
            xhr.send(null);
        }, 0);
    }, 1);
}());

Я поместил этот код в <head> и назвал это с конца <body> чтобы убедиться, что моя страница отображается до того, как она начинает спорить со злонамеренным кодом, не знаю, является ли это лучшим подходом, YMMV.

Как это работает?

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

Вот мысль за этим:

  • Установите функцию для запуска с минимально возможным интервалом. Основная концепция любого из реалистичных решений, которые я видел, состоит в том, чтобы заполнить планировщик большим количеством событий, чем имеет Buster-Buster.
  • Каждый раз, когда функция срабатывает, попробуйте изменить местоположение верхней рамки. Довольно очевидное требование.
  • Также запланируйте немедленное выполнение функции, которая займет много времени (таким образом, блокируя работу противодействия нарушению местоположения). Я выбрал синхронный XMLHttpRequest, потому что это единственный механизм, о котором я могу думать, который не требует (или, по крайней мере, не запрашивает) взаимодействия с пользователем и не отнимает процессорное время пользователя.

Для меня http://mysite.tld/page-that-takes-a-while-to-load (цель XHR) Я использовал скрипт PHP, который выглядит так:

<?php sleep(5);

Что просходит?

  • Chrome и Firefox ждут 5 секунд, пока XHR завершит работу, а затем успешно перенаправят на URL страницы в рамке.
  • IE перенаправляет почти сразу

Разве вы не можете избежать времени ожидания в Chrome и Firefox?

Очевидно нет. Сначала я указал XHR на URL, который вернул бы 404 - это не сработало в Firefox. Тогда я попробовал sleep(5); подход, который я в конечном итоге получил для этого ответа, затем я начал играть с продолжительностью сна различными способами. Я не мог найти никакой реальной картины поведения, но обнаружил, что если она слишком короткая, то Firefox не будет играть в мяч (Chrome и IE, похоже, ведут себя довольно хорошо). Я не знаю, что такое определение "слишком короткий" в реальном выражении, но кажется, что каждый раз срабатывает 5 секунд.


Если какой-нибудь мимолетный ниндзя Javascript захочет немного лучше объяснить, что происходит, почему это (вероятно) неправильно, ненадежно, худший код, который они когда-либо видели, и т.д., я с удовольствием выслушаю.

Итак, мы знаем, что были в кадре. Таким образом, мы location.href переходим к другой специальной странице с путем в качестве переменной GET. Теперь мы объясним пользователю, что происходит, и предоставим ссылку с параметром target="_TOP". Это просто и, вероятно, сработает (не проверял), но требует некоторого взаимодействия с пользователем. Может быть, вы могли бы указать на сайт оскорбительного пользователя и сделать так, чтобы где-то на вашем сайте было много позорных кликеров. Просто идея, но это работает ночью..

Ну, вы можете изменить значение счетчика, но это, очевидно, хрупкое решение. Вы можете загрузить свой контент через AJAX после того, как вы определили, что сайт находится за пределами фрейма - это тоже не лучшее решение, но, надеюсь, оно позволит избежать запуска события on beforeunload (я полагаю).

Изменить: еще одна идея. Если вы обнаружите, что находитесь в кадре, попросите пользователя отключить JavaScript, прежде чем щелкнуть ссылку, которая приведет вас к нужному URL-адресу (передав строку запроса, позволяющую вашей странице сообщить пользователю, что он может повторно включить JavaScript, когда они здесь).

Редактировать 2: Идите ядерным путем - если вы обнаружите, что находитесь в рамке, просто удалите содержимое тела документа и напечатайте какое-нибудь неприятное сообщение.

Редактировать 3: Можете ли вы перечислить верхний документ и установить все функции на ноль (даже анонимные)?

Если вы добавите предупреждение сразу после кода ошибки, тогда предупреждение остановит поток JavaScript и позволит странице загрузиться. Это то, что делает Stackru, и он вылетает из моих фреймов, даже когда я использую бастер. Это также работало с моей простой тестовой страницей. Это было проверено только в Firefox 3.5 и IE7 на Windows.

Код:

<script type="text/javascript">
if (top != self){
  top.location.replace(self.location.href);
  alert("for security reasons bla bla bla");
}
</script>

Если вы посмотрите на значения, возвращаемые setInterval() обычно они состоят из одной цифры, поэтому вы можете отключить все такие прерывания одной строкой кода:

for (var j = 0 ; j < 256 ; ++j) clearInterval(j)

Я думаю, что вы были почти там. Ты пытался:

window.parent.onbeforeunload = null;
window.parent.location.replace(self.location.href);

или, альтернативно:

window.parent.prevent_bust = 0;

Примечание: я на самом деле не проверял это.

А как насчет вызова бастера, а также несколько раз? Это создаст условия для гонки, но можно надеяться, что бастер выйдет победителем:

(function() {
    if(top !== self) {
        top.location.href = self.location.href;
        setTimeout(arguments.callee, 0);
    }
})();

Возможно, я только что получил способ уничтожить javascript. Используя getElementsByName в моей функции javascript, я установил петлю между обработчиком кадров и реальным сценарием уничтожения кадров. проверить этот пост. http://www.phcityonweb.com/frame-buster-buster-buster-2426

Используйте htaccess, чтобы избежать большого набора кадров, iframe и любого контента, например изображений.

RewriteEngine on
RewriteCond %{HTTP_REFERER} !^http://www\.yoursite\.com/ [NC]
RewriteCond %{HTTP_REFERER} !^$
RewriteRule ^(.*)$ /copyrights.html [L]

Это покажет страницу с авторскими правами вместо ожидаемой.

setInterval и setTimeout создают автоматически увеличивающийся интервал. Каждый раз, когда вызывается setTimeout или setInterval, это число увеличивается на единицу, поэтому, если вы вызовете setTimeout, вы получите текущее максимальное значение.

   var currentInterval = 10000;
   currentInterval += setTimeout( gotoHREF, 100 );
   for( var i = 0; i < currentInterval; i++ ) top.clearInterval( i );
   // Include setTimeout to avoid recursive functions.
   for( i = 0; i < currentInterval; i++ )     top.clearTimeout( i );

   function gotoHREF(){
           top.location.href = "http://your.url.here";
   }

Так как почти 10000 одновременных setIntervals и setTimeouts работают почти так же, и так как setTimeout возвращает "последний созданный интервал или тайм-аут + 1", и так как top.clearInterval по-прежнему доступен, это отразит атаки черной шляпы на кадр сайты, которые описаны выше.

Вы можете улучшить всю идею, используя postMessage(), чтобы разрешить некоторым доменам доступ к вашему контенту и отображать его, блокируя все остальные. Во-первых, родительский контейнер должен представиться, отправив сообщение вcontentWindow из iframeкоторый пытается отобразить вашу страницу. И ваша страница должна быть готова принимать сообщения,

window.addEventListener("message", receiveMessage, false);

function receiveMessage(event) {
  // Use event.origin here like
  if(event.origin == "https://perhapsyoucantrustthisdomain.com"){
  // code here to block/unblock access ... a method like the one in user1646111's post can be good.
  }
  else{
  // code here to block/unblock access ... a method like the one in user1646111's post can be good.
  }
}

Наконец, не забудьте обернуть вещи внутри функций, которые будут ждать load События.

Другие вопросы по тегам