Скрытие информации в Angular

Kate

Administrator
Команда форума

О себе​

Всем привет! Меня зовут Мащенко Вадим, я работаю в группе разработки и тестирования и занимаюсь тестированием безопасности приложений Мир Plat.Form. С недавнего времени увлекся разработкой на фреймворке Angular. Я решил объединить свое новое увлечение со своей основной работой и показать результаты своего исследования в данной статье.

В данной статье описано, как можно получить полезные данные Angular приложения, почему это важно и как от этого защититься.

52dc32fc08fb353d12f019835183ab3d.png

Введение​

Представьте себя в роли злоумышленника, который хочет взломать сайт. Как бы вы действовали? Атаку можно обобщить в два шага:

  1. Изучение системы: стек технологий, поиск всех возможных точек входа, поиск известных уязвимостей в приложении и т.д.
  2. Эксплуатация найденных уязвимостей
Можно провести сравнение защищаемого сайта с крепостью с массивными, укрепленными главными воротами. Чтобы проникнуть туда, придется потратить время на изучение замка. Но если знать, как строился замок и где там может быть брешь, то первый этап атаки на приложение можно считать выполненным.

54ce7e207e85b83e31b75f96a318a46c.png

Часто такой брешью в приложениях SPA (Single Page Application) является раскрытие информации о самом приложении и frontend и backend частей.

Дальнейшие примеры будут рассматриваться с использованием фреймворка Angular.

Немного про SPA​

Существует два подхода в работе веб приложений:

  1. Multi Page Application (многостраничное приложение) - сайт, который работает по традиционной схеме, когда на каждый запрос вам возвращается новый HTML файл. Т.е. при каждом запросе страница обновляется.
  2. Single Page Application (одностраничное приложение) - сайт, для работы которого не требуется обновление страницы, потому что весть код приложения загружается при инициализирующем запросе, а данные загружаются с помощью асинхронных запросов.
    7ed7a30ccf62236f05b13817a3dd7a55.png

Немного про Angular​

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

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

117ff1d488c89f84983393d15d47545d.png

Раскрытие данных​

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

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

4e46f9cfd10727f4181132c9d8eea6f3.png

  • runtime.js - загрузчик Webpack, позволяющий загружать другие файлы;
  • main.js - хранит код приложения;
  • polyfills.js - позволяет обеспечить совместимость приложения с браузерами.
Начинаем исследовать приложение. Открывая файл main.js из этой же панели разработчика, можно увидеть из чего состоит приложение.

Например, у нас есть исходный код:

48a1f48caf2ef5f98b7b7a84e5fa66d3.png

И есть этот же участок кода из бандла:

1c170ee95667006ee6e7d25334cd264d.png

Сперва может показаться, что всё не совсем понятно, однако можно увидеть некоторое сходство.

Далее по сути реверс.

Например, атрибуты тега:

245a071ede4bbf6a99b5bf8e29035171.png

Сами теги:

dddad7738c992b24e8a3707579c3c24d.png

Названия переменных:

b74c2f19f7a7ffd01c3ff454eb0f5e87.png

Селектор компонента (его название), обычно связан с его логикой:

9a41da7c7637cf101062506eef3bc7b8.png

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

Открываем панель разработчика, ищем и открываем main.js файл, далее через поиск ищем различные endpoint-ы.

Найденные API запросы системы. Часть адреса скрыта.
Найденные API запросы системы. Часть адреса скрыта.
Собираем все запросы и начинаем искать уязвимости на стороне backend-а. Тут можно сказать: "Это же проблемы backend, при чем здесь frontend?". Да, это проблемы backend-а, но упростил исследование приложения для неаутентифицированного пользователя frontend. Вместо перебора запросов у нас теперь есть определенные точки входа, некоторые из которых могут быть не защищены.

Тем самым, информация, которую мы можем получить с frontend части на Angular, может быть:

  • конфигурация системы;
  • API запросы;
  • сервисы + компоненты;
  • явно прописанные данные (ключи, логины, пароли и т.д.)
  • ...

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

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

Роут в Angular приложении - это привязка Angular компонента к URL, по которому его можно получить.

Два роута приложения - страница входа и основная страница
Два роута приложения - страница входа и основная страница
Обязательными атрибутами роута являются:

  1. path - относительный путь до компонента;
  2. component - привязанный к нему Angular компонент.
Также могут быть необязательные атрибуты, такие как Guard-ы:

  1. CanActivate
  2. CanLoad

CanActivate​

