2022/03/10 11:09:09

Анализ кода: проблемы, решения, перспективы

Уязвимости в программном обеспечении были, есть и будут одними из основных ворот, через которые злоумышленники реализуют свои атаки. Поэтому уже не первый год в тренде так называемая безопасная разработка – все больше вендоров уделяют внимание выявлению и устранению уязвимостей на этапе создания программных продуктов. Один из главных инструментов для этого – анализ кода на наличие уязвимостей и закладок. Даниил Чернов, директор Центра Solar appScreener компании «Ростелеком-Солар», рассказал о том, какие технологии анализа кода сейчас наиболее востребованы, в каком направлении они развиваются, какие изменения могут произойти в этой области в ближайшем и далеком будущем.

Содержание

Динамический, статический и бинарный анализ

Сегодня есть две наиболее зрелые и востребованные технологии анализа кода – динамический анализ (DAST – Dynamic Application Security Testing) и статический анализ (SAST – Static Application Security Testing). Разновидностью SAST является бинарный анализ.

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

Статический анализ – метод «Белого ящика» – представляет собой тип тестирования, при котором, в отличие от динамического анализа, не происходит выполнения программы, а анализируется весь ее код. В результате мы имеем нахождение большего количества уязвимостей. Также метод выгодно отличает возможность внедрения в начальные стадии разработки: чем раньше обнаруживаем уязвимость, тем дешевле обходится ее устранение.

У метода есть два недостатка. Первый – наличие ложноположительных срабатываний. Как следствие – необходимость оценки, что перед тобой реальная уязвимость или ошибка сканера. Второй недостаток – необходимость наличия исходного кода программы, который не всегда доступен. В последнем случае помогает бинарный анализ – он позволяет с помощью технологий реверс-инжиниринга провести статический анализ кода, даже если у нас нет исходника. Когда в 2015 году мы выводили на рынок анализатор кода приложений Solar appScreener, эта функциональность уже была заложена в системе. Мы видели тогда и видим сейчас, что в реальной жизни это зачастую единственная возможность грамотно и полноценно выявить уязвимости в приложениях.

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

Альтернативный вариант – прогнать приложение через динамический анализатор: перед нами черный ящик, мы не можем его открыть, но можем на него воздействовать – «пнуть, поднять, потрясти, уронить». И по результатам этого воздействия сделать выводы. При динамическом анализе на вход приложения подаются разные данные, в текстовые поля вводятся различные последовательности, в случае с веб-сайтом посылаются разнообразные команды, протоколы, пакеты. И по ответу от приложения мы делаем вывод, есть ли в нем уязвимости. DAST действительно хорош для веб-приложений, потому что можно повоздействовать на систему, защищенную при этом файрволом.

Однако DAST помогает обнаружить только определенный спектр уязвимостей. А, например, угрозы вроде временной бомбы, которая по триггеру времени запустит в систему вредонос, скрытую «учетку», небезопасный пароль и прочее динамическим анализом не обнаружить. Бинарный анализ же делает из черного ящика белый, позволяя в ходе статического анализа увидеть его содержимое.

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

True or False: как решить главную проблему автоматизированного анализа кода?

Ложные срабатывания – проблема любых систем, которые автоматически что-то анализируют и выдают результат. Очевидно, что она актуальна и для анализаторов кода. Чем больше ложных срабатываний, тем дороже использование инструмента для пользователя. Ведь на верификацию тратятся человеческие ресурсы – время.

Сегодня есть несколько путей развития технологий для минимизации ложных срабатываний. Один из самых хайповых – применение искусственного интеллекта, а точнее его подраздела – машинного обучения (МО). Какими способами можно искать уязвимости в коде с помощью МО?

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

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

Во-вторых, можно не готовить примеры вручную, а обучать МО-анализатор на основе больших объемов открытого исходного кода. Можно отслеживать историю коммитов на GitHub и выявлять закономерности изменения или исправления программного кода. Однако проблема в том, что правки на GitHub вносятся довольно хаотично: например, пользователь не хочет тратить время на внесение каждой правки в отдельности – и он где-то вносит пару изменения, а где-то просто переписывает кусок кода. Тогда даже человек не сможет разобраться, связаны ли эти исправленные ошибки между собой.

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

Таким образом, в целом использование ИИ для анализа кода – очень трудозатратная и недешевая история при неочевидном результате.

Но есть и другой метод борьбы с ложными срабатываниями – математический.

Нечеткая логика: как это работает

Нечеткая логика – относительно новый раздел математики. Он является обобщением классической логики и теории множеств и базируется на понятии нечеткого множества, впервые введенного в 1965 году. Если в обычной логике у нас есть true или false, 0 или 1, то нечеткая логика оперирует градациями между true и false. Она может сказать, что событие верно на столько-то процентов и на столько-то неверно, – и это ближе к мышлению человека.

Представьте, что у вас есть стакан воды и нужно сказать, что вода холодная, если ее температура ниже 15 градусов, и теплая, если выше 15 градусов. Мы опускаем палец в стакан (допустим, там 16 градусов, но мы об этом не знаем). И мы не скажем: «Да, она точно теплая». На пограничных значениях мы будем внутренне колебаться: вроде теплая, но скорее холодная. Нечеткая логика помогает нам уйти от линейного мышления.

В обычном линейном мышлении, когда систему научили принимать решение, что это true или false, при выставлении математического порога (допустим, мы хотим меньше ложных срабатываний) приходится устанавливать для машины жёсткие фильтры принятия решений. Тогда она начнет выдавать меньше ложных срабатываний, но при этом начнет пропускать реальные уязвимости. Двигаем линейный фильтр в обратную сторону – машина перестает пропускать уязвимости, но при этом ложных срабатываний становится очень много.

