Сторонний CSS небезопасен
Перевод статьи Third party CSS is not safe с сайта jakearchibald.com, опубликовано на css-live.ru с разрешения автора — Джейка Арчибальда.
Несколько дней назад немало шума наделал «кейлоггер», написанный на CSS.
Одни требовали, чтобы браузеры это «починили». Другие вникли чуть поглубже и заметили, что уязвимость коснулась лишь сайтов, написанных на фреймворках типа Реакта — мол, это Реакт виноват. Но на самом деле проблема в том, что сторонний контент считают «безопасным».
Сторонние картинки
<img src="https://example.com/kitten.jpg">
Если я включаю этот код в свой, я доверяю домену example.com
. Он может обмануть мое доверие, удалив ресурс и выдав мне ошибку-404, так что моя страница будет выглядеть поломанной. Или заменить милого котика на что-то куда менее приятное.
Тем не менее, влияние картинки ограничено областью контента самого элемента. В моих силах объяснить пользователям: «Здесь кое-какой контент с example.com
, если там какая-то гадость, то это они виноваты, не я», авось они мне поверят. И она уж точно не затронет чего-нибудь типа полей пароля.
Сторонний скрипт
<script src="https://example.com/script.js"></script>
По сравнению с картинками, у стороннего скрипта куда больше возможностей. Если я вставлю в свой код строчку выше, я дам домену example.com
полный контроль над сайтом. Он сможет:
- Читать/менять содержимое страницы.
- Следить буквально за каждым действием пользователя.
- Запускать тяжелые вычисления (скажем, майнер криптовалюты).
- Посылать запросы на мой сервер с куками пользователя и пересылать ответ кому-то еще.
- Читать/менять доменное хранилище.
- …да практически всё, что пожелает.
Момент насчет «доменного хранилища» важен. Если скрипт доберется до indexedDB или API хранилища кеша, весь домен окажется под угрозой, даже после удаления вредоносного скрипта.
Если вы включаете скрипт с другого домена в свой, вы должны доверять ему и его безопасности абсолютно.
Если вы пострадали от нехорошего скрипта, вам нужно вычистить все данные сайта с помощью заголовка Clear-Site-Data.
Сторонний CSS
<link rel="stylesheet" href="https://example.com/style.css">
CSS гораздо ближе по возможностям к скрипту, чем к картинке. Как и скрипт, он действует на всю страницу. Он может:
- Удалять/добавлять/изменять содержимое страницы.
- Посылать запросы в зависимости от содержимого страницы.
- Реагировать на многие действия пользователя.
CSS не может менять доменное хранилище, и на CSS нельзя написать майнер криптовалюты (скорее всего, наверное, не знаю), но вредоносный CSS всё же может наделать немало бед.
Кейлоггер
Начнем с того, к чему было столько внимания:
input[type="password"][value$="p"] { background: url('/password?p'); }
Этот код вызовет запрос к /password?p
, если значение атрибута value
оканчивается на p
. Сделайте так с каждым символом, и немало занятных данных у вас в руках.
Браузеры по умолчанию не хранят введенные пользователем значения в атрибуте value
, так что атака зависит от чего-то, что синхронизирует эти значения, скажем, от Реакта.
Чтобы защититься от этого, Реакт мог бы поискать другой способ синхронизации полей пароля, либо браузеры могли бы урезать возможности селекторов по атрибуту value
полей пароля. Но это дало бы ложное чувство безопасности. Это решило бы один частный случай проблемы, но всё остальное осталось бы неприкрытым.
Если Реакт перейдет на атрибут data-value
, защите конец. Если сайт поменяет поле на type="text"
, чтобы пользователи могли видеть, что набирают, защите конец. Если сайт заведет элемент <better-password-input>
и будет выводить значения в его атрибуте, защите конец.
Кроме этого, есть много других атак через CSS:
Скрытие контента
body { display: none; } html::after { content: 'HTTP 500 Server Error'; }
Это крайний пример, но представьте, если бы сторонний CSS делал такое для небольшого процента ваших пользователей. Вы бы едва ли смогли такое отладить, а доверие ваших пользователей было бы подорвано.
А менее грубые хаки могли бы просто невзначай убрать кнопку «купить», либо поменять местами несколько абзацев.
Добавление контента
.price-value::before { content: '1'; }
Вот незадача, вам только что повысили цены.
Перемещение контента
.delete-everything-button { opacity: 0; position: absolute; top: 500px; left: 300px; }
Возьмите кнопку, удаляющую что-нибудь важное, сделайте ее невидимой и положите ее поверх чего-то, на что пользователь наверняка захочет кликнуть.
На наше счастье, если кнопка удаляет что-то действительно важное, сайт скорее всего сперва покажет диалог подтверждения. Не вопрос, добавьте лишь еще чуть-чуть CSS и таким же обманом заставьте пользователя нажать кнопку «да, я согласен» вместо кнопки «нет, ни за что».
Представьте, что браузеры попытались защититься от трюка с «кейлоггером». Хакеры смогут просто взять любое другое поле на странице (поле поиска, как вариант) и положить его поверх поля пароля. И всё, они опять в игре.
Чтение атрибутов
Не только о паролях нужно беспокоиться. У вас наверняка есть другая личная информация в атрибутах:
<input type="hidden" name="csrf" value="1687594325"> <img src="/avatars/samanthasmith83.jpg"> <iframe src="//cool-maps-service/show?st-pancras-london"></iframe> <img src="/gender-icons/female.png"> <div class="banner users-birthday-today"></div>
Всё это можно выбрать CSS-селекторами, и полученную информацию можно передать в запрос на сервер.
Слежка за действиями
.login-button:hover { background: url('/login-button-hover'); } .login-button:active { background: url('/login-button-active'); }
О наведениях курсора и нажатиях можно сообщать серверу. С умеренным количеством CSS вы можете составить неплохую картину того, что собирается делать пользователь.
Чтение текста
@font-face { font-family: blah; src: url('/page-contains-q') format('woff'); unicode-range: U+85; } html { font-family: blah, sans-serif; }
В этом случае будет отправлен запрос, если на странице есть буква q
. Можно сделать много таких для разных букв и проверять конкретные элементы. В шрифтах бывают еще и лигатуры, так что можно отлавливать целые последовательности символов. Можно даже объединить трюки со шрифтами и трюки с обнаружением скроллбара, чтобы узнать о контенте еще больше.
Сторонний контент небезопасен
Это лишь немногие трюки, о которых я знаю, но уверен, что есть еще масса других.
Сторонний контент может много на что повлиять в своей «песочнице». У картинки или iframe с атрибутом sandbox «песочница» невелика, но скрипты и стили действуют в пределах целой страницы, а то и вовсе целого домена.
Если вы беспокоитесь о том, чтобы пользователи какой-нибудь хитростью не заставили ваш сайт грузить сторонние ресурсы, в качестве этакой страховочной сетки можно использовать CSP (политику безопасности контента), ограничив список мест, откуда можно запрашивать картинки, скрипты и стили.
Можно также воспользоваться Subresource Integrity (проверкой целостности составных ресурсов), чтобы убедиться, что содержимое скриптового/стилевого файла совпадает с определенным хешем, в противном случае оно не выполнится. Спасибо Piskvorrr с Hacker News за напоминание!
Если вам интересны подобные хаки, включая подробности о трюках со скроллбаром, посмотрите доклад 2014 г. Матиаса Байненса , доклад 2013 г. Майка Уэста или статью 2012 г. Марио Хайдериха c соавторами (PDF) (прим. перев.: еще есть замечательный доклад 2016 г. на русском «Как хакнуть фронтенд» Владимира Дашукевича, вот его слайды). Да, это вовсе не что-то новое.
P.S. Это тоже может быть интересно:
Спасибо за перевод отличной и познавательной статьи. Продолжайте в том же темпе.
Интересно а какие есть подводные камни у технологий, вроде postcss-next?
Насколько я понимаю, главный «подводный камень» в том, что на выходе у них всё равно обычный CSS, со всеми его ограничениями и потенциальными уязвимостями. И если автор смотрит лишь в исходник, не проверяя скомпилированный результат, этот результат может его удивить (как минимум, размером кода, получающегося при разворачивании строчки типа
:matches(.component-1, .component-2) :matches(h1, h2, h3) { all:initial }
для старых IE:). Так что если кто-то подключает такой код со стороны, надо быть вдвойне внимательными.Ну и конкретно с postcss-next есть тонкость, что он пытается «полифилить», в числе прочего, совсем экспериментальные черновики, которые не факт что дойдут до стандартизации, и уж точно не факт, что именно в таком виде (например, от многообещающего когда-то правила @apply сам Таб Аткинс позже отказался). Так что нужно отслеживать такие изменения и оперативно обновлять код, иначе есть риск вместо будущей совместимости наплодить зоопарк несовместимых синтаксисов…
Полифилить — безопасно?..
И, кстати, достаточно ли быстро?