Статья Тестирование безопасности SPA и фронтенд-фреймворков: где ломают React/Vue/Angular

1769005595393.webp

Где и как ломают SPA-приложения в 2026 году?​

SPA(Single Page Application) - это основа современной веб-разработки. Эти приложения, как ленивые кэшированные страницы, обещают плавный, безболезненный пользовательский опыт и мгновенный отклик. Но вот парадокс: за этой гладкой оболочкой часто скрываются глубокие уязвимости, которые могут в миг перевернуть всё с ног на голову.

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

И если ты считаешь, что React, Vue и Angular - это просто безболезненные фреймворки, которые помогут ускорить разработку, то ты сильно заблуждаешься. На этих самых фреймворках устроены такие слабые места, что жди, не дождешься, когда кто-то проснется и скажет: "А давай-ка проверим, а что тут может быть не так?".

В этой статье мы разберемся, как ломают SPA-приложения в 2026 году. Какие уязвимости в React, Vue и Angular обычно ловят, какие баги часто прокачиваются через клиентскую сторону, как работает анализ JavaScript-бандлов и как нам, пентестерам, отыскивать все эти дырки, используя стандартные инструменты и свои навыки. В общем, поехали разбираться, где мы можем порвать их по швам.

Модель угроз SPA​

Отличия от классических веб-приложений
Чем же, спрашивается, SPA отличается от старых добрых многостраничных веб-приложений (MPA)? Всё на самом деле не так сложно, как может показаться на первый взгляд. В классических веб-приложениях клиент, по сути, не является самостоятельным субъектом. Все данные и логику обработки запросов берет на себя сервер. Он же, в свою очередь, решает все проблемы с пользователем и его запросами, практически не оставляя простора для маневра на клиентской стороне.

А вот в Single Page Application ситуация другая. Это как раз тот случай, когда большая часть обработки и логики происходит на клиенте. При этом сервер предоставляет только необходимые данные, а фронтенд-фреймворк берет на себя весь процесс отображения и взаимодействия с пользователем. Все, что нужно - это красиво запаковать приложение в JavaScript, отправить его на клиент, и дальше это будет работать без дополнительных запросов.

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

1769005094834.webp


Client-side attack surface
Вот где реально начинается магия для пентестера. В SPA атакующие не просто могут атаковать сервер, который ты защищаешь всеми силами, а выходят на более уязвимую и открытое поле - клиентскую сторону. Ведь весь фронтенд-код - это твоя открытая книга. Ну, то есть, клиент может абсолютно спокойно смотреть все, что происходит в JavaScript, изменять DOM, а тем более найти способы поиграться с template-инъекциями и другими багами.

И вот здесь уже начинаются настоящие проблемы: те самые client-side уязвимости, с которыми будет непросто бороться без понимания основ. Это не только классы и шаблоны, это манипуляции с JavaScript-объектами, DOM-инъекциями и использование таких фишек, как dangerouslySetInnerHTML или v-html директивы. С этим ты сталкиваешься ежедневно, и это уже становится твоей привычной зоной тестирования.

State management vulnerabilities
Кстати, еще одна важная проблема - это управление состоянием в SPA. В веб-приложениях состояние обычно хранится на сервере, и если оно где-то ломается, то можно прицепиться к базе или сессии пользователя. В SPA, однако, весь state хранится на клиенте, и это открывает новые горизонты для атак.

Что это значит? Да всё просто: атакующий может использовать уязвимости в хранилищах состояния, таких как localStorage, sessionStorage или даже через манипуляции с Redux. Всё это позволяет ему запускать скрипты или подменять данные на клиенте, что, конечно же, является прямым путём к компрометации.

React-специфичные уязвимости​

dangerouslySetInnerHTML abuse
React - это удобный фреймворк, но, как и любой другой инструмент, он имеет свои подводные камни. Одним из самых частых моментов, когда React-приложения становятся уязвимыми, является использование dangerouslySetInnerHTML. Это, по сути, как пускать в приложение безлимитный доступ к HTML-контенту, и сразу же становиться открытым для XSS атак. Да, React предоставляет удобный способ вставлять HTML-содержимое напрямую в компоненты, но вот эта фича по своей сути абсолютно не безопасна.

Что тут происходит? Когда ты используешь dangerouslySetInnerHTML, то отдаешь возможность пользователю или стороннему коду напрямую инжектировать HTML. А дальше… всё очень просто: инжекция скриптов, обход CSP, и вот ты уже с уязвимым приложением, где злоумышленник может делать что угодно.

Так что если в коде приложения видишь что-то вроде:

