За последние 20 лет стек веб-технологий претерпел серьезные изменения. Если изначально сайты использовались как обычные документы для предоставления статичной информации, то со временем стало понятно, что они способны на большее. Появились, так называемые Multi Page Application (далее MPA),  генерация страниц на стороне серверного фреймворка с использованием шаблонизатора (классический подход с PHP (Laravel, а также WordPress использует этот подход), Python, Ruby). Вся логика тогда хранилась на сервере (backend), так же, как и правила безопасности страницы, frontend представлял из себя всего лишь красивое лицо и особо ничего больше. Например, на сайте есть таблица и фильтры для нее, при клике по фильтру запрос отправляется на сервер, сервер фильтрует данные из таблицы и возвращает новую страницу с отфильтрованными значениями. Минусы такого подхода: во-первых, нужно ждать загрузку новой страницу, чтобы увидеть результаты фильтрации, во-вторых, сама страница перезагружается, что ухудшает пользовательский опыт, и очень отличается от того, как мы привыкли использовать десктопные приложения.  

Со временем  появился подход Asynchronous JavaScript and XML (далее AJAX). Это подход предполагал, что данные смогут обновляться и загружаться непосредственно с фронтенда. Если пример выше в AJAX подходе, то теперь данные будут фильтроваться на клиенте за счет JavaScript, и запроса к серверу на перерендер страницы уже не будет. Продолжением развития такого подхода стало появление Single Page Application (далее SPA). SPA во всю использует подход AJAX, сервер всего лишь один раз отдает HTML пользователю, при первом его запросе к сайту, дальше все переходы по ссылкам внутри сайта берет на себя JavaScript, динамически обновляя содержимое страницы и URL в адресной строке браузера. Сайты использующие SPA уже начали называть веб-приложениями. Примерами веб-фреймворков помогающим в создании SPA могут служить, React-based (React+Redux+React Router), Angular, Vue и Svelte. В случае с SPA на клиент приходит еще больше логики, а вместе с тем и возможных дыр безопасности.

У SPA была одна важная проблем по сравнению с MPA, он не мог обеспечить SEO, в частности актуальные для страницы мета-теги для поисковый роботов, тех, что используют Google, Yandex, Yahoo и другие. Из-за того, что вся страница строится динамически на клиенте, а поисковые роботы в случае с SPA всего лишь загружают ту самую пустую страницу и не инициализируют ее динамическую генерацию. Для решения такой проблемы появились подходы, когда SPA приложения генерируются на сервере, либо на каждый запрос клиента (SSR) или заранее (SSG). Это привело к том, что полномочия Frontend-разработчиков по созданию веб-приложения стали еще шире. SSR-фреймворк Remix вообще предполагает, что каждый запрос с клиента к основному Backend серверу, будет проходить через Backend Remix. Это накладывает на разработчиков знание и понимание инфраструктуры, а также особенности обеспеченья безопасности.

Именно об основах обеспеченья безопасности пойдет речь в данной статье. Будут рассмотрены виды атак на современные веб-приложения и способы защиты от них.  

Виды атак

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

Click Jacking

Данный вид атаки сейчас является не самым популярным, так как от него весьма просто защититься, но он был весьма распатронен раньше и все же изредка может встретиться и сейчас. Атака реализуется по следующему алгоритму [1]:

  1. Создается вредоносная страница (evil.com), с каким-либо интерактивным элементом, допустим c кнопкой «Забрать приз».
  2. Злоумышленник добавляет на этот сайт HTML-тег iframe, данный тег позволяет загрузить внутрь него другую страницу, даже если она находится на другом домене. В iframe кладется целевая страница, допустим bank.com, в которой нужно сделать клик.
  3. iframe делается прозрачным с помощью CSS-свойства opacity и выносится на верх страницы с помощью z-index, при это он остается кликабельным.
  4. Как правило злоумышленники находят какой-то пул краденных данных пользователей от разных сервисов: банков, торговых-площадок, онлайн-сервисов и т. д. Там берут их логин и пароль, но как правило вход в эти сервисы защищен 2FA (двухфакторной аутентификацией) или чем-то подобным, то злоумышленник вставляет подкладывает именно страницу с настройками целевого сайта, где 2FA отключается.
  5. Пользователь, чьи данные были найдены в краденном пуле данных заманивается на evil.com под разными предлогами и ожидается его клик по кнопке.
  6. По итогу пользователь кликает по кнопке «Забрать приз» на сайте evil.com но на самом деле, он кликает по кнопке «Отключить 2FA» на сайте bank.com, который был встроен с помощью iframe.
  7. Далее злоумышленник успешно заходит по взятыми из пула данных пользовательскими данными, так как 2FA был отключен оригинальным владельцем аккаунта.

Главным виновником этой уязвимости служит тот факт, что cookie отправляются при любом запросе к сайту bank.com. Даже несмотря на то, что сайт открыт не на верхнем уровне, а встроен в другую страницу [2]. В текущее время есть множество механизмов защиты от этого и многие из них включены по умолчанию. Но их мы рассмотрим в следующей главе.

