Типичный парадокс из жизни безопасника: инцидентов быть не должно (потому что инциденты = потери для бизнеса); но инцидентов должно быть много (потому что без опыта реагирования будет трудно сохранять квалификацию и оперативно отражать атаки). Для выхода из этого порочного круга крупные компании заказывают услуги Red Team: нанимают сторонних специалистов, которые пытаются взломать компанию. Но, во-первых, это довольно дорого; во-вторых, развернуться здесь трудно: мало кто позволит всерьез ломать бизнес-критичные сервисы. Мы решили попробовать другой подход — практические учения — и год назад впервые организовали бесплатный тренинг для корпоративных команд Cyber Polygon. В роли Red Team мы атаковали сразу нескольких команд-участниц, причем все происходило в специальной тренировочной инфраструктуре, которую не жалко.  В июле прошел Cyber Polygon 2.0. В нем участвовали уже 120 команд из 29 стран, а сценарии тренинга включали и защиту инфраструктуры от активной атаки (Defence), и реагирование и расследование инцидентов (Response). В этом райтапе мы расскажем о заданиях сценария Defence: идеи для него мы черпали из опыта подготовки attack-defence CTF. Согласно легенде, в виртуальной инфраструктуре организации функционировал сервис, обрабатывающий конфиденциальную информацию клиентов. Этот сервис заинтересовал некую APT-группировку, которая планировала украсть конфиденциальные данные пользователей и перепродать их на черном рынке, чтобы извлечь финансовую выгоду и нанести репутационный ущерб организации. APT-группировка заранее провела необходимую разведку, обнаружила ряд критичных уязвимостей в целевой системе и в день учений начала свою атаку. Перед участниками тренинга стояли следующие задачи: как можно быстрее справиться с начавшейся атакой; минимизировать объем украденной информации; сохранить работоспособность сервиса. Участники могли использовать любые доступные и привычные им средства и методы защиты. Как мы сказали, при разработке сценария мы вдохновлялись форматом attack-defenсe CTF. Однако на Cyber Polygon участникам не нужно было атаковать другие команды — достаточно было только защищать свой сервис. Такое правило мы ввели для того, чтобы все участники оказались в равных условиях и сконцентрировались на улучшении навыков обороны. Кроме того, благодаря ему количественные метрики, а значит и оценка уровня команд, были более объективными. В качестве метрик были определены следующие показатели: Health Points (HP). HP выражалось простым численным значением. Команда теряла очки HP, если Red Team смогла успешно проэксплуатировать заложенную в сервисе уязвимость и получить флаг. Чем больше уязвимостей смогла проэксплуатировать Red Team, тем больше HP теряла команда, но при этом у каждой из команд HP отнимались только один раз за раунд. Service Level Agreement (SLA). В контексте сценария показатель SLA характеризовал целостность и доступность сервиса. SLA измерялся в процентах (0–100%). Команда теряла очки SLA, если на момент обращения чекера сервис оказывался недоступен или функционировал ненадлежащим образом. Обращения чекера к сервису могли происходить несколько раз за раунд, но количество обращений к каждой из команд всегда было одинаковым. Результирующее значение SLA высчитывалось как процентное соотношение удачных проверок (когда сервис доступен и полностью функционален) к общему количеству проверок. Чекер — механика, которая позволяла нам проверять, что сервисы участников функционируют должным образом. Поскольку игровой сервис имитировал реальное веб-приложение, чекер также использовался для проверки выполнения правил игры: участники не могли просто выключить сервис или отключить часть его функциональности, им нужно было защищаться от атак Red Team. Результирующее количество баллов, заработанных командой в ходе сценария, вычислялось как SLA * HP. Участникам давалось 30 минут на подготовку, в ходе которых они должны были ознакомиться с предоставленным им сервисом, развернуть средства мониторинга и защиты и начать искать уязвимости в коде сервиса. По истечении этого времени начиналась так называемая «активная фаза» сценария: Red Team приступала к атаке. Активная фаза состояла из 18 раундов продолжительностью в 5 минут каждый. Перед началом сценария каждая команда получала 180 HP для каждой из 5 заложенных в сервис уязвимостей (900 HP в сумме). За эксплуатацию уязвимости команда теряла 10 HP. Так, если в каком-то раунде было проэксплуатировано 3 уязвимости, за этот раунд команда теряла суммарно 30 HP, а если было проэксплуатировано 5 уязвимостей — 50 HP. Помимо проверки того факта, что сервис команды функционирует должным образом, чекер применялся, чтобы в начале каждого раунда доставить в сервис команды так называемый флаг (используя легитимную функциональность сервиса). Флаг — это строка формата “Polygon{JWT}”, где JWT — JSON Web Token. В контексте сценария флаг выступал в роли конфиденциальных данных: чем больше флагов смогла похитить Red Team, тем большей была утечка. Похищенный флаг также означал факт эксплуатации уязвимости: команда теряла очки HP именно тогда, когда Red Team получала флаг, эксплуатируя ту или иную уязвимость. Каждой команде, участвующей в учениях, мы предоставили виртуальный сервер под управлением ОС Linux. После подключения по VPN участники получали доступ к своему серверу посредством SSH, при этом участникам предоставлялся полный доступ (root) к своей системе. В домашней директории пользователя /home/cyberpolygon/ch4ng3org располагался игровой сервис участников. Бэкенд игрового сервиса был реализован на Ruby, фронтенд — с использованием фреймворка React JS, для управления базой данных была использована СУБД PostgreSQL. Сервис был предназначен для запуска в Docker, на что указывало, в частности, то, что в содержащей игровой сервис директории были расположены файлы Dockerfile и docker-compose.yml. Участники имели полный доступ к исходным кодам сервиса, файлам конфигурации и базе данных и могли использовать эти сведения для поиска и устранения присутствующих в сервисе уязвимостей.  Уязвимость класса «небезопасные прямые ссылки на объекты» (IDOR, Insecure Direct Object Reference) возникает из-за недостатков в механизмах авторизации. Уязвимость позволяет злоумышленнику получить доступ к данным других пользователей, к которым при нормальных условиях функционирования приложения у него не должно быть доступа. В игровом сервисе уязвимость присутствовала в методе get класса UsersController. backend/app/controllers/users_controller.rb: При обращении по адресу вида http://example.com/api/users/<USER_ID>, где USER_ID — числовой идентификатор пользователя, любой пользователь мог получить JSON-объект, содержащий числовой идентификатор и имя пользователя, соответствующее этому числовому идентификатору. Эта функциональность сама по себе не несет какой-либо опасности пользовательским данным. Однако следует обратить внимание на следующий фрагмент кода: Как можно увидеть, если передать в запросе параметр full, в ответе от сервера будет содержаться уже большее количество данных: помимо идентификатора и имени пользователя в ответе от сервиса будут возвращены еще его email и номер телефона. В игровом сервисе флаги хранились как раз в поле user.phone (это можно было обнаружить, например, анализируя сетевой трафик). Каждый раунд чекер создавал нескольких пользователей и в качестве номера телефона для одного из них сохранял флаг. Чтобы воспользоваться данным недостатком приложения, члены Red Team отправляли в сервис запросы вида http://example.com/api/users/<USER_ID>?full=1 и искали флаг в поле phone полученных JSON-объектов. Для защиты от этой уязвимости хорошей практикой считается маскирование конфиденциальных данных при их отображении пользователю. Так, номер телефона +71112223344 можно отображать как +7111*****44. Например: В таком случае вместо полного значения флага Red Team получала бы строку вида Polyg********X}, а команда участников не теряла бы очки HP из-за эксплуатации этой уязвимости. Внедрение команд ОС (Command Injection) происходит в результате недостаточной фильтрации пользовательских данных. Используя эту уязвимость, злоумышленник может формировать ввод, содержащий команды ОС, которые выполняются на целевой системе с привилегиями уязвимого приложения. В игровом сервисе уязвимость присутствовала в методе disk_stats класса StatsController. backend/app/controllers/stats_controller.rb: При обращении по адресу вида http://example.com/api/disk_stats в ответе сервиса в поле disk JSON-объекта возвращается вывод системной утилиты df, позволяющей оценить количество свободного пространства в файловой системе. В вызываемую команду, по задумке разработчика, можно передавать различные параметры, однако при этом их значение никак не фильтруется: Это означает, что потенциальный злоумышленник может выполнить практически любую команду в системе, используя специальный синтаксис командной строки. Так, например, выполнив запрос http://example.com/api/disk_stats?flags=;cat/etc/passwd, злоумышленник сможет прочитать содержимое системного файла /etc/passwd. Red Team эксплуатировала данный недостаток следующим образом: При помощи отправки запроса http://example.com/api/disk_stats?flags=>dev/null;cat config/secrets.yml Red Team получала содержимое файла backend/config/secrets.yml, в котором хранился приватный ключ для подписи JWT-токенов. Имея приватный ключ, Red Team могла сформировать и подписать себе валидный JWT-токен для любого пользователя. Поскольку Red Team использовала актуальный приватный ключ сервиса, данный токен был бы успешно провалидирован и принят приложением. При помощи отправки запроса http://example.com/api/me от лица пользователя, для которого был сгенерирован токен, Red Team получала номер телефона этого пользователя и проверяла, нет ли в нем флага. Чтобы защититься от этой уязвимости, достаточно было запретить передавать какие-либо параметры в вызов команды, поскольку общая работоспособность системы не завязана на использовании этого эндпоинта: Уязвимость небезопасной конфигурации (Security Misconfiguration) возникает, как правило, из-за человеческого фактора. Стандартные конфигурации приложений часто недостаточно ориентированы на безопасность. Из-за лени, недостатка внимания или некомпетентности обслуживающего персонала эти конфигурации порой остаются неадаптированными к суровым реалиям, что существенно сказывается на безопасности приложения. В игровом сервисе эта уязвимость присутствовала в описании сервиса db в файле docker-compose.yml. Как можно заметить, сетевой порт базы данных доступен из внешней сети: Кроме того, сервер базы данных использует в качестве имени базы данных, имени пользователя и пароля одну и ту же строку, к тому же совпадающую с именем сервиса ch4ng3.org. Обнаружив в результате сканирования сети порт базы данных, Red Team смогла подобрать логин и пароль к этой базе данных. После этого она выполнила следующий SQL-запрос, получив в результате сразу все номера пользовательских телефонов, в которых хранились флаги: Для защиты от этой уязвимости идеальным решением стал бы запрет на подключение к базе данных из внешней сети и смена пароля пользователя базы данных (при этом нужно было не забыть внести соответствующие изменения в конфигурацию сервиса api): Однако достаточно было предпринять одно действие из двух: сменить пароль пользователя базы данных на более безопасный либо запретить подключения к базе данных из внешней сети. Следующая заложенная в игровом сервисе уязвимость была связана со сменой алгоритма подписи JWT. В игровом сервисе уязвимость присутствовала в методе decode класса JsonWebToken. backend/app/lib/json_web_token.rb: Стоит более внимательно присмотреться к следующим строкам: Приложение подгружает строку с публичным ключом сервиса из файла конфигурации и, если в токене был передан алгоритм RS256, производит преобразование этой строки в публичный ключ RSA, который в дальнейшем используется для проверки подписи токена. Можно заметить, что, если в параметре algorithm передано любое другое значение, преобразования строки с публичным ключом не произойдет. Если передать в поле alg JWT значение HS256, то для проверки подписи токена будет использован симметричный алгоритм HMAC, и именно эта строка с публичным ключом будет использована в качестве ключа для проверки подписи токена. Red Team эксплуатировала данный недостаток следующим образом: При помощи отправки запроса http://example.com/api/auth/third_party Red Team получала публичный ключ сервиса из поля public_key полученного JSON-объекта. Имея публичный ключ, Red Team могла сформировать валидный JWT-токен для любого пользователя, передав в поле alg JWT значение HS256 и подписав токен, используя в качестве секрета для алгоритма HMAC строку, содержащую публичный ключ сервиса. При помощи отправки запроса http://example.com/api/me от лица пользователя, для которого был сгенерирован токен, Red Team получала номер телефона этого пользователя и проверяла, нет ли в нем флага. Чтобы защититься от этой уязвимости, можно было руководствоваться следующей рекомендацией: при работе с JWT желательно использовать одновременно только один алгоритм подписи — либо симметричный, либо асимметричный. Так, самое простое исправление будет выглядеть следующим образом: backend/app/lib/json_web_token.rb: Теперь, если передать в поле alg токена значение, отличное от RS256, токен будет помечен как невалидный и Red Team не сможет получить доступ к приложению от лица других пользователей, подписывая токены публичным ключом сервиса. Последняя заложенная в игровом сервисе уязвимость была связана с небезопасной десериализацией YAML. За импорт петиций через их описание в формате YAML отвечал метод import класса PetitionsController. backend/app/controllers/petitions_controller.rb: Особое внимание стоило уделить следующим строкам кода: Как можно заметить, содержимое YAML-объекта берется из base64-кодированного параметра petition, после чего преобразуется в объекты языка Ruby конструкцией YAML.load(yaml). Данная конструкция является небезопасной и позволяет, в том числе, выполнить на целевой системе произвольный код на языке Ruby в контексте уязвимого приложения, чем и пользовалась Red Team. При помощи следующего скрипта был сгенерирован YAML-объект, эксплуатирующий данный недостаток: В качестве полезной нагрузки был использован следующий код: Код получал номера телефонов всех зарегистрированных в сервисе пользователей, объединял их друг с другом через «;» и при помощи конструкции raise вызывал исключение, передавая в качестве сообщения об ошибке строку, содержащую номера телефонов пользователей. Сообщение об ошибке далее возвращалось сервером в поле JSON-объекта message вместе с кодом ответа 500. При получении такого ответа Red Team оставалось только найти флаг в сообщении об ошибке. Чтобы защититься от данной уязвимости, достаточно было заменить вызов функции YAML.load(yaml) на вызов функции YAML.safe_load(yaml). Однако чекер в процессе проверки функциональности проверял, чтобы в переданном YAML-объекте было возможно использовать алиасы. Поэтому результирующая конструкция будет выглядеть примерно так: YAML.safe_load(yaml, aliases: true). А результирующая безопасная функция — так: Итак, мы рассмотрели уязвимости, заложенные в игровом сервисе Defence-сценария тренинга Cyber Polygon, разобрали способы их эксплуатации и привели примеры исправлений, которые позволили бы участникам защитить свой сервис от атак Red Team. Конечно, наши варианты устранения уязвимостей не единственно верные. Мы просто перечислили те способы, которыми воспользовались бы сами. Сценарий предусматривал, что участники могут защититься, не исправляя код в своих игровых сервисах. Например, для защиты от третьей уязвимости Security Misconfiguration, связанной с небезопасной конфигурацией Docker, достаточно было заблокировать порт базы данных на файрволе. Однако мы убеждены, что лучшее решение — исправлять недостатки сервисов и приложений, а не «прикрывать» их с помощью компенсационных мер защиты, которые рано или поздно атакующий может обойти. Вот почему мы подробно рассмотрели корректировку исходного кода для защиты от уязвимостей. Если вы участвовали в Cyber Polygon, напишите, что оказалось самым полезным для вас.