По роду своей деятельности в последнее время сталкиваюсь с различными текстами, в которых прямо или с намеками обсуждаются преимущества тех или иных продуктов или подходов над другими. Речь идет о задаче статического анализа исходного кода с целью поиска недостатков, снижающих защищенность программного продукта (т.е. речь не идет о недостатках качества кода в общем смысле слова).
Так как я человек впечатлительный и легко поддающийся на троллинг, решил написать своё ответное мнение в серии постов на тему “Типичный булшит в якобы аналитических заметках про технологии анализа исходного кода”.
Итак, сегодня – первая серия, и поговорим мы вот про какие заявления (встречаются в произвольной комбинации): “сигнатурный анализ не может обеспечить полноту”, “сигнатурный анализ дает много ложных срабатываний”, “сигнатурный анализ хуже, чем анализ потоков данных”, “анализ потоков данных дает офигительную полноту при низком числе ложных срабатываний”, “о какой полноте может идти речь при наличии в базе N сигнатур” и вот это вот всё.
Для тех, кто испугался объема, сразу выводы:
1. Все применяемые сейчас вида анализа кода – сигнатурные (в т.ч. taint анализ на основе потоков данных).
2. В свете п.1. сравнивать подходы и продукты по количеству сигнатур – нонсенс. Сигнатура может описывать как целый класс недостатков (например, input validation), так и один-единственный экземпляр (unserialize($_GET[.*])).
3. Про полноту и точность можно говорить только после фиксирования класса недостатков (например, input validation). Полноты “вообще” – не существует. В свете разговора про каждый класс недостатков на первое место выходит формальная модель описания недостатка. По факту, сейчас почти всегда используется модель невмешательства (aka taint). Данная модель описывает только уязвимости класса input validation. Она бесполезна для недостатков авторизации. Полнота, говорите?
4. Даже в рамках одного класса недостатков, например, input validation, существующая модель невмешательства не может обеспечить не только полноту, но и даже приемлемую точность. Ограничения модели невмешательства расписаны вот тут (англ).
Итак, начнем.
В задаче анализа исходного кода просто необходимо выделять существенные составляющие.
Первая ключевая составляющая – это представление программы, на котором происходит анализ. Типичные представления будут такими: текст программы, поток лексем, дерево синтаксического разбора (AST), граф потоков управления (CFG), графы зависимостей по данным (SDG, PDG), набор состояний (например, значений переменных) в каждой точке, собранный бинарник.
Вторая ключевая составляющая – модель недостатка или то, как формулируется искомое свойство в терминах выбранного представления программы. Кому интересно, может почитать подробнее про это вот тут .
Первый вывод: статический анализ на основе потоков данных – тоже сигнатурный (упс!!!). Действительно, для поиска недостатка необходимо специфицировать, какой подграф в графе программы надо найти. Для модели Non Interference (aka taint) это будет граф зависимости вида <получение данных от пользователя> -> <использование в критичной функции без предварительной обработки>.
Второй вывод: “о какой полноте может идти речи при наличии в базе N сигнатур” – бред с той точки зрения, что одна сигнатура может описывать как целый класс недостатков, так и вполне конкретный экземпляр недостатка. Соответственно, сравнение подходов и продуктов по количеству сигнатур – нонсенс.
На этом месте справедливо было бы поставить такой вопрос: хорошо, но может представление прямо или косвенно влияет на выразительную силу сигнатур, которые для него создаются? Т.е., например, в представлении потока лексем нет понятия “зависимость”, а следовательно нельзя формулировать и правила в терминах зависимостей; напротив, понятие “зависимость” – основа для представлений класса System или Program Dependence Graph и там правила, использующие это понятие, будут работать.
В итоге, логично предположить, что доступные концепты для формулирования недостатков повлияют на полноту анализа. И, что характерно, все так и есть. Однако, многие забывают о важнейшем факте: при построении представления более высокого уровня абстракции делаются некоторые предположения и обобщения, которые прямо влияют на точность описания свойств программы в этом самом представлении. Т.е. свойства для поиска еще не сформулированы, анализ еще не начат, а точность уже потерялась.
Теперь давайте разберем этап формулирования искомых свойств. Логично, что недостатки объединяются в классы по схожести их проявления в коде. Соответственно, для класса схожих недостатков необходимо сформулировать его модельное описание. Напомню, что про модели недостатков написано вот тут . Перейдем к рассуждения про полноту и точность.
[Manipulation mode on]
Любому здравомыслящему человеку должно быть очевидно, что полнота поиска уязвимостей вообще – бесполезнейшая характеристика, которую к тому же невозможно измерить. [Manipulation mode off]
В общем, меряться полнотой и точностью можно только в рамках зафиксированного класса недостатков. Например, в рамках класса input validation, или более гранулярного – sql injection, или класса другой категории – authorization flaws или memory leakage в контексте DoS. Давайте же зафиксируем класс и померяемся полнотой и точностью.
В посыле поста звучало про анализ потоков данных (“анализ потоков данных дает офигительную полноту при низком числе ложных срабатываний”). Хорошо, зафиксируем класс недостатков input validation. Учитывая, что для описания этого класса используется модель невмешательства (aka taint), которая сформулирована в терминах зависимостей по данным, то недостатки этого класса просто созданы для анализа потоков данных! Ура! Полнота! Точность! Да, но нет.
Убыль в точности я отметил уже на этапе построения представления. Другая убыль в точности – это ограничения модели невмешателсьтва (подробности тут ). Плюс, не забываем про убыль в полноте за счет невоможности поиска межмодульных уязвимостей (stored XSS, second order sql injection). Конечно, всегда, можно скатиться в 100% полноту, пометив все строчки недостатками, но этот вариант мы не рассматриваем.
В отличие от ортодоксального научного подхода к поиску недостатков класса input validation, есть еще рабоче-крестьянский, который практикуют многие аудиторы кода – регулярные выражения и grep. Тут кто-то должен сказать про работу из коробки vs необходимость эксперта. Да, но это уже тема совершенно другого поста. Если же говорить про сравнение по точности, регулярные выражения и grep очевидно способны зарулить подход на основе модели невмешательства и анализа потоков данных. В общем, как говорят у нас в МГИМО – for whom how.