Межсетевая подделка запросов

На английском данный вид атаки называется Cross-Site Request Forgery (далее CSRF). Эта атака, при которой злоумышленник может совершать действия как бы от лица пользователя, при этом без его ведома. Эта атака уже более распатронена в современной сети интернет [3].

В основе этой атаки лежит идея того, что cookie, определенные сайтом N могут отправляться при запросе к сайту N, иногда даже в тех случаях, если они были сделаны не с сайта N, то есть во время межсайтового взаимодействия. Сейчас за такое взаимодействие между сайтами отвечает политика Cross-origin resource sharing (далее CORS). Она отвечает только за запросы к внешним ресурсам и никак ни влияет на запросы внутри одного домена. CORS делит запросы к внешним ресурсам на две части: простые и непростые.

Простые запросы – это те запросы, которые удовлетворяют следующим критериям:

  1. Используется простой метод: GET, POST или HEAD
  2. Используются только простые заголовки – а именно разрешены только:
  • Accept,
  • Accept-Language,
  • Content-Language,
  • Content-Type со значением application/x-www-form-urlencoded, multipart/form-data или text/plain.

Любой другой заголовок будет определять запрос как непростой. Если запрос простой, то он сразу будет отправлен на сторонний сервер, а дальше этот сервер уже может решить, что с ним делать, основываясь на Origin (неизменяемый заголовок, содержащий домен сайта с которого был сделан запрос) из простого запроса. Например, если мы сделаем простой GET-запрос с одного домена к другому, а сервер другого домена никак не настраивал политику CORS, допустим это сервер из 2005 года, то, когда нам вернется ответ, в котором не будет разрешающих заголовков на такое взаимодействие, браузер по умолчанию заблокирует этот ответ сервера. Важно понимать, что именно браузер печется о безопасности пользователя, таким образом даже старые сайты могут быть отчасти защищены. Если сторонний сервер, хочет, чтобы к нему могли обращаться с других доменов, то ему нужно это явно указать, с помощью заголовка Access-Control-Allow-Origin, и поместить туда конкретное значение сайта, с которого можно обращаться, либо поставить «*», чтобы быть доступным для всех сайтов. Пример простого запроса на рисунке 1.

Диаграмма описывает взаимодействие во время простого запроса
Рисунок 1. Простой запрос [4]

Также стоит рассмотреть непростые запросы. Как уже было сказано выше, любой запрос, который не удовлетворяет критериям просто запроса, считается непростым. Это, например использование методов: DELETE, PUT, PUTCH, или использование POST метода вместе с Content-Type: application/json, отправка JSON как правило самая распатроненная операция во взаимодействие с сервером. Если запрос непростой, то он сразу не будет отправлен на сервер, вместо этого браузер сначала направит предварительный запрос («предзапрос», по англ. «preflight») с методом OPTONS, который спрашивает у сервера – согласен ли он принять такой непростой запрос или нет. И, если сервер явно не даёт согласие в заголовках, непростой запрос не посылается браузером. Пример непростого запроса на рисунке 2.

Диаграмма описывает взаимодействие во время непростого запроса
Рисунок 2. Непростой запрос [4]

Может показаться, что это дает полную защиту, но есть случаи, когда это не так. Если в случае с непростым запросом, сервер действительно не получает этот запрос, так как браузер его отклоняет еще на этапе получения неудовлетворительного preflight. В случае с простыми запросами сервер все же может обработать запрос. Если сервер получил простой запрос с методами GET или HEAD он может их обработать (если, конечно, сам не проверил Origin).  Некоторые сервера могут быть построены без знания о существование CORS, в таком случае они их обрабатывают и вернуть в ответе результат, а уже на клиенте, браузер блокирует получение результата. А вот если мы направим POST запрос с Content-Type значениями application/x-www-form-urlencoded или multipart/form-data, браузер определит это как простой запрос и сразу его отправит на сервер, сервер его обработает и вернет результат, да, браузер заблокирует вернувшийся ответ от сервера, так как там не было разрешающего заголовка, но все же сервер обработает данный вид запроса, а он может нести в себе любую угрозу: это может быть запрос на изменения пароля с новым значением или перевод денег на другой счет, если сервер является банковским. Таким образом сайты, которые ничего не знают о CORS, все же остаются уязвимы, причем в самом опасном варианте взаимодействия – передаче значения им.

Из вышеперечисленного поведения становится понятным, как злоумышленники пользуются данным аспектом в своих CSRF-атаках:

  1. Заманивают пользователя, который был аутентифицирован на сайте bank.com (важно, чтобы этот сайт поддерживал простые POST запросы) на свой вредоносный сайт evil.com;
  2. На сайте может использоваться форма (дублирующая форму на перевод средств сайта bank.com) с автоматической отправкой (эта форма будет считаться простой, и она автоматически подхватит cookie bank.com) или XMLHttpRequest с методом POST и значением Content-type: application/x-www-form-urlencoded (такой запрос будет простым), а также со значением true в withCredential (это скажет браузеру отправить cookie сайта bank.com в запросе);
  3. Сервер bank.com получит этот запрос и если он не делает проверок на Origin или дополнительного подтверждения перевода посредством кода из SMS/Push-уведомления, то он обработает данный запрос и сделает перевод;

