Атака типу “clickjacking” (англ. “захоплення кліка”) дозволяє шкідливій сторінці натиснути посилання на “сайт-жертви” від імені відвідувача.
Багато сайтів були зламані подібним способом, включаючи Twitter, Facebook, Paypal та інші. Усі вони, звісно ж, зараз захищені.
Ідея
Ідея дуже проста.
Ось як clickjacking було зроблено на Facebook:
- Відвідувача заманюють на шкідливу сторінку. Не важливо як.
- На сторінці є нешкідливе посилання (наприклад, “розбагатіти зараз” або “натисніть тут, дуже смішно”).
- Над цим посиланням шкідлива сторінка розміщує прозорий
<iframe>
зsrc
з facebook.com таким чином, що кнопка “Подобається” знаходиться прямо над цим посиланням. Зазвичай це робиться за допомогоюz-index
. - Намагаючись натиснути посилання, відвідувач фактично натискає кнопку.
Демо
Ось як виглядає шкідлива сторінка. Щоб було зрозуміло, <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="/clickjacking/facebook.html"></iframe>
<button>Натисніть!</button>
<div>...І ти крутий (насправді я крутий хакер)!</div>
Повна демонстрація атаки:
<!DOCTYPE HTML>
<html>
<body style="margin:10px;padding:10px">
<input type="button" onclick="alert('Лайк натиснуто на facebook.html!')" value="I LIKE IT !">
</body>
</html>
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<style>
iframe {
width: 400px;
height: 100px;
position: absolute;
top: 5px;
left: -14px;
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, але не бачить його, оскільки iframe прозорий.
Як наслідок, якщо відвідувач авторизований у Facebook (як правило, “запам’ятати мене” включено), він додає “подобається”. У Twitter це була б кнопка “Підписатися”.
Ось той самий приклад, але ближчий до реальності, з opacity:0
для <iframe>
:
<!DOCTYPE HTML>
<html>
<body style="margin:10px;padding:10px">
<input type="button" onclick="alert('Лайк натиснуто на facebook.html!')" value="I LIKE IT !">
</body>
</html>
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<style>
iframe {
width: 400px;
height: 100px;
position: absolute;
top: 5px;
left: -14px;
opacity: 0;
z-index: 1;
}
</style>
<div>Натисніть, щоб розбагатіти:</div>
<!-- URL-адреса з сайту-жертви -->
<iframe src="facebook.html"></iframe>
<button>Натисніть тут!</button>
<div>...І ти крутий (насправді я крутий хакер)!</div>
</body>
</html>
Все, що нам потрібно для атаки – це розташувати <iframe>
на шкідливій сторінці таким чином, щоб кнопка знаходилась прямо над посиланням. Таким чином, коли користувач натискає посилання, він фактично натискає кнопку. Зазвичай це можна зробити за допомогою CSS.
Атака впливає лише на дії миші (або подібні, як-от натискання на мобільному пристрої).
Введення з клавіатури дуже важко переспрямувати. Технічно, якщо у нас є текстове поле для зламу, ми можемо розташувати iframe таким чином, щоб текстові поля перекривали одне одного. Тому, коли відвідувач намагається сфокосуватися на текстовому полі, яке він бачить на сторінці, він фактично фокусується на полі всередині iframe.
Але тоді виникає проблема. Усе, що введе відвідувач, буде приховано, оскільки iframe не видно.
Люди зазвичай припиняють вводити текст, коли не бачать, як на екрані друкуються нові символи.
Приклади слабкого захисту
Найстаріший спосіб захисту – це код JavaScript, який забороняє відкривати сторінку у фреймі (так званий “framebusting”).
Це виглядає так:
if (top != window) {
top.location = window.location;
}
Тобто: якщо вікно дізнається, що воно не зверху, то воно автоматично стає верхнім.
Це не надійніший засіб захисту, тому що є багато способів зламати його. Давайте розглянемо декілька.
Блокування top-навігації
Ми можемо заблокувати перехід, викликаний зміною top.location
в обробнику події beforeunload.
Зовнішня сторінка (що належить хакеру) встановлює обробник події для запобігання, наприклад:
window.onbeforeunload = function() {
return false;
};
Коли iframe
намагається змінити top.location
, відвідувач отримує повідомлення із запитом, чи хоче він піти.
У більшості випадків відвідувач відповість негативно, тому що він не знає про iframe – все, що він бачить, це верхня сторінка, немає причин залишати її. Тож top.location
не зміниться!
В дії:
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div>Змінює top.location на javascript.info</div>
<script>
top.location = 'https://javascript.info';
</script>
</body>
</html>
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<style>
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>Ймовірно, він відповів би "ні", а захист iframe зламано.</p>
<button onclick="attack()">Додайте "захищений" iframe</button>
</body>
</html>
Атрибут sandbox
Однією з речей, які можна обмежити атрибутом sandbox
є навігація. Ізольований iframe може не змінювати top.location
.
Тож ми можемо додати iframe за допомогою sandbox="allow-scripts allow-forms"
. Це послабить обмеження, дозволивши сценарії та форми. Але ми опускаємо allow-top-navigation
, щоб заборонити зміну top.location
.
Ось код:
<iframe sandbox="allow-scripts allow-forms" src="facebook.html"></iframe>
Є й інші способи обходу цього простого захисту.
X-Frame-Options
Заголовок на стороні сервера X-Frame-Options
може дозволяти або забороняти відображення сторінки всередині фрейму.
Він має бути надісланий точно як HTTP-заголовок: браузер проігнорує його, якщо знайде в HTML <meta>
тегу. Отже, <meta http-equiv="X-Frame-Options"...>
нічого не дасть.
Заголовок може мати 3 значення:
DENY
- Ніколи не показувати сторінку всередині фрейму.
SAMEORIGIN
- Дозволити показ всередині фрейму, якщо батьківський документ походить із того самого джерела.
ALLOW-FROM domain
- Дозволити показ всередині фрейму, якщо батьківський документ із заданого домену.
Наприклад, Twitter використовує X-Frame-Options: SAMEORIGIN
.
Ось результат:
<iframe src="https://twitter.com"></iframe>
Залежно від вашого браузера, iframe
вище або порожній, або попереджає вас про те, що браузер не дозволяє відобразити цю сторінку.
Відображення з вимкненою функціональністю
Заголовок X-Frame-Options
має побічний ефект. Інші сайти не зможуть показати нашу сторінку у фреймі, навіть якщо у них є для цього вагомі причини.
Тому є інші рішення…Наприклад, ми можемо “покрити” сторінку <div>
зі стилями height: 100%; width: 100%;
, щоб він перехоплював усі клацання. Цей <div>
можна видалити, якщо window == top
або якщо ми зрозуміли, що захист нам не потрібен.
Щось на зразок цього:
<style>
#protector {
height: 100%;
width: 100%;
position: absolute;
left: 0;
top: 0;
z-index: 99999999;
}
</style>
<div id="protector">
<a href="/" target="_blank">Перейти на сайт</a>
</div>
<script>
// буде помилка, якщо верхнє вікно має інше походження
// але тут гаразд
if (top.document.domain == document.domain) {
protector.remove();
}
</script>
Демо:
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<style>
#protector {
height: 100%;
width: 100%;
position: absolute;
left: 0;
top: 0;
z-index: 99999999;
}
</style>
</head>
<body>
<div id="protector">
<a href="/" target="_blank">Перейти на сайт</a>
</div>
<script>
if (top.document.domain == document.domain) {
protector.remove();
}
</script>
Цей текст завжди видно.
Але якщо сторінка була відкрита всередині документа з іншого домену, div над нею завадить будь-яким діям.
<button onclick="alert(1)">Клік у даному випадку не спрацює</button>
</body>
</html>
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<iframe src="iframe.html"></iframe>
</body>
</html>
Атрибут cookie: samesite
Атрибут cookie samesite
також може запобігти clickjacking атакам.
Файл cookie з таким атрибутом надсилається на веб-сайт, лише якщо його відкрито безпосередньо, а не через фрейм чи іншим чином. Більше інформації в розділі Файли cookies, document.cookie.
Якби сайт, наприклад Facebook, при аутентифікації мав атрибут samesite
у файлі cookie, ось так:
Set-Cookie: authorization=secret; samesite
…Тоді такий файл cookie не надсилатиметься, коли Facebook буде відкрито в iframe з іншого сайту. Тож атака не вдасться.
Атрибут cookie samesite
не матиме ефекту, якщо файли cookies не використовуються. Це може дозволити іншим веб-сайтам легко показувати наші загальнодоступні, неавтентифіковані сторінки в iframes.
Однак це також може дозволяти атакам за допомогою clickjacking працювати в кількох обмежених випадках. Наприклад, веб-сайт анонімного опитування, який запобігає дублюванню голосування шляхом перевірки IP-адреси, все одно буде вразливим до clickjacking атаки, оскільки він не автентифікує користувачів за допомогою файлів cookie.
Підсумки
Clickjacking – це спосіб “обдурити” користувачів, щоб вони натиснули на сайт-жертву, навіть не знаючи, що відбувається. Це небезпечно, якщо є важливі дії, які активуються кліком.
Хакер може розмістити в повідомленні посилання на свою шкідливу сторінку або заманити відвідувачів якимось іншим способом. Існує багато варіацій.
З однієї точки зору – атака “не глибока”: все, що робить хакер, це перехоплює один клік. Але з іншої – якщо хакер знає, що після натискання з’явиться інший елемент керування, він може використовувати хитрі повідомлення, щоб змусити користувача натиснути на них.
Атака досить небезпечна, тому що коли ми розробляємо інтерфейс користувача, ми зазвичай не очікуємо, що хакер може клацнути від імені відвідувача. Тому вразливість можна знайти в абсолютно несподіваних місцях.
- Рекомендується використовувати
X-Frame-Options: SAMEORIGIN
на сторінках (або веб-сайтах загалом), які не призначені для перегляду всередині фреймів. - Використовуйте
<div>
прикриття, якщо потрібно дозволити показ сторінок у iframes та залишатися в безпеці.