Эта статья является продолжением данной статьи.

Способы противодействия

В первой статье мы рассмотрели виды атак и сценарии их реализации, в данной статье мы рассмотрим способы противодействия им.

Противодействие Clickjacking-атакам

В текущее время противостоять данному виду атак весьма просто, выделим две основные техники:

Первый вариант — использовать HTTP-заголовок X-Frame-Options. Этот серверный заголовок может разрешать или запрещать отображение страницы внутри фрейма. У него есть три значения:

  • DENY — никогда не показывать страницу внутри фрейма.
  • SAMEORIGIN — разрешить показ страницы внутри фрейма, но только если родительский документ имеет тот же источник.
  • ALLOW-FROM domain — разрешить показ страницы внутри фрейма, только если родительский документ находится на указанном домене.

Как правило заголовок ставят в SAMEORIGIN, чтобы можно было встраивать страницу только в пределах одного источника. Но стоит учитывать, что сама по себе механика iframe очень противоречива и ее стоит использовать только в самых крайних случаях. Так как браузер будет грузить страницу из iframe как самостоятельную страницу, соответственно и все скрипты, стили он будет грузить по новой. Из-за этого может получиться, что этот iframe может весить в 3–4 раза больше, чем основная страница, на которой этот iframe подключают.

Второй вариант — настройка атрибута samesite у cookie. Данный атрибут определяет в каких случаях браузер будет посылать cookie связанные с сайтом. Есть три значения этого атрибута:

  • Strict – никогда не отправлять cookie, если пользователь не пришел не с этого же сайта. Используя данный атрибут, мы сразу можем пресечь отправку наших cookie при запросах с других сайтов, но он несет в себе и некоторые ухудшения пользовательского опыта. Допустим, пользователь открывает сайт, на котором был уже авторизован, из закладок браузера или из заметок, в таком случае браузер при переходе на сайт не добавит к запросу cookie, получится так, что, когда пользователь попадет на сайт он будет не авторизован. Для получения сессии нужно будет перезагрузить страницу, чтобы удовлетворять политике strict – cookie в таком случае отправятся, так как запрос сделан с сайта, к которому привязаны эти cookie.
  • Lax – также запрещает браузеру отправлять cookie, когда запрос происходит не сайта, к которому были привязаны cookie, но добавляет некоторые исключения [1]:
    1. Используются безопасные HTTP-методы (например, GET, но не POST). По сути, безопасными считаются методы, которые обычно используются для чтения, но не для записи данных. Они не должны выполнять никаких операций на изменение данных. Переход по ссылке является всегда GET-методом, то есть безопасным.
    2. Операция осуществляет навигацию верхнего уровня (изменяет URL в адресной строке браузера). Обычно это так, но если навигация выполняется в <iframe>, то это не верхний уровень. Кроме того, JavaScript-методы для сетевых запросов не выполняют никакой навигации, поэтому они не подходят.

Таким образом, режим lax обеспечивает самый популярный вариант «переход по ссылке», подхватывая в таком случае cookie. Но вот сетевой запрос с другого сайта, iframe или отправка формы с другого сайта не приведут к отправке cookie. Последний сценарий позволяет покрыть также CSRF-атаки.

Противодействие межсетевой подделке запросов

Самым эффективным способом противодействия данному виду атак в текущее время является установка cookie c атрибутом samesite=lax. Поведение атрибута с этим значением описано в главе выше. С ним при запросах на другой сайт не будут подхватываться cookie. Разве что, стоит отметить, что данный атрибут появился относительно недавно — впервые появился в Chromium в 2016 и во всех основных браузерах в 2018 году. Так что можно рассмотреть варианты, как разработчики боролись с этой атакой до появления samesite.

Самым популярным способом раньше являлся CSRF token. Работал он по следующему принципу:

  • Сервер отдавал страницу вместе со сгенерированным случайным уникальным токеном. Этот токен мог идти в cookie или сразу мог быть вшит в шаблон HTML-страницы.
  • Далее посредством JS скриптов, если токен был передан в cookie, он вшивался в тело формы, например, как невидимый тег input c атрибутом value равным этому токену. Либо сервер сразу отдавал уже с вшитым токеном.
  • Когда клиент отправлял форму на сервер, сервер проверял, что токен полученный вместе с формой соответствует тому, что он отправил клиенту, соответственно, если клиент не направил такой токен, то и запрос не принимался.

Благодаря такому поведению можно было предотвратить CSRF-атаку, за счет того, что злоумышленник не может узнать какой токен нужен серверу, так как форма у злоумышленника статична и не имеет возможности запросить у сервера персональную форму конкретно для этой жертвы, так как не располагает информацией о нем. Стоит также отметить, что токен не обязательно должен встраиваться в форму в виде невидимого input-тега. Токен может быть отправлен с клиента, как HTTP заголовок, например, x-secret-csrf-token, а уже сервер проверит его на соответствие.

