Проактивный поиск уязвимостей в исходном коде 25.10.2021 00:00
В настоящее время можно выделить более пятисот видов уязвимостей, встречающихся в исходном коде ПО. Это различные инъекции (“внедрения”), возникающие при слабой параметризации и валидации запросов, проблемы с шифрованием, утечка памяти, межсайтовый скриптинг и многие другие проблемы, способные создать серьезные неприятности тогда, когда вы будете меньше всего к этому готовы.
Автор: Владимир Кислицин, технический директор (CTO) Finsight Ventures
В последнее время при разработке особенно остро встают проблемы архитектуры, когда ошибки допускаются при написании кода, а при проектировании приложения. Примером могут служить хранение открытых паролей в сессиях и базах данных, возможность просматривать серверные каталоги за пределами дозволенного или отсутствие контроля доступа по списку адресов и др.
При возникновении брешей в безопасности и их эксплуатации злоумышленниками несправедливо будет обвинять лишь изъяны в исходном коде. Немаловажную, а иногда и главную роль в обеспечении надежности играет сетевая, серверная безопасность. Поэтому следует уделять внимание и качеству разработки, и защите инфраструктуры.
Чем грозит использование уязвимости, обнаруженной злоумышленниками?
На этот вопрос можно отвечать бесконечно долго и развернуто, но в двух словах: ничем хорошим, чаще – очень плохим. В конечном счете все закончится убытками, а также тяжелыми судебными процессами с клиентами, партнерами или регулирующими органами.
Компрометация защищенных данных, нарушение финансовых потоков, ненужные звонки и СМС, нарушение режима работы оборудования и вывод его из строя – очень серьезные, но далеко не самые опасные проблемы. Если абстрагироваться от корпоративных рисков и вспомнить, с какой скоростью набирает обороты использование IoT, то несложно представить, к чему может получить доступ запущенный злоумышленником бот, достучавшись до вашей камеры или микрофона.
Особенности организации проактивной разработки
Быть проактивным – значит действовать сейчас для того, чтобы предотвратить проблемы в будущем. Если говорить о разработке, это означает писать код и строить архитектуру так, чтобы в будущем избежать неприятных сюрпризов.
Именно проактивность часто становится большим препятствием на пути эффективного взаимодействия ИТ и бизнеса, ведь она влечет:
-
увеличение времени разработки, как следствие – ее стоимости;
-
повышение затрат на специалистов, понимающих принципы безопасной разработки и умеющих ее сопровождать;
-
повышение требований к четкости и слаженности процессов разработки, то есть в компании должны быть специалисты, создающие и поддерживающие эти процессы.
Данные факторы рассматриваются в многочисленных компаниях как "избыточная" разработка, а написание кода в них начинает строиться по неправильной траектории: поверхностное ТЗ – беглый анализ – быстрая разработка – быстрое тестирование (иногда и вовсе без этого шага) – релиз – патчинг.
Это порочный круг. В таких условиях практически любой задаче, решаемой дополнением кода в ПО, потребуется дальнейшая поддержка. В этом случае будет удачей, если между очередным патчем и новой проблемой не случится крупномасштабного инцидента (инъекции, утечки данных, накрутки исходящих звонков и т.д.).
В организации процессов разработки не должно быть компромиссов
В настоящее время бизнес все чаще основан на ИТ, и вполне целесообразно рассматривать разработку как самолет, на котором летит вся компания. В этой аналогии можно поставить вопрос таким образом: хотели бы вы искать компромиссы в безопасности полета? Думаю, ответ очевиден.
К сожалению, на практике далеко не каждая компания имеет правильное представление и ресурсы для создания и поддержки нормального процесса проактивной разработки. А если ресурсы появляются, то не очень понятно, с чего и как начинать.
Давайте попробуем разобраться с самого начала.
Безопасная разработка на старте
Если вы только стартуете и находитесь на этапе формирования процессов и архитектуры, как полностью с нуля, так и, возможно, начинаете новый проект на имеющейся инфраструктуре, то важнейшими факторами для проактивной безопасности будут бизнес-процессы разработки и ключевые этапы жизненного цикла любой задачи.
Здесь неважно, по какой методологии вы будете работать, но важно соблюдать стандартные требования, в том числе:
-
Таск-трекинг. Желательно с возможностью формирования бизнес-процесса с разделением на подпроекты (Jira, YouTrack, Basecamp). Особенно важен момент именно с бизнес-процессом, так как при увеличении объема задач, как правило, постановка и анализ часто становятся поверхностными, а этапы уточнения или тестирования и вовсе игнорируются. Правильный жизненный цикл задачи не допустит реализации какого-либо шага без участия ответственного.
-
Жизненный цикл задачи. В качестве примера возьмем финтех-компанию средних объемов с командой разработки из 12 человек (Java/Angular), в которой он может выглядеть так: идея – анализ – сбор требований (формирование ТЗ) – определение исполнителя и сроков – согласование – разработка – внутреннее тестирование – внешнее тестирование – предрелизное тестирование – релиз – поддержка. Если у читателя возникает вопрос о том, где же здесь проактивность в безопасности разработки, то ответ довольно прост: от качества постановки задачи зависит большая часть ее дальнейшей жизни. Именно при анализе и сборе информации должны закладываться первоначальные требования к безопасности.
-
Назначение ответственных за каждый этап. Целью является не поиск виноватых, а четкое понимание задач и результатов, которых ждут коллеги. То есть при хорошо поставленном процессе для реализации конкретной задачи, помимо программистов, понадобится тестировщик – бизнес-аналитик (методолог), ответственный за задачу со стороны тех, кто ее поставил (эдакий локальный Product Owner). Дополнительным плюсом будет наличие в команде технического писателя и системного аналитика вкупе с архитектором для выстраивания правильных связей с уже имеющимся функционалом.
-
Требования непосредственно к разработке. Паттерн разработки, Code-Style, написание юнит-тестов (до или после написания основного кода), правила работы с контролем версий (правила ветвления и подтверждения изменений), Code Review и т.д. – практически все пункты являются залогом проактивной безопасности и, к сожалению, в полном комплекте используются только в 20–30% компаний.
Стоит упомянуть, что львиной доли проблем в будущем можно избежать, если имеются подробные требования при постановке задачи, включая технический анализ, и хорошо знающий ваше ПО разработчик-техлид, выполняющий Code Review (рецензирование кода) при Merge Request. Организация и осуществление процесса Code Review должны проходить в соответствии с устоявшимся в компании стилем и требованиями, заложенными техническим директором или техлидами команд. Бывали случаи, когда в компании код не принимался для слияния, если не был прокрыт тестами или хоть немного не соответствовал общей стилистике. Здесь выбор за вами – компромиссы или четкое соответствие всем требованиям.
Проактивное устранение и предотвращение уязвимостей в коде особенно актуально на старте проекта или задачи. Со временем, начиная от постановки ТЗ и заканчивая сопровождением кода, вышедшего в релиз, и, соответственно, работой готового модуля, использование злоумышленниками потенциальных дыр в безопасности будет стоить дороже, а проактивность на этапах поддержки может и вовсе не сработать.
Проактивная поддержка кода
Рассказывать о том, как писать безопасный код, можно очень долго. И здесь нужно говорить не только про контроль утечек памяти, строгую типизацию, контроль доступа, правильную валидацию и правильный возврат ожидаемых данных. Такие вещи, как стиль кода, комментирование и документирование, имеют очень большое значение в безопасном сопровождении кода.
Очень важно обязательное покрытие тестами ключевого функционала. Именно ключевого, потому что на практике бывают задачи, написание юнит-тестов к методам которых действительно может являться избыточной разработкой. Например, если ваша функция возвращает true или false в зависимости от типа единственного аргумента, то такому методу действительно может не требоваться проверка тестами. А методы авторизации, сравнения, обработки входящих данных крайне важно покрывать юнит-тестами для того, чтобы впоследствии при сборке приложения не было проблем с ключевыми местами, на которые со временем будет обращаться все меньше внимания. На самом деле написание тестов не поздно внедрять на любом этапе разработки, даже если вашему ПО уже декада лет и ни один метод в нем до сих пор ими не покрыт. Просто начинайте планировать разработку немного иначе и вводите новые требования для технических специалистов. Если, конечно, остро не стоит вопрос тотального рефакторинга – в этом случае можно вернуться к описанному в предыдущей части статьи.
Интеграционное тестирование
Интеграционное тестирование – это более сложный и комплексный подход к проверке всего приложения путем написания тестов к связке модулей и классов, то есть проверка взаимодействия различных компонентов ПО. В случае необходимости внедрения такого функционала нужно понимать, что данную область будет очень сложно "повесить" на разработчиков (как в случае с юнит-тестированием) и что эффективнее иметь ведущих специалистов именно по тестированию вашего ПО для своевременного внедрения, расширения и изменения библиотек.
Системное (функциональное) тестирование
Системное (функциональное) тестирование, которое в небольших проектах с успехом заменяется ручным, является эффективным инструментом для поиска большого числа уязвимостей, равно как и ошибок работы приложения, и предлагается в виде готовых решений, повсеместно использующихся в отделах тестирования по всему миру, – Selenium, HPE UFT и др.
Статический и динамический анализ кода
Дополнительными инструментами, повышающими качество кода и в некоторых случаях существенно снижающими риск уязвимостей, станут статические и динамические анализаторы кода. Это разные по назначению и процессу исполнения проверок программы, направленные, в сущности, на одно и то же – предотвращение и поиск уже имеющихся ошибок, то есть повышение надежности и безопасности разрабатываемого функционала.
Различий между ними много, но основное в том, что статические анализаторы проверяют код до его исполнения (компиляции), а динамические – проверяют работающую программу на предмет используемых ресурсов, утечек памяти, определения ряда других уязвимостей.
Статический анализатор можно представлять как автоматизированный процесс Code Review, с более эффективным проникновением (полный анализ всего кода, включая "мертвый"), но с отсутствующим прогнозом (не всегда такой анализ может увидеть утечку памяти, например), а также с частыми случаями нахождения ошибок вида false-positive – "подозрительных" мест, наличие или отсутствие ошибки в которых может точно определить только программист. Для разработчика, использующего, например, привычную для всех IDEA (и большинство других современных IDE), статический анализатор уже включен в базовый функционал среды разработки. В последнее время этот модуль значительно увеличил свою эффективность и как локальное решение (для конкретного разработчика) покрывает большую часть процесса анализа кода. Существенная сложность может заключаться в том, что анализатор IDE практически невозможно имплементировать в процесс непрерывной интеграции – CI/CD, а также в сбор статистики по анализу в разрезе отдела или департамента. Для таких задач подойдут standalone-решения: SonarQube, PMD, Checkstyle.
Использование динамических анализаторов (таких как Valgrind, DynamoRIO и подобных) на практике встречается намного реже, скорее всего, по причине избыточности для большинства разрабатываемых приложений. В веб-приложениях динамический анализ полностью перекрывается функциональным тестированием, логами веб-окружения и профилированием с отладкой в IDE. Средства динамического анализа оправданны в конвейерной разработке крупных многопоточных приложений в больших количествах. Основное "направление" таких анализаторов – мониторинг и контроль утечек памяти в приложениях (особенно актуально при разработке на C/C++).
Немного об угрозах
В рамках комплексной безопасности программного обеспечения, несомненно, нужно озадачиваться обеспечением защиты сети и баз данных, расположенных как на локальных серверах, так и удаленно (колокейшн или облако).
Например, при осуществлении DDoS-атак для злоумышленников главное значение будет иметь конфигурация сетевого окружения, а также настройка фильтрации трафика и межсетевых экранов, а не то, как написан код.
Большинство угроз, обычно инициированных извне, но осуществляющих свою деятельность уже внутри вашей сети (выполнение серверных команд, размещение файлов, подключаемых и исполняемых вашим ПО), также предотвращаются настройкой сети и дополнительных инструментов, включая фильтры контента и спама, гео-фильтрацию, сетевые антивирусы и различные системы предотвращения вторжений (IDS/IPS).
Но не стоит забывать и про угрозы, которые могут приходить изнутри, – закладки в коде, считывание паролей сетевыми кейлоггерами, перенос на флешку дампа "боевой" базы данных и вынос ее за пределы компании с последующей продажей конкурентам или использованием в других целях. Меры, предпринимаемые для защиты от подобных атак, сильно варьируются в зависимости от специфики компании, ее культуры, внутренних процессов и имеющихся ресурсов.
Учитывая скорость развития технологий, а также растущее количество внешних и внутренних угроз безопасности, совсем не лишним будет пересмотр подходов к процессам разработки, формированию ИТ-отделов и главных целей, которые преследуют команды в компании. Все это – подходы, процессы, инфраструктура разработки – должно быть пересмотрено с точки зрения безопасности. Быстрота работы приложения, удобство его интерфейсов отходят на второй план, когда дело касается утечки личной или корпоративной информации, а также финансового и репутационного ущерба.
Если сегодня вы пересмотрите подход к разработке ПО в вашей компании, это будет главный вклад в проактивное устранение уязвимостей в будущем.
Какой инструмент выбрать? У каждого своя правда
Дарья Орешкина, директор по развитию бизнеса компании Web ControlНаписать приложение, которое будет работать в момент сдачи релиза, и наладить производство программного обеспечения, которое будет надежно эксплуатироваться в запланированные сроки, – две существенно разные задачи. Повышение качества кода и всевозможные его проверки требуют финансовых ресурсов, затрат времени, наличия компетенций, а также налаженного взаимодействия между командами разработки, информационной безопасности, юристами и, конечно, бизнесом.Не получится навязать разработчикам неудобный инструмент, он быстро окажется на свалке, несмотря на свою важность для задач других заинтересованных подразделений. Если же новый инструмент будет помогать достигать ключевых показателей производства продукта, то разработчики станут союзниками.Безопасники заинтересованы в том, чтобы инструменты защиты реально использовались и при этом были достаточно точны, чтобы из-за ложных срабатываний не сдвигались сроки релизов.Юристам приходится отслеживать лицензионную чистоту продукта как в части применяемых компонентов Open Source, так и в части внутреннего заимствования кода внутри группы компаний и между различными проектами. Юристам крайне необходима автоматизация ручных операций по отслеживанию лицензионных соглашений компонентов и выявлению случаев неправомерного заимствования исходных кодов.Бизнес-подразделениям, в свою очередь, важно достигать стратегических целей и иметь полную информацию о текущем состоянии разработки для своевременной реакции в условиях быстро меняющейся ситуации. Важно, чтобы внедряемый инструмент помогал каждой вовлеченной в процесс команде достигать своих ключевых показателей, не мешал и не тормозил работу, удобно встраивался в конвейер разработки и автоматизировал рутину.