(https://angular.io/api/router/CanActivate)

Еще один ключ объекта, canActivate, указывает на класс AuthGuard - реализация интерфейса CanActivate. В нём установлена проверка доступа к роутам.

Все роуты, кроме /login, недоступны неаутентифицированным пользователям.
Все роуты, кроме /login, недоступны неаутентифицированным пользователям.
В примере есть всего 2 роута: страница входа (login) и основная страница (main). При вводе пустого роута, будет происходить редирект на страницу "main". При каждом посещении данной страницы будет выполняться проверка доступа пользователя для данного роута через функцию canActivate.

5aaac01df902ec55e9c7bd50908b2228.png

Если у пользователя есть доступ, функция будет возвращать true, иначе функция вернет false.

Собираем такое приложение, запускаем и смотрим наши бандлы. По ключевым слова canActivate или auth находим нашу функцию canActivate и сервис по проверке доступа.

Найденный canActivate в бандле приложения
Найденный canActivate в бандле приложения
Почему так произошло? Angular - это модульное приложение, т.е. его можно разбивать на части (модули). Модуль - инкапсуляция функционала приложения. По умолчанию, в приложении Angular присутствует корневой модуль, в котором перечислены все компоненты, сервисы, Guard-ы и т.д.

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

d278fa8dda87cc0bf70e5dea7db976c0.png

Таким образом такая защита роутов не является надежной.

Can Load​

(https://angular.io/api/router/CanLoad)

Lazy Load (Ленивая загрузка) позволяет подгружать некоторые бандлы отдельно от основного приложения.

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

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

2bfae2369847050c46e4635830fae606.png

Таким образом, пока мы не обратимся по пути /posts, данный модуль не будет загружен. Однако, сделав это будучи не авторизованным пользователем, мы также сможем подгрузить данный участок кода.

fe0e712cf42b2093c18d4b85def17433.png

Для таких случаев используется Guard CanLoad (https://angular.io/api/router/CanLoad), который, как и CanActivate, записывается в качестве атрибута роута и ссылается на файл, где выполняется проверка доступа пользователя для данного роута.

38336ee4e66a1256cf2a67d0f4cfe978.png

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

ef09b9a1180d25cccab90aeb9a12fd22.png

Однако это ненадежная защита, т.к. обход такой защиты есть.

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

Хэши двух Lazy Load модулей
Хэши двух Lazy Load модулей
Зная имя файла мы сможем открыть код Lazy Load модуля в браузере. Просто берем атрибут хэша, сам хэш, добавляем в конце ".js" и подставляем в хосту: http://{host}/275.f668…324.js

Таким образом Guard-ы не скроют должным образом необходимую информацию.

CTF​

В рамках проведения локальных соревнований по компьютерной безопасности (Capture The Flag) я подготовил задание, связанное с Angular Lazy Load (https://gitlab.com/v4dimm/lazy-load). Необходимо найти флаг (секретное слово), заполучив права администратора, имея только пользователя без соответствующих прав.

Запустите приложение и попробуйте достать флаг методом black box.

Какие у нас есть варианты защиты?​

62c3ce47b7f7dd2a7be2ca1d2fdcb668.png

Можно предложить 2 варианта:

  1. Сделать код нечитаемым для злоумышленника
  2. Хранить часть кода на другом сервере (по сути, использование микрофрентенда)

Нечитаемый код​

Обфускация (процесс создания нечитаемого кода) JS кода - это ненадежная защита из-за особенностей реализации JS. Например, при обфускации сохраняется структура кода, т.к. обфускатор не понимает, как он будет исполняться. Тем самым задача обфускации JS кода сводится к его минимизации.

Деобфускация JS кода - это лишь вопрос времени. Достаточно понять алгоритм обфусцирования, и его можно автоматизировать и применить к остальным файлам. К тому же, если разработчики оставили source map - механизм для связи минифицированного файла с исходным, то в обфускации с точки зрения безопасности вообще нет смысла. Тут можете погуглить различные исследования на эту тему. Есть еще вариант попробовать использовать сборщик gcc (https://www.npmjs.com/package/google-closure-compiler) для изменения структуры самого бандла, но это уже не про нечитаемый код и не тема данной статьи.

Микрофронтенд​

Рассмотрим несколько вариантов скрытия информации SPA приложений. В рамках приведенных примеров будем считать backend априори безопасным. Все проверки доступа пользователей происходят на стороне backend.

Server Side Rendering​

Обычное приложение Angular выполняется в браузере, отображая страницы в DOM в ответ на действия пользователя. С использованием Angular Universal (опенсорсный проект, который расширяет функциональность Angular приложений, делая возможным SSR в Angular) приложение запускается на сервере, генерируя статические страницы приложения, которые впоследствии загружаются на клиент.

d09937268346235e339ce75358b9a8fc.png

Обычно это делается для оптимизации и для поисковиков, чтобы была информация для индексации.

Запуск стартового приложения Angular в SSR
Запуск стартового приложения Angular в SSR
При использовании Angular Universal создаются новые файлы для работы на стороне сервера. Нас интересует файл сервера - server.ts.

Созданные файлы сервера
Созданные файлы сервера
Это Express сервер (он используется по умолчанию) и в нем можно прописать дополнительные обработки маршрутов.

Возьмем приложение, которое использует Lazy Load. При сборке все файлы будут храниться на сервере, включая бандлы, которые не должны быть на клиенте при инициализирующем запросе. Сделаем на сервере обработку прямых обращений к этим бандлам (для начала попробуем возвращать 403 ошибку).

При обращении к бандлам возвращается 403 ошибка - нет доступа
При обращении к бандлам возвращается 403 ошибка - нет доступа
При попытке загрузить бандл, вернётся 403 ошибка.

128c5bc8c92ae75e9458cda7906e4e15.png

Мы смогли защитить часть приложения от раскрытия данных. Далее можем писать в тело функции собственные проверки.

Особенности данного подхода:

  • Определенный backend (Express, ASP.NET Core, hapi). Если в рамках разработки уже есть свой бэк, например на Java, то Express можно использовать как proxy.
  • Придётся поправлять код под серверную часть, иначе могут возникнуть ошибки, например, работа с localStorage, т.к. при создании файлов сервера код копируется из браузерной части. Если была работа с localStorage, на стороне бэка будет ошибка, т.к. там нет localStorage.

Angular Elements​

Есть такая технология как Web Components, которая позволяет вставлять в приложение некоторый функционал посредством создания пользовательских HTML-элементов. Это позволяет делать Web Components универсальными для разных фреймворков.

19c1806846f9569d06411e9912e6574f.png

Angular Elements - это по сути Custom Elements (пользовательские элементы - часть Web Component), возможность создавать новые HTML-теги с произвольным имене из Angular приложения.

Рассмотрим на примере. Создадим приложение, которое хотим использовать как Angular Element. Убираем загрузку корневого компонентам при запуске приложения (bootstrap) в корневом модуле. Добавляем его загрузку через injector - часть механизма Dependency Injection. Там же помечаем название нового тега.

63a55561999b0b74de9ccd0e94a1b672.png

В момент, когда на основном приложении выполняется необходимое условие, например, пользователь успешно прошел аутентификацию, созданный нами HTML-тег и скрипты (основные бандлы для работы Angular Element) можно просто вставить в HTML файл.

93a150d808000a3b79a2a9afbc1068b8.png

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

Angular Element и его бандлы
Angular Element и его бандлы
Особенности данного подхода:

  • Сложная реализация и поддержка такого решения.
  • Routing внутри Angular Elements работать не будет. Придется искать обходные пути, либо на каждый отдельный роут использовать отдельный Angular Element.
Есть проект, в котором можно посмотреть реализацию загрузки модулей (по сути Lazy Load) через Angular Elements: https://github.com/konstantindenerz/angular-lazy-loading-modules-different-server.

Lazy Load с сервера​

Вспомним про ленивую загрузку в Angular.

Если посмотреть, как билдится (собирается) приложение, можно увидеть помимо основных бандлов создание lazy chunk файлов, которые отвечают за подгружаемые модули.

efd8ad030c37deb6a11d27e8fa0718b8.png

Упоминания о которых также есть в runtime.js файле

2893e593348a95521e208dbeb0dfe18a.png

Посмотрим на этот файл в дебаг сборке командой:

$ NG_BUILD_DEBUG_OPTIMIZE=true ng build

Можно увидеть, что это chunkId, которые используются webpack-ом (сборщиком модулей JS):

00a4af852829cba85ee0653300c04796.png

Идем дальше и видим, что есть переменная url, которая формируется с помощью chunkId и еще какой-то переменной:

72002f465bb3975cf32643c3b56e5b39.png

Если вернуться к сжатому представлению файла runtime.js файла, то данная строка будет иметь данный формат:

598655ab7f190b372ba569f6e7e5016b.png

Там же можно заметить переменную, которая также участвует в формировании url-а.

f607278ee547ab7d20612b76bdd01838.png

И если явно записать в эту переменную адрес сервера, где будут лежать lazy chunk файлы:

da7e40e59f63d3fd770ea7a1b1d4daab.png

То при запуске приложения можно увидеть такую картину

bfcd4054f9548b006fc7ac3806088e23.png

Когда мы переходим по роуту, за которым закреплен другой модуль, т.е. когда подтягивается lazy chunk файл, запрос идёт на другой сервер. Т.к. бандл больше не хранится на фронте, а лежит на backend-а, где выполняется проверка для его получения, то можно считать, что нам удалось скрыть часть данных приложения.

Особенности данного подхода:

  • Проблематично автоматизировать такое решение
  • На backend можно будет ходить только с cookie. Запрос на загрузку JS файлов frontend делает автоматически без возможности вставить туда дополнительные HTTP заголовки. Единственный заголовок, которые появляется автоматически - это Cookie, если для данного сайта есть файлы cookie.

Заключение​

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

  • Server Side Rendering
  • Angular Elements
  • Lazy Load с севера
Отмечу, что данные методы защиты подойдут не всем и не для каждого приложения. Однако стоит знать о рисках при использовании Angular и в целом SPA приложений.

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

 
Сверху