Клиент не получит результат запроса, так как браузер не получит разрешающих заголовков на CORS, но запрос все равно может быть обработан сервером, так что, то, что получит клиент (в том числе и код злоумышленника) уже не важно, так как скорее всего перевод был проведен.

Межсайтовый скриптинг

На английском данный вид атаки называется Cross Site Scripting (далее XSS), первая буква в сокращении была заменена на X, так как у разработчиков могла быть путаница с популярным сокращение CSS – таблиц стилей. XSS — это внедрение вредоносного кода на страницы атакуемого сервиса. Данная атака является самой опасной из вышеперечисленных, так как позволяет злоумышленники получить контроль над страницей жертвы. Также, как бы это не было парадоксально, эта атака является самой часто встречаемой в вебе, так как некоторые разработчики могут по незнанию оставить для нее брешь в весьма распатроненных аспектах сайта. У этой атаки есть два вида: Stored XSS и Reflect XSS.

Рассмотрим Stored XSS. Эта самый опасный подвид XSS, так как в таком случае код для XSS-атаки хранится на сервере и запускается у всех клиентов. Пример реализации атаки будет следующий:

  1. Злоумышленник находит сайт, который разрешает пользовательский ввод в комментариях, отзывах или других текстовых полях, которые потом будут встроены в код сайта, и сайт не экранирует этот ввод.
  2. Вместо обычного текста в поле ввода вводится, что-то вроде <script>alert(1)</script> (данная команда выведет 1 в браузере пользователя). HTML-тег script позволяет запускать JS-код внутри HTML-страницы.
  3. Сообщение отправляется на сервер, чтобы потом оно сохранилось в базе данных.
  4. Все пользователи, которые просто зайдут на страницу, где будет отображаться отправленное сообщения попадут под XSS-атаку. Для этого им даже не нужно ничего делать, они просто получат содержимое сообщения, которое, в случае небезопасного монтирования в DOM будет преобразовано в тег, позволяющий запускать JS-код.

 

Теперь опишем Reflected XSS, атака называется отраженной, потому что действие сразу же отображается на поведение страницы. Reflected XSS отличается от Stored XSS тем, что он выполняется один раз и под его воздействие попадет только конкретная жертва, а не все пользователи, что зайдут на сайт. Сценарий атаки, следующий:

  1. Допустим, злоумышленник зашел на страницу поисковика. Если написать какую-то что-то в поле ввода и нажать поиск, поисковик отобразит результаты и под полем ввода напишет: «По вашему результату: смешные коты найдено 100 тыс. результатов». То есть введенное в поле ввода будет встроено в DOM страницы. Также злоумышленник удостоверится, что при этом встраивание поисковик не производит никакого экранирования ввода.
  2. Злоумышленник вынуждает жертву (не важно каким способом) ввести в поле поиска <script>alert(1)</script> и нажать поиск.
  3. В страницу жертвы встраивается текст из поля ввода, содержащий XSS-атаку.

Возможен также вариант, что пользователя и не нужно вынуждать вводить что-то на страницы. Некоторые сайты могут автоматически производить поиск, если получили в URL-адресе, например, параметр search или q (сокращение от query), ссылка вида poiskovik.com?search=<script>alert(1)</script>. Поисковик может автоматически направить пользователя на страницу поиска по данному запросу, которая будет содержать вредоносный код, например, «По вашему результату: <script>alert(1)</script> найдено 100 тыс. результатов». Справедливо можно подумать, что пользователь заметит, что что-то не так с этой ссылкой и не будет по ней кликать. Отчасти это правда, но не все пользователи технически подкованы. В добавок злоумышленник может использовать сервис по сокращению ссылок и тогда ссылка на поиск из примера выше может выглядеть совсем безобидно: clck.ru/rbGQQ или вообще будет представлена в виде QR-кода.

В примерах выше мы выводили на экран пользователя всего лишь безобидную цифру 1. Но злоумышленник может встроить туда код загрузки своего вредоносного-скрипта, или код, который будет красть данные для авторизации, чтобы потом злоумышленник мог войти в систему под видом жертвы, а может будет и сразу слать запросы на перевод денег с счета жертвы себе, если сайт попавший под атаку является онлайн-банком.

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

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

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

  1. Атака типа clickjacking — Современный учебник JavaScript
  2. Безопасность веб-приложений и распространённые атаки — Doka 
  3. CSRF-уязвимости все еще актуальны — Хабр
  4. Fetch: запросы на другие сайты — Современный учебник JavaScript

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

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

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

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

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

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

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

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