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

В этом материале расскажем о безопасности на примере мультиязычного e-commerce сервиса – интернет-магазина с аккаунтом покупателя. Проект построен на NextJS, где часть бекенда на JS и пишется фронтендерами. Поэтому помнить о безопасности в таком случае приходится более старательно, держа в уме случаи, о которых расскажем в этой статье. 

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

Как уже упомянули выше, сервис, о котором мы говорим, построен на NextJS. Немного об архитектуре. Рендеринг страниц происходит на NextJS, а подтягивание контента и пользовательских данных сделаны через API на Umbraco (относительно недавно Umbraco зарелизила Content Delivery API для разработки headless решений). Пользовательские сессии так же создаются в Umbraco и потом используются в NextJS. Еще есть сторонний сервис Shopify, который реализует e-commerce функциональность, и NextJS сайт взаимодействует с ним через API. За счет этих архитектурных особенностей бекенд ответственность за безопасность размывается на бекенд и фронтенд разработчиков. Поэтому security issues могут возникать и там, и там.

Проблема №1 – утечка данных при пробросе значений фильтров в API

Фича: в личном кабинете пользователь может просмотреть свои текущие подписки. Данные тянутся POST-запросом из my account API на Umbraco, которая тянет активные подписки из Shopify. Тестировщик перехватил этот запрос, изучил его содержимое, убрал из него cookie пользовательской сессии, а также передал пустое тело запроса. В результате ему вернулись все подписки всех пользователей. 

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

  1. Клиентское JS приложение запрашивало подписки текущего пользователя из NextJS website, передавая cookie текущей сессии пользователя и email текущего пользователя. Собственно, те данные, которые тестировщик и вырезал в рамках теста.

  2. NextJS website, зная секретный Application token Umbraco, запрашивал подписки из Umbraco, и в качестве фильтра прокидывал тот самый email пользователя.   

  3. Umbraco, зная секретный Application token Shopify, запрашивало подписки из Shopify, снова прокидывая email пользователя из параметров.

Отдельно и фронт и бэк были сделаны нормально, но при сопоставлении получилась дырка в безопасности – сессию пользователя никто не проверил: фронт думал, что сессию проверит бэк на Umbraco, а бэк думал, что сессию проверит фронт на NextJS

Также оказалось, что Shopify API, когда ему передают пустой email в качестве параметра, возвращает все подписки всех пользователей, то есть параметр email в API выступает, как фильтр, и если он пустой – возвращается все. Сочетание этих факторов и привело к найденной проблеме.

Как правильно

Из клиентского JS приложения нельзя передавать email и другие пользовательские данные, как параметры запроса. Вместо параметров email пользователя нужно брать из контекста сессии текущего пользователя, заодно эту сессию и проверив на валидность.

Проблема №2 – проверка прав доступа по параметрам запроса, а не по текущей сессии

На сайте есть возможность поменять пароль. Тестировщики перехватили запрос на change password и убрали identification cookie, отправили пустой запрос только с телом, где передаем старый и новый пароль на бэк. В одном месте безопасность такой запрос пропустила, а в другом проверила. Когда делаем запрос на изменение пароля, информацию о залогиненном пользователя нужно брать из identification cookies, а не из параметров.

Другой случай подобного характера: на сайте есть секция project builder. Что-то вроде избранного, где пользователь может разложить понравившиеся продукты по папкам-проектам. Перехватили отправленный запрос, убрали identification cookie, но оставили тело запроса, в котором передается информация о том, для какого пользователя создать коллекцию и из каких продуктов. В итоге без аутентификации можно было обновить избранное другого пользователя. Ни один из уровней безопасности не отработал такое проникновение.

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

Проблема №3 – возможность записать вредоносный скрипт при создании сущностей

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

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

Проблема №4 – возможность создать редирект на фишинговый сайт

Редиректы. Пользователь, перемещаясь по сайту, может добавить продукт в корзину, а затем перейти на чекаут. Особенность имплементации сайта, о котором мы говорим, в том, что e-commerce сделан на Shopify – корзина отрисовывается на нашем сайте, но на чекаут – доставку и оплату – мы редиректим пользователя на Shopify портал. 

