То, что вы видите, это не то, что вы получаете - Clickjacking
Last updated
Last updated
В этой последней главе мы рассмотрим некоторые темы безопасности, которые сложнее классифицировать и охватим более широкий спектр контента.
Сначала давайте взглянем на Clickjacking. Clickjacking — это когда вы думаете, что нажимаете на что-то на Веб-сайте A, но на самом деле вы нажимаете на что-то на Веб-сайте B. Ваш клик "угоняется" с Веб-сайта A на Веб-сайт B.
Какой импакт может создать простой клик?
Предположим, что страница, на которую вы нажимаете, — это страница банковского перевода, и ваш номер счета и сумма уже заполнены. Все, что нужно, — это одно нажатие кнопки, чтобы перевести деньги. Это может быть очень опасно (это всего лишь пример, но он иллюстрирует, почему для переводов необходима дополнительная проверка).
Или давайте возьмем более распространенный пример. Предположим, есть страница, которая выглядит как страница отмены подписки на рассылку. Вы нажимаете кнопку "Подтвердить отмену", но под ней на самом деле находится кнопка "Нравится" на Facebook. Таким образом, вы не только не отменили подписку, но и невольно поставили лайк чему-то. Этот тип атаки также известен как Likejacking.
Теперь давайте углубимся в этот метод атаки!
Принцип Clickjacking заключается в наложении двух веб-страниц, где пользователь видит Веб-сайт A, но кликает на Веб-сайт B.
В более технических терминах это достигается путем встраивания Веб-сайта B с помощью iframe с прозрачностью 0.001, а затем наложения его на содержимое Веб-сайта A с помощью CSS.
Мне кажется наиболее интересным и понятным объяснять Clickjacking через примеры. Обратите внимание на GIF ниже:
Некоторым людям этот пример может показаться слишком простым, и в реальных приложениях такие простые атаки, которые требуют всего лишь одного нажатия кнопки, могут быть редкостью. Возможно, на более сложных веб-сайтах пользователю потребуется сначала ввести какую-то информацию?
В следующем примере Clickjacking разработан для функции "Изменить электронную почту". В отличие от предыдущего примера, где вся веб-страница была покрыта, в этом примере намеренно оставляется ввод с оригинальной веб-страницы, а все остальное накрывается с помощью CSS. Часть с кнопкой использует pointer-events:none
, чтобы события могли проходить сквозь.
Веб-страница выглядит как страница для ввода информации о подписке на электронную почту, но после нажатия "Подтвердить" появляется сообщение "Изменение электронной почты успешно", потому что за ней на самом деле находится веб-страница для изменения электронной почты:
Процесс атаки Clickjacking можно кратко описать следующим образом:
Встраивание целевой веб-страницы в контролируемую веб-страницу (с использованием iframes или аналогичных тегов).
Использование CSS на контролируемой веб-странице для наложения целевой веб-страницы, делая её невидимой для пользователя.
Перенаправление пользователя на контролируемую веб-страницу и побуждение его выполнять действия (такие как ввод или клики).
Запуск функций целевой веб-страницы для достижения атаки.
Таким образом, сложность атаки зависит от того, насколько хорошо спроектирована контролируемая веб-страница и сколько взаимодействия требует целевая веб-страница. Например, клик по кнопке гораздо проще, чем ввод информации.
Также стоит отметить, что для проведения такого типа атаки пользователь должен уже быть авторизован на целевом веб-сайте. Пока целевая веб-страница может быть встроена в контролируемую веб-страницу, существует риск Clickjacking.
Как уже упоминалось, если веб-страница не может быть встроена в другую веб-страницу, риск Clickjacking отсутствует. Это основное решение для защиты от Clickjacking.
В общем, существует два типа защиты от Clickjacking. Один из них — использование JavaScript для проверки, а другой — информирование браузера через заголовки ответа о том, может ли веб-страница быть встроена.
Один из методов, называемый Frame Busting, заключается в использовании JavaScript для проверки, как я уже упоминал. Принцип прост, и код тоже:
Каждая веб-страница имеет свой собственный объект window
, и window.self
ссылается на своё собственное окно. top
ссылается на верхнее окно, которое можно рассматривать как верхний уровень окна всей вкладки браузера.
Если веб-страница открыта независимо, top
и self
будут указывать на одно и то же окно. Однако если веб-страница встроена в iframe
, top
будет ссылаться на окно, использующее iframe
.
Рассмотрим пример. Предположим, у меня есть index.html
на localhost
, который содержит следующий код:
Схема взаимоотношений будет выглядеть так:
Зеленый и желтый представляют две веб-страницы, загруженные в iframes
, которые являются двумя разными окнами. Если вы получите доступ к top
внутри этих веб-страниц, он будет ссылаться на объект окна localhost/index.html
.
Поэтому, проверяя if (top !== self)
, вы можете определить, помещена ли веб-страница внутри iframe
. Если это так, вы можете изменить top.location
, чтобы перенаправить верхнюю веб-страницу куда-то еще.
Это звучит отлично и, кажется, не имеет проблем, но его можно обойти с помощью атрибута sandbox
у iframes
.
У iframe
есть атрибут, называемый sandbox
, который ограничивает функциональность iframe
. Если вы хотите убрать ограничения, вы должны явно указать их. Существует множество возможных значений, но я перечислю несколько:
allow-forms
- Разрешает отправку форм.
allow-scripts
- Разрешает выполнение JavaScript.
allow-top-navigation
- Разрешает изменение top location
.
allow-popups
- Разрешает всплывающие окна.
Другими словами, если я загружу iframe
так:
Даже если busting.html
имеет защиту, о которой я упоминал ранее, она не сработает, потому что у неё нет allow-scripts
, и JavaScript не может быть выполнен. Однако пользователи все равно могут отправлять формы как обычно.
Сначала скрываем всю веб-страницу, которая может быть открыта только при выполнении JavaScript. Таким образом, если вы заблокируете выполнение скриптов с помощью упомянутого sandbox
, вы увидите только пустую страницу.
Если вы не используете sandbox
, проверка JavaScript не сработает, и вы все равно увидите пустую страницу. Хотя это может обеспечить более полную защиту, у этого метода есть и недостатки. Недостаток в том, что если пользователи добровольно отключат JavaScript, они не увидят ничего.
Таким образом, для пользователей, которые отключают JavaScript, опыт будет довольно плохим. Когда Clickjacking впервые появился в 2008 году, не существовало таких методов защиты, поэтому нам приходилось использовать эти обходные решения. Теперь браузеры имеют улучшенные методы защиты от встраивания веб-страниц.
Этот заголовок может иметь три следующих значения:
DENY
SAMEORIGIN
ALLOW-FROM https://example.com/
Первое значение отклоняет любую веб-страницу от встраивания этой веб-страницы, включая теги <iframe>
, <frame>
, <object>
, <applet>
, или <embed>
.
Второе значение позволяет делать это только веб-страницам с тем же Origin, а последнее значение позволяет встраивание только из определенных Origin. Кроме того, никакое встраивание не разрешается (можно указать только одно значение, поэтому, если требуется несколько Origin, их нужно динамически настраивать на сервере, аналогично заголовкам CORS).
RFC специально упоминает, что определение последних двух значений может отличаться от ожидаемого, и реализация в каждом браузере может варьироваться.
Например, некоторые браузеры могут проверять только "родительский" и "верхний" уровни, а не проверять каждый уровень. Что означает "уровень"?
Потому что теоретически iframe
может иметь бесконечное количество уровней, таких как A встраивает B, B встраивает C, C встраивает D и так далее.
Если мы представим эти отношения в текстовом виде, это будет выглядеть так:
Для самой внутренней target.html
, если браузер проверяет только родительский уровень (B.html) и верхний уровень (A.html), то даже если он установлен на X-Frame-Options: SAMEORIGIN, проверка пройдет, потому что эти два уровня действительно имеют один и тот же Origin. Однако на самом деле между ними находится веб-страница атакующего, поэтому все еще существует риск атаки.
Кроме того, есть вторая проблема с X-Frame-Options
, которая заключается в плохой поддержке ALLOW-FROM
. На данный момент, в 2023 году, основные браузеры не поддерживают директиву ALLOW-FROM
.
Первоначальная буква X
в X-Frame-Options
указывает на то, что это скорее временное решение. В современных браузерах его функциональность была заменена на Content Security Policy (CSP)
, которая также решает упомянутые ранее проблемы.
CSP
имеет директиву под названием frame-ancestors
, которую можно установить следующим образом:
frame-ancestors 'none'
frame-ancestors 'self'
frame-ancestors https://a.example.com https://b.example.com
Эти три опции соответствуют предыдущим директивам X-Frame-Options
: DENY
, SAMEORIGIN
и ALLOW-FROM
(с поддержкой нескольких источников на этот раз).
Давайте проясним потенциальную путаницу: поведение, ограниченное frame-ancestors
, такое же, как и у X-Frame-Options
, то есть "какие веб-страницы могут встраивать меня с помощью iframe
". С другой стороны, правило CSP frame-src
определяет "какие источники могут загружаться на моей веб-странице".
Например, если я установлю frame-src: 'none'
в index.html
, любая веб-страница, загруженная внутри iframe
в index.html
, будет заблокирована, независимо от её собственных настроек.
В общем, для успешного отображения iframe
обе стороны должны согласиться; если одна из сторон не согласна, это приведет к сбою.
Кроме того, стоит отметить, что frame-ancestors
— это правило, поддерживаемое только в CSP
уровня 2, которое постепенно принимается основными браузерами, начиная с конца 2014 года.
Из-за различного уровня поддержки рекомендуется использовать как X-Frame-Options
, так и frame-ancestors CSP
. Если вы не хотите, чтобы ваша веб-страница загружалась внутри iframe
, не забудьте добавить следующие заголовки HTTP-ответа:
Если вы разрешаете загрузку только с тем же Origin, установите это как:
Если вы хотите указать список разрешенных Origin's, используйте:
Наконец, есть еще один механизм защиты, который уже реализован в браузерах. Можете ли вы вспомнить, что это?
Это куки по умолчанию SameSite=Lax
! С их помощью веб-страницы, встроенные в iframes
, не будут отправлять куки на сервер, тем самым не выполняя предварительное условие для атак Clickjacking
, которое заключается в том, что "пользователь должен быть авторизован". С этой точки зрения, помимо упомянутого ранее CSRF, куки Same-Site также решают многие другие проблемы безопасности.
Один из отчетов обсуждал страницу бронирования ресторана. После входа на страницу личная информация пользователя автоматически заполняется, и он может успешно сделать бронирование, нажав кнопку. Таким образом, целью Clickjacking является эта кнопка бронирования.
Каковы последствия, если пользователь неосознанно нажимает кнопку бронирования? Во-первых, злоумышленник может зарегистрировать ресторан и:
Увидеть информацию о людях, которые сделали бронирование, и украсть их адреса электронной почты.
Чтобы отменить бронирование, необходимо заплатить сбор за отмену.
Даже не регистрируя ресторан, все равно можно атаковать. Например, если мне не нравится определенный ресторан, я могу намеренно поделиться их страницей бронирования и создать много поддельных бронирований, что затруднит ресторану определение настоящих заказов.
Эта уязвимость довольно интересна и использует проблемы реализации браузера, упомянутые ранее.
В этом случае Twitter уже установил X-Frame-Options: SAMEORIGIN
и Content-Security-Policy: frame-ancestors 'self'
. Однако во время проверки реализации в некоторых браузерах проверялся только верхний уровень окна на соответствие.
Другими словами, если это было twitter.com => attacker.com => twitter.com, проверка пройдет, позволяя встраивать веб-страницы.
Более того, эта уязвимость возникла в ленте Twitter, поэтому она могла достичь эффекта, похожего на червя. После Clickjacking она отправляла твиты, которые видели бы больше людей, что приводило к тому, что больше людей отправляли бы те же твиты.
Эта ошибка была вызвана проблемами совместимости. Веб-страница установила только X-Frame-Options: ALLOW-FROM
без установки CSP
, что неэффективно, потому что современные браузеры не поддерживают ALLOW-FROM
. Влияние, которое это может оказать, заключается в том, что на сайте есть кнопка "Deactivate Account", которая может ввести пользователей в заблуждение и заставить их нажать на нее без их ведома.
Решение простое: просто используйте frame-ancestors CSP
, который поддерживается современными браузерами.
Я специально выбрал этот случай, потому что это цепочка атак!
Ранее существовал тип уязвимости, называемый Self-XSS, при котором только пользователь мог инициировать XSS. Поэтому многие программы BugBounty не принимают этот тип уязвимости, потому что он имеет небольшое влияние.
Этот отчет сочетает Self-XSS с Clickjacking, позволяя пользователям инициировать Self XSS через Clickjacking, что делает атаку легче осуществимой и более реальной.
Как работает эта цепочка?
Сначала пользователю предлагается нажать кнопку, тайно копируя полезную нагрузку XSS в фоновом режиме. Затем пользователя просят вставить ее в другое поле ввода и нажать другую кнопку. Это поле ввода на самом деле является полем для имени пользователя, а последняя кнопка — "Update Data". Следуя инструкциям, пользователь неосознанно изменяет свое имя пользователя на полезную нагрузку XSS.
Это некоторые практические примеры, связанные с Clickjacking. Стоит отметить, что некоторые проблемы вызваны проблемами совместимости, а не неправильными настройками, поэтому правильная конфигурация также важна.
Защита от Clickjacking в основном заключается в том, чтобы не позволять другим встраивать вашу веб-страницу. Но что делать, если цель веб-страницы — позволить другим встраивать её? Что следует делать в случае виджетов, таких как виджет Facebook, который включает кнопки "Like" и "Share", предназначенные для встраивания с помощью iframes?
Согласно этим двум статьям:
Полученная информация может в настоящее время лишь немного ухудшить пользовательский опыт в обмен на безопасность. Например, после нажатия кнопки появится всплывающее окно для подтверждения, что добавляет еще один клик для пользователя, но также избегает риска Likejacking.
В качестве альтернативы, я предполагаю, что это поведение также может зависеть от источника веб-сайта. Например, на более авторитетных веб-сайтах это всплывающее окно может не появляться.
Если Likejacking успешен, нажатие кнопки вызовет "Like" страницы Facebook Developer Plugin (я успешно тестировал это сам). Вы можете попробовать это и затем нажать "Show Original page", чтобы увидеть, что находится под кнопкой, а также убрать "Like" со страницы.
По сравнению с тем временем, когда поддержка браузеров была не такой полной, сейчас нам гораздо лучше. Браузеры внедрили все больше и больше функций безопасности и новых заголовков ответа, чтобы защитить пользователей от атак. Хотя Clickjacking стал все более трудным для осуществления с появлением куки по умолчанию Same-Site, все же важно помнить о необходимости установки X-Frame-Options
и CSP
, упомянутых в статье. В конце концов, так работает кибербезопасность: наличие дополнительного уровня защиты всегда полезно.
Дополнительные ссылки:
Я думал, что нажал "Да" и отменил подписку на электронную почту, но на самом деле я нажал "Удалить аккаунт". Это и есть Clickjacking. Если вы хотите испытать это на себе, вы можете попробовать на этой веб-странице: .
Веб-версия, с которой можно поэкспериментировать, также доступна по ссылке: .
Поэтому кто-то предложил более практический подход, улучшив существующий метод (код взят из: ):
Этот заголовок HTTP-ответа был впервые реализован в IE8 в 2009 году, и другие браузеры последовали его примеру. Он стал полноценным в 2013.
Другой пример: если мой index.html
установлен на frame-src:
, но example.com
имеет установленный frame-ancestors: 'none'
, index.html
все равно не сможет загрузить example.com
внутри iframe
, потому что это будет отклонено с другой стороны.
В 2018 году hk755a сообщил о двух уязвимостях Clickjacking в Yelp, крупнейшем сайте отзывов о ресторанах в США. Уязвимости были названы: и
Сначала давайте рассмотрим уязвимость, о которой сообщил filedescriptor в Twitter в 2015 году: .
Запись автора отличная, но блог недоступен. Вот архив:
Еще один отчет был представлен eo420 в Periscope, дочерней компании Twitter, в 2019 году: .
В 2020 году fuzzme сообщил о уязвимости в Tumblr:.
Я создал простую демонстрационную веб-страницу: