Можно ли при идеальном коде и защищённой инфраструктуре иметь серьёзные проблемы в безопасности продукта? Защитят ли нас полностью современные средства инфраструктурной защиты, анализа кода? А что, если нет? Давайте подумаем об этом на отвлечённых примерах из практики.
Привет, Хабр! Меня зовут Игорь Игнатьев, я директор департамента защиты приложений в VK. Сегодня расскажу о подходе к построению безопасных продуктов, и это будет моя первая статья из цикла про принципы Secure by Design.
Идеология Secure by Design в моём понимании — это интеграция свойств безопасности в будущий продукт наравне с функциональными требованиями. Первостепенно именно функциональные требования в продукте определяют его ключевые цели и идеи. Добавление свойств безопасности только в автоматизируемые части продукта (ИТ-системы) помогает создать безопасное приложение, но не продукт.
На конференциях и в статьях довольно часто говорят о подходе Secure by Design и о смещении Shift-Left в обеспечении безопасности продуктов компании, демонстрируя в расширенном или сжатом виде примерно такую схему:
Но в сложившейся практике рассуждать о безопасности продукта начинают с приложений, кода и его зависимостей, то есть фактически уже на этапах разработки и тестирования. Несомненно, устранение уязвимостей в коде до выхода приложения или сервиса в эксплуатацию является неотъемлемой частью Secure by Design, но безопасный код — это ещё не безопасный продукт. Более того, само по себе создание нового продукта или дополнительных функций в нём не всегда предполагает наличие исходного кода, доступного для анализа на безопасность. Это может быть и использование различных готовых «кирпичиков», модификация функциональности через настройки, использование Low-code-систем. Мы можем использовать BPM-системы без программирования, либо использовать шину данных, в которой новые интеграции реализуются через добавление заранее разработанных компонентов, как в Integration Bus от IBM.
Использование подобных Low-code-решений предполагает наличие готовых, протестированных и даже безопасных по отдельности элементов, сокращающих этапы создания продукта. Причём их можно переставлять «на лету», не переживая за скорость и качество их работы. Действительно, от перестановки элементов (или блоков) местами код не меняется и остаётся таким же безопасным, но бизнес-логика приложения меняется кардинально.
Отсутствие безопасности на начальном этапе формирования продукта способно нанести ему ущерб, и более того, вы понесёте затраты на разработку заведомо уязвимого продукта.
Сегодня мы разберём первую часть, в которой необходимо участие безопасности, а именно этап Product Vision (PV) и проектирования бизнес-процессов в продукте (или клиентских путей). Этап PV фактически привносит основную ценность продукта и затрагивает ключевые процессы. Развитие составляющих продукта в дальнейшем начинается с детализации процессов, которые будут эту ценность реализовывать.
Рассмотрим первый (простой) пример. Возьмём синтетический продукт, в котором пользователям начисляются деньги, бонусы или баллы за определённую активность. При этом основной расчёт идёт по строгому алгоритму в системе биллинга, после чего проводится сверка и назначаются выплаты. Но в дополнение к основному процессу команда продукта планирует реализовать мотивационные выплаты, процесс по которым будет выглядеть иначе:
Процесс изображён очень поверхностно и без использования нотаций, но этого вполне достаточно для отображения его сути. Рассмотрим, как всё устроено.
Руководитель аналитика инициирует процесс, он же является и владельцем бюджета на мотивационные выплаты. В данный процесс заложены две ключевые задачи:
- Подготовка данных, по которым необходимо начислить клиентам мотивирующие деньги, баллы или бонусы.
- Само начисление этих средств, исполняемое сотрудником поддержки.
В первой части процесса владелец бюджета готовит для аналитиков информацию, и важно, что после анализа сам владелец бюджета и постановщик задачи принимает результат. Он как автор маркетинговой кампании своим подтверждением соглашается с результирующей выборкой клиентов и распределением между ними мотивационных выплат. А далее мы переходим к исполнению этого поручения сотрудником поддержки. И здесь картина совершенно другая: специалист поддержки получает задачу и самостоятельно, без какого-либо дополнительного контроля, зачисляет средства. А куда он зачислит эти деньги? А что остановит его от назначения выплаты не клиенту, а своему товарищу или себе? А может ли он по ошибке из большого списка однофамильцев нажать не туда? Очевидно, что может.
Давайте остановимся на этом моменте и предположим, что мы потратили огромные усилия и сделали безопасным код и архитектуру приложения, прикрутили везде двухфакторную аутентификацию и «обмазали» себя WAFами и NGFW. Но это нам не помогло, так как риски внутреннего злоумышленника, начисляющего деньги (или их аналог в виде баллов) себе, своим друзьям и т.п., заложены в процесс задолго до первой строчки кода. |
Как сделать этот процесс безопаснее? Убрать такой риск из процесса, конечно же, можно различными способами — от исключения человеческого фактора в виде сотрудника поддержки до реализации подтверждения его действий «второй рукой» (например, другим произвольным сотрудником поддержки) и прочей автоматизации.
Это был достаточно простой пример, не предполагающий вызов связей между различными процессами, циклов, развилок и прочего. Он состоит всего лишь из одной схемы. А если процессов больше, да они ещё и связаны друг с другом? Посмотрим на них.
Рассмотрим пример из другой области.
Он будет чуть сложнее, так как процессов несколько, и они используют друг друга. Возьмём близкую для многих тему — программы лояльности торговой сети. Вы наверняка сталкивались с бонусными картами различных торговых гигантов, кэшбеками и т.п.
Итак, компания N решила создать свою собственную бонусную программу, раздать всем очередную порцию пластиковых карт лояльности и ввести свои правила по оплате товаров бонусами. Аналитики продукта начали работу с процесса списания бонусов. Посмотрим на него:
Списание бонусов в программе лояльности осуществляется при накоплении бонусов выше порога в 10 тысяч, и оплату ими товаров, сумма которых превышает 50 рублей (естественно, суммы тут могут быть любыми, это лишь пример).
Далее переходим к основному процессу покупок, куда встраивается бонусная программа:
Рассмотрим только основную линию, которая предполагает вызов подпроцесса списания бонусов. Здесь учтены подробности проверки способа оплаты — деньгами или бонусами, — наличия достаточного количества бонусов и вызова необходимого подпроцесса по списанию. Но наконец кто-то из команды вспомнил, что есть и процесс возврата товаров, при котором начисленные по предыдущей схеме бонусы нужно забрать обратно.
В процессе возврата купленных товаров, как и в процессе покупки, решили использовать существующий подпроцесс списания бонусов, чтобы не создавать лишние ветки. Процедура списания бонусов использована как готовый сервис, ведь нет смысла городить дублирующий процесс или сервис, если есть готовый.
Вероятно, вы уже нашли ключевую проблему, но для тех, кому лень:
Покупаем товары на 9 999 р, получаем условные 9 999 бонусов.
Возвращаем товар. И тут из-за дефекта связки в процессах мы попадаем на подпроцесс списания бонусов, который запускает проверку «Бонусов>10 000». И наша транзакция под эти условия не попадает.
У нас на руках остались возвращённые за товары деньги и 9 999 бонусов!
А дальше делаем, что захотим. Например, покупаем жвачку за 1 рубль, достигаем заветной цифры в 10 000 бонусов и накупаем на них товары.
Определённо, мы можем выявить это на тестировании, но откатимся в этом случае не к доработке кода, а дальше... к самому истоку проблемы — к перестраиванию бизнес-процессов. Но тут есть и вторая проблема. А если злополучный процесс списания бонусов не содержал каких-либо условий в первой версии программы лояльности? Если мы решили его изменить через год после работы, когда аналитики и разработчики первых сервисов уже сменились? Никто даже не расскажет, как этот сервис работает и какая логика в нём была. Скорее всего, в этот сервис или блок просто внесли бы изменения, без понимания всех сценариев, когда он вызывается.
Это была краткая демонстрация ошибки бизнес-логики, которую невозможно отловить автоматизированными средствами. Никакие SAST|DAST|API-fuzzing тут не помогут. Только руками и только человек, который понимает суть бизнес-процесса и способен идентифицировать риски безопасности в нём, может привнести необходимые свойства в архитектуру и сделать продукт тем самым “Secure by Design”.
К сожалению, нет серебряной пули для всех предметных областей, и я не претендую на полноту описания в том, как анализировать бизнес-процессы. Тем не менее, на практике сформировались следующие подходы (пусть в чём-то и абстрактные) по оценке бизнес-процессов на этапе их проектирования:
Часть 1. Погружение в процесс и PV
1. Понять, что делает продукт и процессы в нём. Определить или узнать у автора о целях продукта и внутренних процессов. Очень часто вместо описания PV и бизнес-цели ограничиваются просто текстом задачи или вырезкой из ТЗ. Если взять наш второй пример, то задача вида «внедрить проверку суммы бонусов перед списанием» выглядит весьма чёткой и понятной. Не копая вглубь, не задавая дополнительных вопросов и без построения сквозной цепочки процессов найти проблему будет невозможно. Ситуация будет ещё хуже, если аналитик ИБ сам начнёт додумывать и строить догадки о целях вносимых изменений — тут может получиться хаос.
2. Определить состав участников. На ранних этапах проектирования забывают о других участниках процесса. Например, от имени клиента может действовать представитель с доверенностью. Не сломается ли ваш процесс, если придёт доверенное лицо вместо клиента?
3. Определить состав информации. Какие данные обрабатывает процесс, обеспечен ли он вообще этими данными?
4. Определить границы бизнес-процесса. Описан ли процесс полностью, либо где-то в нём есть мёртвые и недостижимые ветки или потерянные выходы? Чем больше процесс, тем больше логических ошибок в нём может содержаться.
Часть 2. Работа с задачами
Здесь мы идём по каждому действию в процессе:
1. Проверить условия выполнения действия. Каждое действие требует для соблюдения определённых условий, например, для возврата товара в магазине нужно написать заявление. То есть операция по возврату не может начаться до завершения предыдущего процесса.
2. Проверить состав и полномочия участников. Кто выполняет действие, есть ли у него право на его выполнение? Нельзя возложить на сотрудника проверку, например, актуальности банковской карты, потому что это работа процессинга банка. То же касается и полномочий, когда человек с определённой ролью не может быть участником каких-либо операций.
3. Идентифицировать риски операции. Напомню, что мы говорим про действия в процессе. И самый последний этап работы с ними — анализ рисков. Здесь необходимо оценить, насколько то или иное действие несёт угрозу для компании и требуются ли дополнительные шаги или снижение рисков в рассматриваемой операции. Например, обратившись к первому сценарию с зачислением денег клиенту от сотрудника поддержки, мы должны были остановиться на этом блоке, задуматься о рисках и мерах их митигации.
Часть 3. Работа со связями в процессе
1. Проверка достаточности и избыточности связей. Базовая гигиеническая проверка (особенно актуальная для больших процессов) связана с контролем брошенных операций и дублирующих взаимодействий.
2. Проверка цепочек и циклов. Второе действие над связями, предполагающее оценку процесса на наличие замкнутых контуров и зацикливания.
3. Проверка условий выполнения связи. Здесь также вмешивается процесс анализа рисков, особенно в том случае, когда каждый процесс или подпроцесс сам по себе является корректным, но вызов его из неподходящего сценария приводит к нежелательным последствиям. Такое мы обнаружили во втором примере при попытке вернуть товар и списать бонусы.
Часть 4. Работа с условиями и развилками
1. Определить достаточность условия для принятия решения. Каждое условие в процессе служит для выбора последующей последовательности действий. Но часто в проектировании случается так, что само по себе условие не достигает цели. Важно проверить как наличие необходимой информации для условия, так и достижение его цели.
2. Проверка исполнимости альтернативных веток. Довольно часто при проектировании процесса аналитик бежит за главной целью, то есть выстраивает прямой положительный сценарий. И в проектировании процесса возникают проблемы с корректным описанием альтернативных веток (а где-то их могут и просто забыть описать).
3. Проверка полномочий. Этот пункт ранее был описан для действий, но в соблюдении условий он остаётся таким же. Некоторые развилки попадают в зону ответственности человека, но и он не всегда может принять правильное решение.
Изначальная цель процесса или клиентского пути, которую предполагал владелец продукта, и результат отображения его «на бумаге» могут не совпадать, а иногда даже противоречить друг другу. Обычно дефекты в процессах выявляются на тестировании (а некоторые и в эксплуатации), когда казалось, что продукт уже совсем готов, а попытки поставить подпорки в процесс приводят не к самым лучшим результатам. При этом важно отметить, что любые последующие действия — будь то разработка архитектуры, кодирование и т. п., — опираются на бизнес-процессы как на фундамент продукта, автоматизируя их. Отсутствие контроля за этим фундаментом может приводить к неприятным последствиям.
Именно безопасник (с присущей ему паранойей и складом ума) помог бы бизнес-аналитикам выйти за границы прямого позитивного развития ситуации, проверив каждую ветку создаваемого в продукте процесса на предмет наличия рисков безопасности, и найти необходимые пограничные случаи, проанализировать каждого из участников в роли внутреннего или внешнего злоумышленника.
В следующей статье постараемся разобраться в небезопасной архитектуре. Что интересно, там всё ещё не будет ни одной строчки кода, но в архитектуре продукта уже будут заложены серьезные проблемы.