JSX:
<div dangerouslySetInnerHTML={{ __html: someHTML }} />
Не пугайся, но сразу запускай сканер безопасности или проанализируй, кто и что может в этом контексте вставлять в DOM.

1769005121577.webp


Ref manipulation
Знаешь, как можно найти баги в React-приложении? Иногда достаточно всего лишь немного поиграться с рефами. Ref manipulation - это очень мощная штука, которая, если её не контролировать, может привести к тому, что внешние компоненты получат доступ к внутренним данным.

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

Если речь идет о манипуляциях с ref в React, будьте особенно внимательны. Особенно если используешь компоненты, которые работают с рефами от пользователя, ведь это может быть точкой входа для атак.

Prototype pollution в React apps
Да, Prototype pollution в React-приложениях - это не шутки. Если у тебя есть возможность манипулировать объектами, например, через простое обновление их свойств, то ты можешь случайно или целенаправленно изменить прототипы этих объектов, добавив в них нежелательные методы.

Представь, что ты держишь данные в объекте и передаешь их через компоненты React. Все эти данные могут быть частью объекта, и если ты не проверяешь их на чистоту, злоумышленник может изменить прототип объекта. И вот он - баг, который ломает всю логику. А если эти объекты не будут должным образом защищены, можно получить доступ ко всему приложению. Особенно это касается библиотек с неограниченным доступом к данным или внешним компонентам.

Vue-специфичные уязвимости​

v-html directive injection
Vue - это один из самых популярных фреймворков, и хотя он предоставляет кучу крутых фич, как и в любом другом инструменте, здесь тоже есть свои подводные камни. v-html - это, по сути, тот же опасный dangerouslySetInnerHTML в React, но в Vue. Он позволяет динамически вставлять HTML-контент в шаблон, что может стать лакомым кусочком для атакующих.

Когда ты используешь v-html в Vue, ты даешь возможность инжектировать произвольный HTML в DOM. А значит, если не позаботиться о безопасности, то злоумышленник может внедрить не только HTML, но и JavaScript-код. Это классическая инъекция, которая открывает путь для XSS атак.

Вот пример, когда все может пойти не так:

JSX:
<div v-html="userContent"></div>
Что происходит? Приложение будет вставлять пользовательский контент прямо в DOM, и если этот контент не проверяется и не очищается, то это прямой путь к уязвимости.

1769005152612.webp


Template injection
Кстати, помимо v-html, еще одним потенциально опасным моментом является template injection. Vue имеет свой способ связывания данных с шаблонами, но если ты не будешь аккуратен с динамическими данными, можно легко попасть в ловушку.

Что это значит? Всё очень просто: если данные, которые поступают в Vue-шаблон, не очищаются, атакующий может внедрить свой код прямо в этот шаблон. Это как если бы ты вложил в шаблон чужой код, который будет выполнен на клиенте. Это точно такая же угроза, как и в других фреймворках, и она приходит с динамическими данными, которые не подверглись должной валидации.

SSR-специфичные баги
А вот и ещё одна проблема, о которой редко говорят, - это уязвимости, связанные с SSR (Server-Side Rendering). Когда ты используешь SSR в Vue (или любом другом фреймворке), ты фактически рендеришь контент на сервере, а не на клиенте, и отправляешь готовый HTML. Но если ты используешь SSR без должной валидации или экранирования данных, то ты открываешь возможность для атак, которые могут использовать тот же механизм рендеринга для внедрения злонамеренного кода.

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

Angular-специфичные уязвимости​

bypassSecurityTrust methods*
Одной из самых часто встречающихся уязвимостей в Angular является неправильное использование методов bypassSecurityTrust. Angular имеет встроенные механизмы защиты, которые помогают предотвратить XSS-атаки, но, как это часто бывает, фреймворк предоставляет и методы, которые позволяют обойти эту защиту. Это и есть та самая «дыра», через которую можно пробраться внутрь.

Методы вроде bypassSecurityTrustHtml, bypassSecurityTrustStyle и другие позволяют разработчику обойти стандартные механизмы безопасности Angular и вставлять произвольный HTML, стили или скрипты в DOM. Вроде бы удобно, но если этот функционал используется без должной осторожности, то открывается путь для инъекций и атак.

Вот так это выглядит на практике:

JavaScript:
this.sanitizer.bypassSecurityTrustHtml('<div onclick="maliciousCode()">Click me</div>');
Тут можно легко представить, что, если бы такой код был вставлен в веб-приложение, атакующий мог бы внедрить вредоносный код, который будет выполнен на клиентской стороне. Так что, если работаешь с такими методами, всегда проверяй, что именно ты «пропускаешь» через эти защиты.

