Третья линия обороны против XSS - сокращение области воздействия
Last updated
Last updated
В первой части защиты от XSS, мы научились обрабатывать пользовательский ввод, чтобы убедиться, что он закодирован или санитизирован, предотвращая вставку вредоносного контента. Вторая часть защиты - это знакомство с механизмом работы CSP, который предотвращает выполнение JavaScript и загрузку внешних ресурсов, даже если вредоносный контент вставлен.
Теперь о третьей линии обороны, которая предполагает, что XSS будет существовать и мы будем строить свои стратегии защиты на основе этого предположения.
Некоторые могут задаться вопросом, зачем нам это делать. Разве первых двух способов защиты недостаточно для предотвращения атак XSS? Почему нам нужно предполагать, что XSS произойдет, и разрабатывать стратегии для смягчения ущерба? Разве это не ставит телегу впереди лошади?
Давайте я приведу пример. Вы, наверное, видели фильмы типа «Миссия невыполнима», где есть сцены с кражей. Место, где хранятся ценные предметы, всегда оборудовано многоуровневыми мерами безопасности. Там есть распознавание сетчатки, распознавание лица, распознавание голоса, и даже распознавание походки. После прохождения этих проверок, вам все еще нужен ключ, чтобы открыть дверь хранилища. За дверью может быть сейф, и вам потребуется другой набор паролей, чтобы открыть сейф и наконец получить предмет
Причина наличия нескольких слоев безопасности проста: увеличение безопасности. Хотя некоторые дизайны могут показаться надежными, вы никогда не можете гарантировать, что они не будут нарушены. Поэтому нужны дополнительные слои защиты, чтобы убедиться, что "только когда каждый слой нарушен, произойдет ущерб", тем самым увеличивая затраты для злоумышленников.
Тот же самый принцип применим для кибербезопасности. Даже если мы тщательно проверяем код бэкенда и убеждаемся, что каждая часть должным образом проверяется и кодируется, мы не можем гарантировать, что фронтэнд никогда не будет уязвим для XSS. Новые разработчики могут допустить ошибки и написать небезопасный код, а сторонние библиотеки могут иметь уязвимости 0-day или быть скомпрометированы вредоносным кодом. Все это риски.
Поэтому мы добавляем CSP, который по крайней мере гарантирует, что если первый слой будет нарушен, второй слой все равно сможет заблокировать атаку, предотвращая загрузку злоумышленниками внешних ресурсов или отправку данных. Конечно, второй слой тоже не является неприступным. В следующих частях мы увидим техники обхода правил CSP, который делают CSP неэффективной.
После принятия всех возможных мер защиты от XSS, третий слой - это подумать в обратном направлении: "Если XSS неизбежно произойдет, что мы можем сделать, чтобы свести ущерб к минимуму?" Это добавляет дополнительный слой безопасности, так что даже если XSS и произойдет, воздействие не будет таким серьезным.
Прежде всего, я хочу уточнить, что каждая компания и продукт должны выбирать подходящие меры защиты на основе своих оценок кибербезопасности. Если говорить более профессионально, это зависит от их аппетита к риску. Какой риск они готовы принять?
Хотя добавление дополнительного слоя некоторым образом увеличивает безопасность, это также добавляет затраты и усложняет процесс. Не каждый продукт требует такой строгой защиты. Например, в случае с каким-нибудь простым блогом, даже если он подвержен XSS, воздействие минимально. Поэтому даже CSP не особо нужен или рассмотрение того, как смягчить ущерб, вызванный XSS.
С другой стороны, для криптовалютной биржи, если холодный кошелек будет украден или иным образом скомпрометирован, убытки могут быть значительными. Поэтому они обычно используют ряд мер управления рисками. Например, сам холодный кошелек хранится в высоко зашифрованном устройстве, которое затем помещается в огне- и водонепроницаемый сейф. Этот сейф находится в комнате с круглосуточной охраной, а доступ в комнату требует распознавания отпечатков пальцев и ключа, среди прочих мер.
Зная преимущества различных слоев, вы сможете сразу же определить возможные решения и оценить их затраты и выгоды. Чем больше у вас информации, тем лучше вы сможете определить, следует ли использовать эти решения.
Прежде чем обсуждать меры защиты для третьего слоя, нам нужно понять, что злоумышленник может сделать, как только обнаружит уязвимость XSS.
Когда злоумышленник обнаруживает уязвимость XSS, он может выполнить код JavaScript на странице, когда пользователь ее открывает. Поэтому наиболее общими действиями являются воровство токенов, используемых для аутентификации, или непосредственный вызов API для выполнения опасных операций, таких как изменение паролей или переводы. Они могут также украсть данные, такие как личная информация или записи транзакций.
Чтобы уменьшить воздействие XSS, нам нужно найти способы ограничить то, что злоумышленники могут сделать.
Почему злоумышленники могут получить доступ к данным или выполнить операции после XSS? Это потому, что бэкенд сайта считает запрос законным, будь то от самого пользователя или, чтобы выразиться техническим языком, в запросе содержится токен, который может подтвердить идентификацию пользователя. Бэкенд доверяет этому запросу и выполняет операцию.
Таким образом, одно из наиболее эффективных решений - это введение двухфакторной аутентификации. В дополнение к токену сервер должен требовать другую информацию, которую знает только пользователь, тем самым уменьшая возможный вред.
Например, при выполнении перевода в интернет-банкинге, после ввода суммы и адреса получателя, обычно есть дополнительный шаг, такой как ввод предопределенного пароля для интернет-банкинга или получение кода подтверждения через СМС. Это гарантирует дополнительный уровень безопасности. Например, если у банковского сайта есть уязвимость XSS, и злоумышленник может выполнить произвольный код на банковской странице, без дополнительного шага по обеспечению безопасности, злоумышленник мог бы просто вызвать API /transfer и перевести ваши деньги.
Однако с дополнительным шагом одним из параметров для /transfer будет код подтверждения через СМС. Поскольку злоумышленник не знает код подтверждения, он не может успешно вызвать API, и, следовательно, не может украсть ваши деньги.
Вы заметите, что для значительных операций обычно требуется дополнительный шаг, например, ввод текущего пароля для его изменения или получение кода подтверждения через СМС для переводов. Концепция похожа.
И помимо XSS, это также гарантирует, что "даже если кто-то физически получает доступ к вашему компьютеру, они не смогут сделать ничего вредоносного." Это показывает значительное улучшение в области безопасности. Обычно безопасность и пользовательский опыт обратно пропорциональны. Чем выше безопасность, тем хуже опыт, потому что вам приходится делать больше, и это неизбежно.
Например, самый безопасный подход - требовать новое СМС-сообщения для каждого вызова API, что очень безопасно, но также дает плохой пользовательский опыт. Поэтому, в практических ситуациях, большинство операций требуют второго метода верификации только для крупных действий. Другие API, которые извлекают данные, такие как записи транзакций или пользовательские данные, не требуют дополнительной защиты.
Как упоминалось ранее, самый распространенный способ - украсть токен после атаки. Здесь термин "токен" не относится к какой-либо конкретной технологии. Это может быть идентификатор сессии, токен JWT или токен OAuth. Просто считайте его чем-то, что может проверить идентификацию.
Если токен украден, пользователь может использовать его для отправки запросов на бэкенд API, не ограничиваясь браузером.
Некоторые могут подумать: "Имеет ли значение, если токен украден?" Например, предположим, что токен хранится в виде куки с параметром HttpOnly, который гарантирует, что JavaScript не может получить доступ к куки. Однако, когда злоумышленник использует fetch('/api/me'), они все же могут получить личные данные, потому что куки автоматически включены в запрос.
Это правильно, но хотя это может показаться неразличимым, злоумышленники все еще могут сделать много вещей. Однако есть некоторые различия.
Первое различие - будут ли они "ограничены сайтом".
Если у них есть токен, они могут вернуть его и отправлять запросы на бэкенд из любого места. Но если у них его нет, они могут выполнять вредоносный код только в точке внедрения XSS. На этом этапе могут быть некоторые ограничения, например ограничения на длину полезной нагрузки или политика единого источника.
Например, допустим, есть два веб-сайта, a.example.com и b.example.com, оба используют куки, хранящиеся в example.com для аутентификации.
Предположим, что злоумышленник успешно нашел уязвимость XSS на a.example.com, но пользовательские данные находятся на b.example.com. В этом случае они не могут получить доступ к данным пользователя на домене a, потому что политика единого источника блокирует запрос fetch. Однако, если обе службы используют тот же токен и хранят его в localStorage, злоумышленник может использовать этот токен для доступа к домену b и успешно извлечь данные пользователя.
Второе различие - есть ли "временное ограничение". Если у них есть токен, они могут отправлять запросы от имени пользователя, пока токен не истечет.
Но если они могут использовать только XSS, это означает, что они могут выполнять атаки только тогда, когда пользователь открыл веб-страницу. Как только пользователь закрывает веб-страницу или браузер, они больше не могут выполнить код JavaScript.
Поэтому, если возможно, лучше не позволять злоумышленнику получить токен, так как это ограничивает атаки, которые злоумышленник может провести.
По состоянию на сегодняшний день на фронтенде единственный способ обеспечить недоступность токена для JavaScript - это использовать куки с параметром HttpOnly (исключая уязвимости браузера и API, которые напрямую возвращают токены). Других вариантов нет.
Однако, если ваша задача - "разрешить доступ к токену только определенному JavaScript", есть другое решение. Но обратите внимание, что это решение не хранит токен постоянно. Как только пользователь обновит страницу, токен будет потерян.
Это решение простое: храните токен в переменной JavaScript и оберните его в замыкание, чтобы гарантировать, что к нему нельзя получить доступ извне, вот так:
Таким образом, даже если злоумышленник обнаруживает уязвимость XSS, он не может "directly" получить доступ к переменной token из-за области его видимости. Я подчеркнул слово "directly", потому что как только у злоумышленника есть возможность провести XSS-атаку, он может сделать много опасных вещей, например вот так:
Заменяя реализацию window.fetch, они могут перехватывать параметры, переданные функции, и косвенно получить доступ к token.
Поэтому, более безопасный метод - предотвратить вмешательство с помощью XSS в среду выполнения, которая имеет токен, достигая изоляции контекста. В веб-фронтенде это можно сделать с помощью Web Workers. Используя Web Workers, можно создать новую среду выполнения для ее изоляции, как показано на следующей схеме:
Приблизительный код выглядит так (это просто концептуальный пример):
И в коде приложения инициализируйте рабочего и вызовите API:
На самом деле, идея заключается в том, чтобы поместить все запросы API в рабочий поток. Благодаря изоляции среды выполнения, если в рабочем потоке нет XSS, основной поток не может вмешаться в рабочего и не может получить доступ к его данным. Это обеспечивает безопасность токена.
Как отмечалось ранее, даже если токен не украден, злоумышленники могут все еще вызвать API и получить ответ через XSS, что верно, когда вы используете cookies для хранения токена.
Однако, если вы используете упомянутый выше метод, используя Web Workers и переменные для хранения токена, ситуация меняется. Использование этого метода означает, что для злоумышленников бесполезно самим вызывать API с помощью fetch(), потому что в запросе не будет прикреплен токен, поэтому аутентификация на сервере не пройдет.
Как в приведенном выше примере, все запросы API должны проходить через Web Workers, что похоже на создание прокси на уровне фронтенда в рабочем процессе. Поэтому, даже если XSS может получить доступ к apiWorker, он может вызывать только API, реализованные apiWorker, и не может вызывать другие.
Например, предположим, что серверный API реализовал функцию /uploadFile, но эта функция предназначена только для внутреннего использования, поэтому она не реализуется в рабочем процессе. В этом случае, злоумышленники не могут использовать эту функцию независимо от условий, что добавляет еще один слой защиты.
При разработке стратегии обороны против XSS важно свести к минимуму ущерб в случае появления XSS. Поэтому последний мерой является предположение, что токен будет использован, и размышление о том, что еще можно сделать, чтобы уменьшить ущерб.
Самый очевидный подход - ограничить области действия токена, чтобы он не мог выполнить слишком много действий. Конечно, контроль доступа на бэкенде является обязательным, но на фронтенде тоже можно сделать больше.
Например, предположим, что есть система бронирования ресторана, и бэкенд API представляет собой полноценный сервис, будь то для бронирования или для внутреннего использования. Все они используют один и тот же сервер API, например, /users/me для получения своих данных и /internal/users для получения данных всех пользователей (с проверкой разрешений).
Предположим, что XSS появилась на веб-сайте бронирования ресторана, и целью атаки стал авторизованный внутренний сотрудник. В этом случае, злоумышленник может вызвать /internal/users для получения данных всех пользователей. Идеальным решением было бы разделение внутренней системы и системы бронирования ресторана на уровне бэкенд API, но это может потребовать слишком много времени и затрат.
В этом случае может быть использовано другое решение, называемое Backend For Frontend (BFF). BFF — это бэкенд-сервер, специально предназначенный для фронтенда, и все запросы от фронтенда проходят через BFF, как показано на диаграмме:
Таким образом, токен, полученный фронтендом, является только токеном, используемым для связи с BFF, а не токеном бэкенд-сервера за BFF. Таким образом, на стороне BFF можно ограничить права доступа, прямо блокируя все запросы к /internal, и ограничивая права токена, полученного фронтендом, обеспечивая, что внутренние API не могут быть вызваны.
"Предотвращение XSS" - это то, что обязательно нужно сделать, но это только первый способ защиты. Если сделано только это, то защита либо 0, либо 1. Либо все хорошо защищено, либо если в одном аспекте защита плохо настроена, то как будто нет никакой обороны, и система легко поддаётся.
Вот почему нам нужно несколько способов защиты для обеспечения более глубокой безопасности. Даже если в одном аспекте забыли отфильтровать пользовательский ввод, есть CSP, блокирующий выполнение JavaScript. Даже если CSP обойти, по крайней мере, функция перевода выполнена не будет, так как требуется мобильный проверочный код.
Больше уровней защиты означает более высокую безопасность, но также и более высокую стоимость и сложность системы. Важно понимать средства защиты, но это не означает, что каждый продукт нуждается во всех этих мерах. Для большинства веб-сайтов первых двух уровней обороны может быть достаточно.
Изначально у меня не было глубокого понимания этой темы. Просто случайно кто-то поднял эту тему в Facebook-группе "Front-End Developers Taiwan", что позволило мне лучше понять её, и заставило меня включить её в эту серию статей.
Ссылки:
Однако это решение, очевидно, увеличивает затраты на разработку, так как необходимо настроить множество вещей. Если вас интересуют более подробные детали, а также плюсы и минусы этого решения, вы можете обратиться к техническому блогу Mercari, японской площадки для торговли б/у товарами:
Что касается хранения токенов, если вам нужен доступ к токену с JavaScript и не нужно его сохранение, вероятно, это лучший вариант. Auth0, компания, специализирующаяся на проверке подлинности, также написала статью, посвященную хранению токенов, на которую вы можете ознакомиться:
1. 2. 3. 4.