Поэтому в своей системе мы реализовали механизм борьбы с уязвимостями на основе математического аппарата нечеткой логики – Fuzzy Logic Engine (FLE), который позволяет тонко настраивать линейные фильтры, балансируя между снижением ложных срабатываний и потерей точности выявления уязвимостей. Фильтры в системе позволяют, например, отображать лишь те уязвимости, в которых FLE полностью уверен. Кроме того, можно практически ювелирно настроить уровень уверенности системы в наличии уязвимости. С помощью шкалы Confidence можно, например, задать для уязвимостей критичного уровня более жесткие критерии, по которым система будет относить потенциальные уязвимости к реальным.

Для уязвимостей средней и низкой критичности можно задать более мягкие критерии оценки.

Чем больше языков, тем лучше

Языков программирования становится все больше, многие из новых быстро набирают популярность. Например, язык Rust, который позиционируется как безопасная замена С++ и сегодня часто используется для написания десктопных приложений и бэкэндов. Или Go (Golang), применяемый для создания высоконагруженных сервисов – площадок онлайн-торговли, ДБО, мессенджеры и т.п. Dart, предназначенный для написания веб и мобильных клиентов.

И нередко бывает так, что какая-то часть системы написана на языке, который можно проверить не всеми инструментами. Поэтому, если мы хотим, чтобы анализ кода был эффективным и удобным, важно, чтобы все языки были на борту. Пользователь не должен задаваться вопросом, на каких языках написано приложение, все ли языки поддерживает анализатор. Код должен загружаться в анализатор, который сам определит, какие языки содержатся в приложении, и все их проверит, ничего не пропуская. Именно по такому принципу мы реализуем Solar appScreener: в апреле прошлого года добавили поддержку Dart и продолжаем лидировать по количеству поддерживаемых языков – сейчас их 36.

Мы довольно гибко подходим к управлению командой и разработке новых фич. Некоторые вендоры не могут просто взять и быстро добавить новый язык в свой анализатор, потому что им нужно просчитать выгоду от реализации той или иной функции на других рынках. Много ли запросов на нее от наиболее приоритетных рынков, или лучше потратить ресурсы команды на создание другой функциональности? Это может отнимать много времени.

Технологии на перспективу

Ну а что же там, за горизонтом? Какие технологии могут быть реализованы в анализаторах кода уже в ближайшем будущем? Сейчас на рынке анализа кода наблюдается растущий спрос на бинарный анализ сложных систем. Допустим, компания покупает софт стороннего вендора, устанавливает его в свою критическую информационную инфраструктуру, и ИБ-специалисты хотят понимать реальный уровень защищенности той или иной системы. В чем здесь отличие от уже существующих технологий? Современный бинарный анализ позволяет загрузить в анализатор и проанализировать лишь каждый исполняемый файл в отдельности. В случае со сложной многокомпонентной системой нужно отправить в анализатор целый зоопарк различных файлов, связанных друг с другом, – инсталляционные файлы, готовые файлы системы, библиотеки, исполняемые файлы и т.п. Это не просто файлы, а единое ПО, которое состоит из разных логических компонентов. Поэтому тут нужен сложнейший анализ, который очень тяжело реализовать. Пока подобная функция является экзотикой.

Весьма реалистичной выглядит перспектива применения интегрированных систем, в которых используются преимущества сразу всех передовых методов анализа – DAST, SAST, IAST, mAST, SCA (Software Composition Analysis). Последний представляет собой инвентаризацию библиотек, кода, предоставленного на анализ, и его сверку с базой данных, включающей перечень библиотек и содержащихся в них уязвимостей. Однако пока нет вендора, который свел бы все эти компоненты воедино, а отчеты, предоставляемые каждой системой, настолько разнятся, что привести их к общему знаменателю нереально. Поэтому создание интегрированной системы, которая объединит возможности разных видов анализа кода, с единой системой отчетности, выглядит многообещающе.

Стоит отметить и еще одну довольно занятную технологию для защиты приложений от уязвимостей – RASP (Runtime application self-protection). О ее перспективности однажды заявлял Gartner, однако пока технология самозащиты приложений кажется малоэффективной. Некоторые вендоры уже пытались ее реализовать, однако работает она только в частных случаях и применима далеко не ко всем языкам программирования. Её суть состоит в том, что в само защищаемое ПО добавляется в код, который позволяет приложению понимать, когда его атакуют, и блокировать атаку. Проблема кроется в том, что архитектура веб-приложений очень разная, поэтому такой подход к защите применим либо при индивидуальной разработке под каждое приложение и каждую новую его версию, либо в случае, если RASP-система допускает очень глубокую кастомизацию. Чтобы говорить о RASP как о полноценной замене анализу кода веб-приложений, нужно сделать определенные шаги в развитии технологий в целом и искусственного интеллекта в частности.

P.S. А если вдруг?..

А что, если придет квантовый компьютер и настанет эра квантовых вычислений – как в таком случае защититься, например, от уязвимостей, проэксплуатировать которые можно методом перебора? Вот эти все недостаточные размеры ключа шифрования, слабые алгоритмы хеширования, заданная в исходном коде соль и т.п.? Если такие технологии придут в массовое использование, то все, что сейчас считается безопасным – сложные пароли, алгоритмы шифрования – будет взламываться налету. Очевидно, необходимо будет разрабатывать совершенно другие подходы и к статическому анализу кода, и к динамическому. Как это будет, пока неизвестно, но очень интересно.