Template injection
Переходя к template injection, тут тоже не всё так гладко. Angular активно использует двустороннюю привязку данных (two-way data binding), что в свою очередь может привести к проблемам, если не следить за тем, какие данные ты передаешь в шаблон. Шаблон может включать динамически генерируемые данные, и если эти данные не очищены или не валидируются, злоумышленник может легко внедрить свой код.

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

HTML:
<div>{{ userInput }}</div>
Здесь разработчик думает, что передает безопасные данные, но если userInput будет содержать невалидные или вредоносные строки, это может привести к атаке.

Zone.js и timing attacks
Ещё один интересный момент - это Zone.js. Этот инструмент используется Angular для отслеживания асинхронных операций в приложении, но он может быть использован и злоумышленниками для timing атак. Время отклика, который обработает приложение, может быть использовано для извлечения информации о внутреннем состоянии приложения, что может стать точкой входа для дальнейших атак.

Timing attacks на основе Zone.js могут позволить атакующему вычислить, когда конкретные данные в приложении становятся доступными, и этим воспользоваться для получения доступа к защищенным частям системы. Это может быть особенно опасно, если приложение хранит чувствительные данные, доступ к которым можно получить через разные временные окна.

DOM-based XSS в SPA​

Sources и sinks
Когда мы говорим про DOM-based XSS в SPA, важно понять, как работает разделение на sources и sinks. Это ключевые элементы, которые могут привести к уязвимостям, если их не отслеживать.
  • Sources - это данные, которые поступают извне, например, из URL, с параметров GET-запроса, из localStorage, cookies или даже из данных, введенных пользователем. Эти данные могут быть подменены или манипулированы атакующим.
  • Sinks - это места, где данные попадают в DOM, то есть куда эти данные могут быть вставлены или выполнены. Это могут быть атрибуты HTML, такие как innerHTML, или динамически генерируемые элементы в JavaScript.
Атака DOM-based XSS происходит, когда злоумышленник находит способ передать вредоносные данные через source в sink, и эти данные выполняются в контексте браузера пользователя.

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

JavaScript:
let userInput = getUrlParameter('user'); // source
document.getElementById('welcome').innerHTML = "Hello, " + userInput; // sink
Здесь, если параметр user передается как &lt;script&gt;alert('XSS')&lt;/script&gt;, то злоумышленник может вставить вредоносный код, который будет выполнен в контексте браузера.

Burp DOM Invader
Для тестирования и поиска таких уязвимостей есть отличный инструмент - Burp DOM Invader. Это расширение для Burp Suite, которое помогает искать и анализировать DOM-based XSS уязвимости в веб-приложениях, включая SPA.

Как это работает? Burp DOM Invader позволяет перехватывать и анализировать данные, которые передаются между браузером и сервером, а также отслеживать, как эти данные вставляются в DOM. С помощью этого инструмента можно легко выявить потенциальные уязвимости, чтобы позже их эксплуатировать или, наоборот, устранить.

Например, если в приложении есть динамическое внедрение данных в HTML, Burp DOM Invader поможет найти эти места и предложит возможные точки для XSS-атак. Это не просто полезно - это спасает время и помогает сразу же выявить слабые места в клиентской логике.

В статье о XSS‑атаках подробно рассматриваются классы уязвимостей, включая DOM‑based XSS - именно тот тип, который чаще всего проявляется в современных SPA‑приложениях за счёт динамической манипуляции DOM и небезопасного использования innerHTML и соседних API.

Анализ JavaScript-бандлов​

В мире SPA-приложений JavaScript-бандлы - это, по сути, то, что делает их возможными. Если бы не эта магия сборки, мы бы не имели такой легкости в загрузке и управлении множеством зависимостей и компонентов. Однако здесь есть свои подводные камни, и для пентестера это настоящий рай, чтобы найти уязвимости.

JavaScript-бандлы - это готовые пакеты кода, которые собираются в процессе сборки приложения. Эти бандлы могут содержать не только приложения, но и сторонние библиотеки, которые также могут скрывать уязвимости. А вот как и где можно найти эти уязвимости? Ответ прост: в анализе бандлов.

Как искать уязвимости в бандлах?​

Зачастую, когда ты пентестишь SPA, ты сталкиваешься с кодом, который был собран и «замешан» с различными зависимостями. Это может быть webpack, Rollup или любой другой сборщик. Основная проблема здесь в том, что когда ты смотришь на минифицированный бандл, ты видишь сплошной зашифрованный поток данных, который выглядит как набор беспорядочных символов. Это не тот код, который можно просто прочитать и понять, где там уязвимость.