Получается, если пользователь залогинен на нашем сайте, это же залогиненное состояние нужно сохранить и при редиректе на Shopify. В терминологии Shopify такая имплементация Single-SignOn называется Multipass, и подробно про нее можно прочитать тут

Вкратце – чтобы сгенерировать редирект, NextJS отправляет запрос с URL для редиректа на Umbraco API, который шифрует этот URL секретным ключом от Shopify, а также подставляет идентификатор пользователя, под которым нужно залогинить при редиректе. Как оказалось, код нашего Umbraco API позволяет в URL с редиректом подбросить вообще любой URL, его не проверяет при редиректе и Shopify тоже. Это опасно тем, что можно попасть на фишинг. Злоумышленник может подготовить фишинговый сайт, который будет выглядеть как ваш сайт, первый домен будет валидным, а по факту при нажатии на ссылку пользователь перейдет на фишинг, и, доверившись изначальной картинке,  может выдать свои данные.

Как правильно

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

Проблема №5 – беспрепятственный перебор паролей

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

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

Если раздражает выбирать на картинках светофоры, то можно сделать невидимую капчу от Google, которая будет проверяться на бэке, прежде чем дальнейший код начнет выполняться. Второй способ защиты – rate limiting: с одного IP, браузера и так далее. Самое простое — после нескольких попыток логина с одного IP адреса, на последующие частые запросы возвращать ошибку. Также есть алгоритмы, которые кроме троттлинга еще и увеличивают время ожидания. То есть на первую попытку неверного логина сайт быстро произведет ответ, на вторую возьмет больше времени и так далее. Если время ответа увеличивать экспоненциально, то уже после нескольких неудачных попыток подбора пароля ждать придется очень долго.

Проблема №6 – активные сессии после смены пароля

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

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

Как правильно

Для реализации этой логики потребуется где-то хранить активные сессии пользователя, так как сам по себе NextJS за вас этого делать не будет. В нашем случае мы организовали хранение активных сессий в базе данных Umbraco, и NextJS приложение запрашивало и обновляло данные о сессиях через API.

Проблема №7 – Robert; drop table students

В форме регистрации на сайте в поля имени и фамилии пользователя можно ввести скрипт и HTML-разметку. И если бекенд, как в классическом комиксе, предусмотрел защиту от SQL инъекций, то защита от фронтенд скриптов не всегда очевидна.

Почему это опасно, ведь их вижу только я? Обычно данные регистрации, которые собирает сайт, вставляются еще и в письмо для подтверждения регистрации. Например, “Добрый день, Антон! Спасибо за регистрацию на нашем портале.” 

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

Проблема №8 – риск увеличения счета на оплату за сторонние сервисы

(Не)правильная настройка Google Maps API keys (и любых других платных сервисов и API, на которых можно задать ограничение по домену использования). Если настройки доменов правильно не выставлены, эти ключи можно использовать на других сайтах и клиент будет платить больше денег. Бывают случаи, когда ключи уводили, и держатель сайта платил тысячи долларов. 

Как правильно

Помимо настройки ограничений для этих ключей, следует оптимизировать и само количество запросов к платным сервисам. Если код написан не очень эффективно, и получается очень много запросов в Maps API, конкуренты могут ходить тыкать и тратить ваши деньги. Как можем ограничить количество запросов?  С помощью уменьшения территории, по которой ищем, и уменьшения запросов в Google Places. Иногда бывает, что компонент карты в коде инициализируется на загрузку любой страницы, хотя сама карта либо глубоко внизу страницы, либо вообще скрыта на вкладке, которую еще надо переключить. 

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

Комментарии (2)


  1. MadridianFox
    04.04.2024 10:21

    А при чём тут next.js и вообще JS?
    Все перечисленные уязвимости - это ошибки в коде без привязки к языку и фреймворку.


    1. diger_74 Автор
      04.04.2024 10:21

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

      Во введении я постарался сделать акцент на том, что ошибки вызваны в основном размытием ответственности между бекенд и фронтенд разработчиками. Эта ситуация часто возникает, когда бекенд базируется на NodeJS.

      Де-факто (мое оценочное суждение) за последние несколько лет Next/Nuxt стали самыми используемыми фреймворками в этом направлении.

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