Также возможен вариант, когда все формы будут отправляться со значение заголовка Content-type: application/json, то есть отправка такого запроса будет считаться CORS как непростой запрос, соответственно он сначала сделает preflight-запрос на проверку разрешающих заголовков. Стоит отметить, что с популяризацией SPA практически все формы отправляются как JSON, то есть как непростые формы. Это делается на клиенте за счет JS, который предотвращает дефолтное поведение формы с переопределением ее логики поведения, но уже за последний благодаря механизмам SSR снова начала популяризироваться возможность отправки нативной формы без использования JSON. Это делается в тех случаях, когда у клиента по каким-то причинам выключен JavaScript. Или вариант, когда SSR-фреймворк передал на клиент статичный HTML, а JS бандл еще не успел полностью загрузиться и гидрироваться на клиенте, в таком случае, чтобы сохранить функциональность страницы возможна отправка нативных форм. Потом, когда JS-бандл загрузится и прогидрируется на страницу, формы уже будут работать с отправкой JSON, заменяя стандартное поведение. Так что, в таком варианте формы все же могут быть уязвимы. Про остальные варианты противодействия CSRF-атакам можно ознакомиться в источнике 2.

Лучшим решением в противодействие CSRF является комбинированием нескольких способов защиты, ввиду того что сайтом все еще могут пользоваться клиенты, чьи браузеры не поддерживают cookie с samesite=lax.

Противодействие межсайтовому скриптингку

Как уже было сказано, 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-атаки используются следующие методы:
  1. Экранирование пользовательского ввода. Экранирование может происходить как на сервере, когда тот получает данные от клиента, так и на клиенте, когда приходит код от сервера. Производить экранирование на сервере кажется более логичным, но это вариант подходит не всегда. Все дело в том, что браузерные движки преобразуют 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-дерево.
  2. Отказ от небезопасного JS API. К такому API можно отнести eval или innerHTML. Зачастую именно использование innerHTML приводит к XSS-атакам, этот метод можно заменить на textContent, если нужно встроить только текст, а не HTML-представление. Но все-таки бывают случаи, когда textContent не подойдет, например, если используется текстовый редактор, который предполагает, что вывод будет именно в HTML с сохранением тегов разметки, вроде <b>, <strong>, <em> и прочих. Современные веб-фреймворки вроде Svelte, Angular, Vue или React по умолчанию производят экранирование вставляемого текста, а если нужно вставить именно HTML для этого есть специальные методы, вроде dangerouslySetInnerHTML в React, из самого названия сразу становится понятно, что никакого экранирования там нет и этот метод небезопасен, так что разработчику необходимо понимать кто может влиять на текст вставляемы данным методом и самостоятельно экранировать текст в нем.
  3. Использовать серверный заголовок Content-Security-Policy, который может запретить исполнение скриптов из ненадёжных источников, в том числе содержимого тега script встраиваемого в HTML или повешанных обработчиков вроде onerror или onload, разрешая загрузку скриптов только из разрешенных доменов. Значение заголовка — это список доверенных источников, из которых пользователь может получать контент. Например, такой вариант не сработает: <script>alert(1)</script> — так как он был встроен в HTML, а не загружен с разрешенного источника, а такой вариант сработает: , так как он загружается с нашего домена.
  4. Использование Trusted Types. Этот механизм предоставляет инструменты для создания, модификации и поддержки приложений, полностью защищенных от DOM XSS. Этот режим может быть включен через CSP. Он делает JavaScript-код безопасным по умолчанию посредством ограничения значений, принимаемых небезопасными API, например innerHTML, таким образом, нельзя злоумышленник ничего не сможет встроить в страницу, даже если это безобидная картинка или обычный заголовок. К сожалению, данный механизм пока работает только в Chromium браузерах (c середины 2020 года) и не может полноценно использоваться в финальных реализациях, так как пользователь может использовать Safari или Firefox, но как дополнительная защита для Chromium-пользователей данный вариант подойдет.
Таким образом в статьях были рассмотрены основные виды атака на веб приложения, способы их реализации и основные способы обеспеченья безопасности и способы противодействия этим атакам. Каждой атаке была дана классификация ее актуальности и степени ее опасности воздействия на веб приложение. А для каждого способа противодействия были приведены актуальные варианты решения проблемы.

Используемые источники

В данной статье использовались материалы из следующих источников:

  1. Куки, document.cookie — Современный учебник JavaScript
  2. CSRF-уязвимости все еще актуальны — Хабр
  3. XSS Filter Evasion Cheat Sheet — OWASP Cheat Sheet Series 

Насколько интересной была статья?

Кликните на звезду, чтобы оценить статью

Средний рейтинг: 0 / 5. Оценок: 0

Оценок еще нет. Оцените, чтобы быть первым

Эксперт Павел Лебедев

Проснувшись однажды утром после беспокойного сна, Грегор Замза обнаружил, что он у себя в постели превратился в страшное насекомое.

Проснувшись однажды утром после беспокойного сна, Грегор Замза обнаружил, что он у себя в постели превратился в страшное насекомое.

Проснувшись однажды утром после беспокойного сна, Грегор Замза обнаружил, что он у себя в постели превратился в страшное насекомое.