Вот и приходит на помощь анализ JavaScript-бандлов. Для этого используются различные инструменты, например:
  • Source Maps: если они доступны, это идеальный способ восстанавливать исходный код из минифицированных бандлов. Да, если они присутствуют в продакшн-версии, это просто дар для тестировщика безопасности.
  • webpack-analyzer: полезный инструмент для анализа и визуализации структуры бандлов. Он помогает понять, какие библиотеки и зависимости были использованы, и позволяет найти места, где могут скрываться уязвимости.
  • Burp Suite с плагинами для работы с JavaScript: Burp DOM Invader можно комбинировать с другими инструментами для отслеживания и тестирования того, как данные обрабатываются в бандле.

Анализирование исходных карт (source maps)​

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

Так что если source maps не были удалены или скрыты, ты сможешь без проблем разобрать весь бандл, найти уязвимости в сторонних библиотеках и даже проверить, какие конкретные скрипты используют потенциально опасные функции, вроде dangerouslySetInnerHTML или eval().

Это то место, где атака через dynamic imports или remote JavaScript может быть обнажена. Когда сервер генерирует скрипты на лету или использует скрипты, которые могут быть внешними, это создает дополнительные векторы атаки. И если source maps доступны, можно найти точку входа и проанализировать динамически загружаемые скрипты.

CSP bypass для SPA​

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

Основная проблема заключается в том, что CSP не всегда справляется с тем, чтобы заблокировать inline-скрипты. Эти штуки, хоть и запрещены в идеальной политике CSP, могут быть легко обойдены с помощью data URIs или eval(). А это уже как попасть в дом через окно, даже если дверь закрыта. Злоумышленник может спокойно инжектировать свой скрипт через эти методы, а твоя политика безопасности будет сидеть и смотреть, как это происходит.

Проблема усугубляется тем, что многие старые фреймворки и библиотеки, которые мы до сих пор используем, могут использовать unsafe-eval. Если CSP не настроена правильно, злоумышленник может просто получить доступ к этим уязвимым местам и провести свою атаку через eval().

А если мы еще вспомним про динамическую загрузку JavaScript в SPA, то тут вообще открывается целый новый мир уязвимостей. Давай подумаем: ты собираешься загрузить какой-то модуль, а тут, хоп, внешняя зависимость подкачивает чужой скрипт. Всё, твоя защита - как сито. И если твоя CSP не настроена должным образом, то злоумышленник может запросто подменить содержимое и выполнить код с внешнего сервера. Привет, атака!

И вот, если CSP настроена с директивой report-uri, то вообще весело. Атакующий может спокойно запустить скрипт, отправить фальшивый запрос и узнать, где у тебя слабое место в CSP.

Чтобы вся эта магия не сработала на злоумышленника, нужно, чтобы твоя политика безопасности была как танк. Прежде всего, забудь про unsafe-inline и unsafe-eval. И да, исключи возможность загрузки скриптов с непроверенных источников. Пусть это будут только доверенные CDN и серверы, потому что даже одно "не то" подключение может сломать всё. Кроме того, CSP нужно не просто настроить - а регулярно проверять и обновлять, потому что мир фреймворков и библиотек меняется быстрее, чем ты успеваешь отследить. Используй reporting в CSP, чтобы получать отчеты о нарушениях и сразу видеть попытки обхода.

1769005185037.webp


Руководство по предотвращению XSS акцентирует внимание на эффективности CSP и других защитных мер против выполнения вредоносных скриптов. Это напрямую перекликается с тем, что мы обсуждали про CSP bypass в SPA: строгие политики контента и правильная настройка - критичные элементы любой безопасности клиентской логики.

Подведем итоги​

Пентест SPA-приложений - это совсем не та задача, которую можно решить по шаблону. В 2026 году, когда фреймворки типа React, Vue и Angular доминируют на фронтенде, ошибки и уязвимости, связанные с их особенностями, становятся настоящими входными точками для атак. Каждое приложение может скрывать уязвимости в своих реактивных шаблонах, динамической загрузке контента и манипуляциях с состоянием, и если не быть внимательным, можно упустить целые сегменты угроз.

Однако если понимать природу угроз и знать, где искать слабые места, можно минимизировать риски и сделать приложение значительно более защищенным. Не стоит недооценивать такие вещи, как dangerouslySetInnerHTML, v-html и все эти способы инъекций в шаблоны, которые позволяют атакующим манипулировать данными и выполнять вредоносные скрипты. Задумываться об этом нужно не только в процессе пентеста, но и в процессе разработки, чтобы заранее предотвратить возможности для атак.

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

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab