Почему фолзят SAST’ы? Часть 1
Не в обиду, но в шутку — всякий раз, когда разработчики, впервые столкнувшиеся с реалиями SAST, приходят с возгласами «боже мой, 10к сработок!», «второй час анализируется!!», «ой, фолз, смотрите, тут ФОЛЗ!!», поневоле вспоминается тот мем про белок-истеричек 😍
Позвольте объяснить…
Зайду, как водится, с козырей. Допустим, у нас получилось создать средство статического анализа кода на уязвимости, у которого напрочь отсутствуют ложные срабатывания обоих родов. Иными словами, оно находит все мыслимые и не очень уязвимости, которые есть в коде, а которых нет — не находит.
Пишем что-то вот такое, и отдаем на анализ:
for n in count(3):
for x in count():
for y in count():
for z in count():
if x**n + y**n == z**n:
eval(request.param1) # RCE
Потренировавшись на кошках, и убедившись в неуязвимости этого кода, меняем Python-формулировку Великой теоремы Ферма на каждую из нерешенных математических проблем, и рубим бабло с математических призовых фондов так, как багхантерам и не снилось.
Что-то тут УЖЕ не так, не находите?
Другой пример. Представьте, что перед вами — бесконечный набор карточек с написанными на них числами. Вам нужно найти среди них какое-то одно, допустим — 23. Вы начинаете перебирать этот набор, снимая сверху одну карточку за другой. Перебираете, перебираете, а числа 23 всё нет и нет. Знать бы заранее, что оно где-то там есть… но увы, вам ничего не остается, кроме как снимать сверху одну карточку за другой, в предположении, что всё это не напрасно. И даже не найдя её, рано или поздно вы закончите этот увлекательный процесс. Ну, или передадите это гиблое дело детям и внукам. Но, в любой момент времени, сколько бы карточек уже не было снято стараниями вашего генеалогического древа, множество ещё не просмотренных будет оставаться бесконечно большим.
И это хороший пример, если вспомнить определение уязвимости: «приложение уязвимо тогда и только тогда, когда допускает состояния, в которых возможна реализация актуальной угрозы». Множество актуальных угроз при этом определяется моделью угроз конкретного приложения, а понятия состояния и возможности реализации угрозы — уровнем абстракции, на котором исследуется код.
Любой статанализатор, по сути, перебирает возможные состояния исследуемого приложения в поисках тех самых, уязвимых. Ищет карточку с нужным числом. Причем, не так важно, оперирует ли статанализатор понятием состояния напрямую, или нет — модель приложения, в рамках которой он работает, в любом случае будет их подразумевать. Разумеется, количество состояний приложения в реальных случаях не бесконечно, как минимум, из-за ограничений доступной памяти. Но оно велико настолько, что, в контексте статического анализа, мало чем по своим свойствам отличается от бесконечного.
Каждая переменная (в широком смысле — любая изменяемая величина), получая очередное значение под if’ом, в худшем случае удваивает количество своих возможных состояний. Каждая итерация цикла или рекурсии, по сути — выполнение кода под if’ом. Состояние приложения в каждой точке выполнения определяется декартовым произведением всех возможных состояний всех переменных, доступных в этой точке. А ведь есть ещё окружение приложения, со своими состояниями…
Вряд ли кому-то нужен анализатор, наблюдение за прогресс-баром которого, будет передаваться из поколения в поколение. Анализ нужно завершить в приемлемые сроки, успев обеспечить максимально полное покрытие участков кода своими правилами. И вариантов достижения этого не так уж и много.
Продолжение в следующем посте...
Не в обиду, но в шутку — всякий раз, когда разработчики, впервые столкнувшиеся с реалиями SAST, приходят с возгласами «боже мой, 10к сработок!», «второй час анализируется!!», «ой, фолз, смотрите, тут ФОЛЗ!!», поневоле вспоминается тот мем про белок-истеричек 😍
Позвольте объяснить…
Зайду, как водится, с козырей. Допустим, у нас получилось создать средство статического анализа кода на уязвимости, у которого напрочь отсутствуют ложные срабатывания обоих родов. Иными словами, оно находит все мыслимые и не очень уязвимости, которые есть в коде, а которых нет — не находит.
Пишем что-то вот такое, и отдаем на анализ:
for n in count(3):
for x in count():
for y in count():
for z in count():
if x**n + y**n == z**n:
eval(request.param1) # RCE
Потренировавшись на кошках, и убедившись в неуязвимости этого кода, меняем Python-формулировку Великой теоремы Ферма на каждую из нерешенных математических проблем, и рубим бабло с математических призовых фондов так, как багхантерам и не снилось.
Что-то тут УЖЕ не так, не находите?
Другой пример. Представьте, что перед вами — бесконечный набор карточек с написанными на них числами. Вам нужно найти среди них какое-то одно, допустим — 23. Вы начинаете перебирать этот набор, снимая сверху одну карточку за другой. Перебираете, перебираете, а числа 23 всё нет и нет. Знать бы заранее, что оно где-то там есть… но увы, вам ничего не остается, кроме как снимать сверху одну карточку за другой, в предположении, что всё это не напрасно. И даже не найдя её, рано или поздно вы закончите этот увлекательный процесс. Ну, или передадите это гиблое дело детям и внукам. Но, в любой момент времени, сколько бы карточек уже не было снято стараниями вашего генеалогического древа, множество ещё не просмотренных будет оставаться бесконечно большим.
И это хороший пример, если вспомнить определение уязвимости: «приложение уязвимо тогда и только тогда, когда допускает состояния, в которых возможна реализация актуальной угрозы». Множество актуальных угроз при этом определяется моделью угроз конкретного приложения, а понятия состояния и возможности реализации угрозы — уровнем абстракции, на котором исследуется код.
Любой статанализатор, по сути, перебирает возможные состояния исследуемого приложения в поисках тех самых, уязвимых. Ищет карточку с нужным числом. Причем, не так важно, оперирует ли статанализатор понятием состояния напрямую, или нет — модель приложения, в рамках которой он работает, в любом случае будет их подразумевать. Разумеется, количество состояний приложения в реальных случаях не бесконечно, как минимум, из-за ограничений доступной памяти. Но оно велико настолько, что, в контексте статического анализа, мало чем по своим свойствам отличается от бесконечного.
Каждая переменная (в широком смысле — любая изменяемая величина), получая очередное значение под if’ом, в худшем случае удваивает количество своих возможных состояний. Каждая итерация цикла или рекурсии, по сути — выполнение кода под if’ом. Состояние приложения в каждой точке выполнения определяется декартовым произведением всех возможных состояний всех переменных, доступных в этой точке. А ведь есть ещё окружение приложения, со своими состояниями…
Вряд ли кому-то нужен анализатор, наблюдение за прогресс-баром которого, будет передаваться из поколения в поколение. Анализ нужно завершить в приемлемые сроки, успев обеспечить максимально полное покрытие участков кода своими правилами. И вариантов достижения этого не так уж и много.
Продолжение в следующем посте...