Как уже было сказано, XSS атаки являются самыми опасными и самыми распатроненными. Потенциальная дыра в безопасности веб приложения может появиться не сразу, а с внедрением какой-то новой фичи и отследить, момент может не каждый разработчик. Поэтому для снижения рисков в случае потенциального появления XSS стоит обезопасить авторотационные данные пользователя.
Для обеспечения безопасности авторотационных данных пользователя важно, чтобы эти данные не были доступны через глобальный JS-код, ибо именно в нем происходит XSS-атака. Для этого можно использовать следующие техники:
- Сохранять авторизационные cookie с атрибутом httpOnly. Данный атрибут сделает невозможным доступ к этой cookie из JavaScript, тем самым, злоумышленник не сможет прочитать ее из document.cookie.
- Использовать JWT токены и хранить их в рантайме приложения, а именно в каком-то замыкание, например, в сторе, вроде Redux или MobX. Частой ошибкой является то, что разработчики сохраняют JWT токен в Local Storage (это браузерное хранилище доступное для JS-кода и уникальное для каждого источника, позволяющее сохранять значение между сессиями пользователя). Так как доступ к этому хранилищу можно получить из любого места кода, то и злоумышленник может читать из него, так что, сохраняя токен там мы создаем потенциальную возможность для кражи токена в случае XSS-атаки.
Это все что касается именно непосредственной кражи авторотационных данных жертвы. Также возможен вариант, когда злоумышленнику не нужно красть авторизационные данные, свою диверсию он может провести и без их кражи. Например, если жертвой XSS-атаки стал онлайн банк и его клиент, то злоумышленник может сразу сделать запрос на перевод денег со счета жертвы на свой счет. В таком случае будут автоматически отправлены cookie. Для решения данной проблемы стоит использовать JWT-токен, так как он будет храниться в замыкание и отправляться на каждый запрос к серверу вручную, то злоумышленник не сможет его прочитать и соответственно подтвердить свой запрос на перевод. Безусловно, менять всю систему с сессий (cookie) на токены (JWT) только ради этого не благоразумно. Для защиты систем с cookie можно запрашивать на каждый перевод дополнительное подтверждение от клиента, например в виде кода из SMS / Push-уведомления из мобильного приложения / кода из email. Проблемами такого подхода можно считать, что, во-первых, придется добавить дополнительную бизнес-логику, что увеличит количество кода и сложность системы, а во-вторых, скажется на пользовательском опыте, ведь пользователю, который не находится под XSS-атакой, придется делать дополнительные шаги, чтобы выполнить действие. Возможен вариант, когда на каждый такой запрос будет генерироваться отдельный токен и отправляться вместе с cookie, который не будет доступен в глобальном JS-коде. Такой вариант нужно прорабатывать для каждой системы в индивидуальном порядке.
Были рассмотрены способы защиты авторизационных данных и отправки автоматических запросов без ведома жертвы в случае потенциальной XSS-атаки. Но есть еще один вариант, который не попадает под вышеперечисленные способы. Это когда злоумышленник маскирует свои данные под те, данные, что ожидает увидеть жертва. Например, жертва хочет отправить денежные средства своим родителям или друзьям, он проходит этап выбора получателя, вбивает нужную сумму, но с самый последний момент код злоумышленника встраивает вместо ожидаемого жертвой получателя, свой подставной счет. Тем самым пользователь думает, что отправляет деньги близкому человека, а на самом деле деньги уйдут мошеннику. Особенностью, является то, что пользователь самостоятельно проходит процесс по переводу денежных средств, он даже может подтвердить его с помощью дополнительной авторизации, например кодом из SMS. В таком случае все вышеперечисленные способы защиты не сработают и тут уже нужно бороться с самой возможностью появления XSS.
Для предотвращения появления XSS-атаки используются следующие методы:
- Экранирование пользовательского ввода. Экранирование может происходить как на сервере, когда тот получает данные от клиента, так и на клиенте, когда приходит код от сервера. Производить экранирование на сервере кажется более логичным, но это вариант подходит не всегда. Все дело в том, что браузерные движки преобразуют HTML-код в DOM дерево весьма сложными алгоритмами, которые не просто повторить на сервере, зачастую браузеры могут достраивать недостающий HTML-код, то есть могут работать инкриментально. И если веб-приложение в первую очередь строится на пользовательском вводе, например блог с расширенным текстовым редактором, то ограниченность серверных библиотек по экранированию может привести не к тому результату, который ожидал пользователь во время своего ввода, а то и вовсе серверное экранирование может не сработать. Например, в первой главе посвященной данной атаки, были приведены примеры с таким кодом: <script>alert(1)</script>. Может показаться, что достаточно просто удалить тег script, либо же заменить «<» на HTML сущность «<», а «>» на «>». Но для атаки может использоваться и другой код [3]: <img src="x" /> — здесь представлен HTML-тег для отображения изображения, который пытается загрузить изображение по пути x (или любому другому пути, которого не существует), ожидаемого срабатывает событие загрузки error, которое тут же ловится атрибутом onerror и выводит сообщение «XSS», путем преобразования значений единиц кода UTF-16 в строку. Так сходу может быть даже и не понятно, что это код выведет, пока не запустить его в песочнице. А еще может быть вариант, когда в HTML-тег a, отвечающий за ссылку на другую страницу, в атрибут src, вставят «javascript:alert(1)», в таком случае не случится никакого перенаправления, а выполнится скрипт по выводу 1 на экран пользователя. Также помимо схемы «javascript:», может быть вставлена схема «data:text/html, <script>alert(1)</script> », которая генерирует страничку только с JS кодом. Не все серверные библиотеки по экранированию справятся с этим.
Именно поэтому некоторые сервисы используют экранирование только на клиенте, когда получают ответ от сервера. В таком случае в БД сервера может храниться код с потенциальной XSS-атакой, но так как на клиенте присутствует экранирование, то эта атака удаляется из код перед тем, как встроить его в документ. Примером такой библиотеки может служить DomPurify. Она подключается на клиенте (и за счет этого использует сам движок браузера для парсинга кода) и работает следующим способом:Полученный код с сервера добавляет в HTML-тег template. Особенностью данного тега является то, что он не отображает свое содержимое на странице, он может валидировать разметку, так же как, если бы она была вставлена в обычный HTML-документ и внутри него не работает JS-код. То есть приведенный выше пример с img не сработает, так как onerror не отработает ввиду отсутствия JS.
- Далее с помощью JS идет проход по всем тегам встроенным в template и из них вырезаются те атрибуты, которые не находятся в списке безопасных. То есть атрибут onerror будет удален.
- Далее этот код может пройти дополнительную обработку с помощью метода XMLSerializer.serializeToString().
- И наконец этот обезвреженный код может быть встроен в основное DOM-дерево.
- Отказ от небезопасного JS API. К такому API можно отнести eval или innerHTML. Зачастую именно использование innerHTML приводит к XSS-атакам, этот метод можно заменить на textContent, если нужно встроить только текст, а не HTML-представление. Но все-таки бывают случаи, когда textContent не подойдет, например, если используется текстовый редактор, который предполагает, что вывод будет именно в HTML с сохранением тегов разметки, вроде <b>, <strong>, <em> и прочих. Современные веб-фреймворки вроде Svelte, Angular, Vue или React по умолчанию производят экранирование вставляемого текста, а если нужно вставить именно HTML для этого есть специальные методы, вроде dangerouslySetInnerHTML в React, из самого названия сразу становится понятно, что никакого экранирования там нет и этот метод небезопасен, так что разработчику необходимо понимать кто может влиять на текст вставляемы данным методом и самостоятельно экранировать текст в нем.
- Использовать серверный заголовок Content-Security-Policy, который может запретить исполнение скриптов из ненадёжных источников, в том числе содержимого тега script встраиваемого в HTML или повешанных обработчиков вроде onerror или onload, разрешая загрузку скриптов только из разрешенных доменов. Значение заголовка — это список доверенных источников, из которых пользователь может получать контент. Например, такой вариант не сработает: <script>alert(1)</script> — так как он был встроен в HTML, а не загружен с разрешенного источника, а такой вариант сработает: , так как он загружается с нашего домена.
- Использование Trusted Types. Этот механизм предоставляет инструменты для создания, модификации и поддержки приложений, полностью защищенных от DOM XSS. Этот режим может быть включен через CSP. Он делает JavaScript-код безопасным по умолчанию посредством ограничения значений, принимаемых небезопасными API, например innerHTML, таким образом, нельзя злоумышленник ничего не сможет встроить в страницу, даже если это безобидная картинка или обычный заголовок. К сожалению, данный механизм пока работает только в Chromium браузерах (c середины 2020 года) и не может полноценно использоваться в финальных реализациях, так как пользователь может использовать Safari или Firefox, но как дополнительная защита для Chromium-пользователей данный вариант подойдет.
Таким образом в статьях были рассмотрены основные виды атака на веб приложения, способы их реализации и основные способы обеспеченья безопасности и способы противодействия этим атакам. Каждой атаке была дана классификация ее актуальности и степени ее опасности воздействия на веб приложение. А для каждого способа противодействия были приведены актуальные варианты решения проблемы.