В программировании и разработке программного обеспечения фаззинг или фаззинг -тестирование — это автоматизированная методика тестирования программного обеспечения , которая включает предоставление недействительных, неожиданных или случайных данных в качестве входных данных для компьютерной программы . Затем программа отслеживается на предмет исключений, таких как сбои , невыполнение встроенных утверждений кода или потенциальные утечки памяти . Обычно фаззингеры используются для тестирования программ, которые принимают структурированные входные данные. Эта структура указывается, например, в формате файла или протоколе и отличает допустимые входные данные от недопустимых. Эффективный фаззинг генерирует полудопустимые входные данные, которые являются «достаточно допустимыми» в том смысле, что они не отклоняются напрямую анализатором, но создают неожиданное поведение глубже в программе и являются «достаточно недопустимыми», чтобы выявить пограничные случаи , которые не были должным образом обработаны.
В целях безопасности входные данные, пересекающие границу доверия , часто являются наиболее полезными. [1] Например, важнее выполнить фаззинг кода, который обрабатывает загрузку файла любым пользователем, чем фаззинг кода, который анализирует файл конфигурации, доступный только привилегированному пользователю.
Термин «fuzz» берет свое начало в проекте 1988 года [2] в аспирантуре Advanced Operating Systems (CS736), которую преподавал профессор Бартон Миллер в Университете Висконсина , результаты которого были впоследствии опубликованы в 1990 году. [3] [4] Для тестирования утилиты UNIX методом fuzzing , которая автоматически генерировала случайные входные данные и параметры командной строки для утилиты. Проект был разработан для проверки надежности программ командной строки UNIX путем выполнения большого количества случайных входных данных в быстрой последовательности до тех пор, пока они не давали сбой. Команда Миллера смогла вызвать сбой от 25 до 33 процентов утилит, которые они протестировали. Затем они отладили каждый из сбоев, чтобы определить причину, и классифицировали каждый обнаруженный сбой. Чтобы позволить другим исследователям проводить аналогичные эксперименты с другим программным обеспечением, исходный код инструментов, процедуры тестирования и необработанные данные результатов были сделаны общедоступными. [5] Этот ранний фаззинг теперь будет называться черным ящиком, генерационным, неструктурированным (немым или «классическим») фаззингом.
По словам профессора Бартона Миллера, «В процессе написания описания проекта мне нужно было дать этому виду тестирования название. Я хотел название, которое вызывало бы ощущение случайных, неструктурированных данных. Перепробовав несколько идей, я остановился на термине fuzz» [4] .
Ключевым вкладом этой ранней работы был простой (почти упрощенный) оракул. Программа проваливала тест, если она вылетала или зависала при случайном вводе, и считалась прошедшей в противном случае. Хотя тестовые оракулы могут быть сложными в построении, оракул для этого раннего тестирования нечеткой логики был прост и универсален в применении.
В апреле 2012 года Google анонсировала ClusterFuzz — облачную инфраструктуру фаззинга для критически важных для безопасности компонентов веб-браузера Chromium . [6] Исследователи безопасности могут загружать собственные фаззеры и получать вознаграждения за ошибки, если ClusterFuzz обнаружит сбой с загруженным фаззером.
В сентябре 2014 года Shellshock [7] был раскрыт как семейство ошибок безопасности в широко используемой оболочке UNIX Bash ; большинство уязвимостей Shellshock были обнаружены с помощью фаззера AFL . [8] (Многие службы, выходящие в Интернет, такие как некоторые развертывания веб-серверов, используют Bash для обработки определенных запросов, что позволяет злоумышленнику заставить уязвимые версии Bash выполнять произвольные команды . Это может позволить злоумышленнику получить несанкционированный доступ к компьютерной системе. [9] )
В апреле 2015 года Ханно Бёк показал, как фаззер AFL мог обнаружить уязвимость Heartbleed 2014 года. [10] [11] ( Уязвимость Heartbleed была раскрыта в апреле 2014 года. Это серьезная уязвимость, которая позволяет злоумышленникам расшифровывать зашифрованные сообщения . Уязвимость была случайно внедрена в OpenSSL , который реализует TLS и используется большинством серверов в Интернете. Shodan сообщил о 238 000 все еще уязвимых машинах в апреле 2016 года; [12] 200 000 в январе 2017 года. [13] )
В августе 2016 года Агентство перспективных исследовательских проектов Министерства обороны США (DARPA) провело финал первого Cyber Grand Challenge , полностью автоматизированного соревнования по захвату флага , которое длилось 11 часов. [14] Целью было разработать автоматические системы защиты, которые могут обнаруживать, эксплуатировать и исправлять недостатки программного обеспечения в режиме реального времени . Фаззинг использовался как эффективная стратегия нападения для обнаружения недостатков в программном обеспечении противников. Он показал огромный потенциал в автоматизации обнаружения уязвимостей. Победителем стала система под названием «Mayhem» [15], разработанная командой ForAllSecure во главе с Дэвидом Брамли .
В сентябре 2016 года Microsoft анонсировала Project Springfield — облачный сервис тестирования методом нечеткого тестирования для поиска критических ошибок безопасности в программном обеспечении. [16]
В декабре 2016 года Google анонсировала OSS-Fuzz, который позволяет проводить непрерывный фаззинг нескольких критически важных для безопасности проектов с открытым исходным кодом. [17]
На конференции Black Hat 2018 Кристофер Домас продемонстрировал использование фаззинга для выявления наличия скрытого ядра RISC в процессоре. [18] Это ядро смогло обойти существующие проверки безопасности для выполнения команд Ring 0 из Ring 3.
В сентябре 2020 года Microsoft выпустила OneFuzz , самостоятельную платформу фаззинга как услуги, которая автоматизирует обнаружение ошибок программного обеспечения . [19] Она поддерживает Windows и Linux. [20] Она была заархивирована три года спустя, 1 ноября 2023 года. [21]
Тестирование программ со случайными входными данными началось в 1950-х годах, когда данные все еще хранились на перфокартах . [22] Программисты использовали перфокарты, которые вытаскивали из мусорной корзины, или колоды карт со случайными числами в качестве входных данных для компьютерных программ. Если выполнение обнаруживало нежелательное поведение, то ошибка была обнаружена.
Выполнение случайных входных данных также называется случайным тестированием или обезьяньим тестированием .
В 1981 году Дюран и Нтафос официально исследовали эффективность тестирования программы со случайными входными данными. [23] [24] Хотя случайное тестирование широко считалось наихудшим способом тестирования программы, авторам удалось показать, что это экономически эффективная альтернатива более систематическим методам тестирования.
В 1983 году Стив Каппс из Apple разработал «Обезьянку» [25], инструмент, который будет генерировать случайные входные данные для классических приложений Mac OS , таких как MacPaint . [26] Образное «обезьяна» относится к теореме о бесконечной обезьяне , которая гласит, что обезьяна, нажимающая клавиши на клавиатуре пишущей машинки в случайном порядке в течение бесконечного количества времени, в конечном итоге напечатает все произведения Шекспира. В случае тестирования обезьяна будет записывать определенную последовательность входных данных, которая вызовет сбой.
В 1991 году был выпущен инструмент crashme, который был предназначен для проверки надежности Unix и Unix-подобных операционных систем путем случайного выполнения системных вызовов со случайно выбранными параметрами. [27]
Фаззер можно классифицировать несколькими способами: [28] [1]
Фаззер на основе мутаций использует существующий корпус начальных входов во время фаззинга. Он генерирует входы, изменяя (или, скорее, мутируя ) предоставленные начальные значения. [29] Например, при фаззинге библиотеки изображений libpng пользователь должен предоставить набор допустимых файлов изображений PNG в качестве начальных значений, в то время как фаззер на основе мутаций должен изменить эти начальные значения для получения полудопустимых вариантов каждого начального значения. Корпус начальных файлов может содержать тысячи потенциально похожих входных значений. Автоматический выбор начальных значений (или сокращение набора тестов) позволяет пользователям выбирать лучшие начальные значения, чтобы максимизировать общее количество ошибок, обнаруженных во время кампании фаззинга. [30]
Фаззер на основе поколения генерирует входные данные с нуля. Например, интеллектуальный фаззер на основе поколения [31] использует модель входных данных, предоставленную пользователем, для генерации новых входных данных. В отличие от фаззеров на основе мутаций, фаззер на основе поколения не зависит от наличия или качества корпуса начальных входных данных.
Некоторые фаззеры способны делать и то, и другое: генерировать входные данные с нуля и генерировать входные данные путем мутации существующих начальных значений. [32]
Обычно фаззеры используются для генерации входных данных для программ, которые принимают структурированные входные данные, такие как файл , последовательность событий клавиатуры или мыши или последовательность сообщений . Эта структура отличает допустимые входные данные, которые принимаются и обрабатываются программой, от недопустимых входных данных, которые быстро отклоняются программой. То, что составляет допустимые входные данные, может быть явно указано в модели входных данных. Примерами моделей входных данных являются формальные грамматики , форматы файлов , модели GUI и сетевые протоколы . Даже элементы, которые обычно не считаются входными данными, могут быть подвергнуты фаззингу, такие как содержимое баз данных , разделяемая память , переменные среды или точное чередование потоков . Эффективный фаззер генерирует полудопустимые входные данные, которые «достаточно допустимы», чтобы они не были напрямую отклонены анализатором , и «достаточно недопустимы», чтобы они могли подчеркнуть пограничные случаи и демонстрировать интересное поведение программы.
Интеллектуальный (основанный на модели, [32] основанный на грамматике, [31] [33] или основанный на протоколе [34] ) фаззер использует входную модель для генерации большей доли допустимых входных данных. Например, если входные данные можно смоделировать как абстрактное синтаксическое дерево , то интеллектуальный фаззер на основе мутаций [33] будет использовать случайные преобразования для перемещения полных поддеревьев из одного узла в другой. Если входные данные можно смоделировать с помощью формальной грамматики , то интеллектуальный фаззер на основе генерации [31] будет реализовывать правила производства для генерации входных данных, которые являются допустимыми относительно грамматики. Однако, как правило, входная модель должна быть явно указана, что трудно сделать, когда модель является частной, неизвестной или очень сложной. Если доступен большой корпус допустимых и недопустимых входных данных, метод индукции грамматики , такой как алгоритм L* Энглуина , сможет сгенерировать входную модель. [35] [36]
Глупый фаззер [37] [38] не требует входной модели и, таким образом, может использоваться для фаззинга более широкого спектра программ. Например, AFL — это глупый фаззер на основе мутаций, который изменяет начальный файл, переворачивая случайные биты , заменяя случайные байты «интересными» значениями и перемещая или удаляя блоки данных. Однако глупый фаззер может генерировать меньшую долю допустимых входных данных и нагружать код синтаксического анализатора, а не основные компоненты программы. Недостаток глупых фаззеров можно проиллюстрировать с помощью построения допустимой контрольной суммы для циклического избыточного кода (CRC). CRC — это код обнаружения ошибок , который гарантирует сохранение целостности данных, содержащихся во входном файле, во время передачи . Контрольная сумма вычисляется по входным данным и записывается в файл. Когда программа обрабатывает полученный файл, и записанная контрольная сумма не совпадает с повторно вычисленной контрольной суммой, файл отклоняется как недействительный. Теперь, фаззер, который не знает о CRC, вряд ли сгенерирует правильную контрольную сумму. Однако есть попытки идентифицировать и пересчитать потенциальную контрольную сумму в мутированном вводе, как только тупой фаззер на основе мутации изменил защищенные данные. [39]
Обычно фаззер считается более эффективным, если он достигает более высокой степени покрытия кода . Обоснование заключается в том, что если фаззер не проверяет определенные структурные элементы в программе, то он также не может обнаружить ошибки , которые скрываются в этих элементах. Некоторые элементы программы считаются более критическими, чем другие. Например, оператор деления может вызвать ошибку деления на ноль , или системный вызов может привести к сбою программы.
Фаззер черного ящика [37] [33] рассматривает программу как черный ящик и не знает о внутренней структуре программы. Например, случайный инструмент тестирования , который генерирует входные данные случайным образом, считается фаззером черного ящика. Следовательно, фаззер черного ящика может выполнять несколько сотен входных данных в секунду, может быть легко распараллелен и может масштабироваться до программ произвольного размера. Однако фаззеры черного ящика могут только царапать поверхность и выявлять «поверхностные» ошибки. Следовательно, существуют попытки разработать фаззеры черного ящика, которые могут постепенно узнавать о внутренней структуре (и поведении) программы во время фаззинга, наблюдая за выходными данными программы при заданных входных данных. Например, LearnLib использует активное обучение для генерации автомата , который представляет поведение веб-приложения.
Фаззер белого ящика [38] [32] использует анализ программы для систематического увеличения покрытия кода или для достижения определенных критических мест программы. Например, SAGE [40] использует символическое выполнение для систематического исследования различных путей в программе (метод, известный как concolic execution ). Если спецификация программы доступна, фаззер белого ящика может использовать методы из тестирования на основе модели для генерации входных данных и проверки выходных данных программы на соответствие спецификации программы. Фаззер белого ящика может быть очень эффективным для выявления ошибок, которые скрываются глубоко в программе. Однако время, используемое для анализа (программы или ее спецификации), может стать непомерно большим. Если фаззер белого ящика тратит относительно много времени на генерацию входных данных, фаззер черного ящика будет более эффективным. [41] Следовательно, есть попытки объединить эффективность фаззеров черного ящика и эффективность фаззеров белого ящика. [42]
Серый ящик фаззера использует инструментарий, а не программный анализ, чтобы собрать информацию о программе. Например, AFL и libFuzzer используют легкий инструментарий для отслеживания основных переходов блоков , осуществляемых входом. Это приводит к разумным накладным расходам производительности, но информирует фаззер об увеличении покрытия кода во время фаззинга, что делает серые ящики фаззеров чрезвычайно эффективными инструментами обнаружения уязвимостей. [43]
Фаззинг в основном используется как автоматизированный метод выявления уязвимостей в критически важных для безопасности программах, которые могут быть использованы со злым умыслом. [6] [16] [17] В более общем смысле фаззинг используется для демонстрации наличия ошибок, а не их отсутствия. Проведение кампании фаззинга в течение нескольких недель без обнаружения ошибки не доказывает правильность программы. [44] В конце концов, программа все еще может давать сбой для ввода, который еще не был выполнен; выполнение программы для всех вводов непомерно дорого. Если цель состоит в том, чтобы доказать правильность программы для всех вводов, должна существовать формальная спецификация и должны использоваться методы из формальных методов .
Чтобы выявить ошибки, фаззер должен уметь отличать ожидаемое (нормальное) от неожиданного (с ошибками) поведения программы. Однако машина не всегда может отличить ошибку от функции. В автоматизированном тестировании программного обеспечения это также называется проблемой тестового оракула . [45] [46]
Обычно фаззер различает аварийные и неаварийные входы при отсутствии спецификаций и использует простую и объективную меру. Аварии легко идентифицируются и могут указывать на потенциальные уязвимости (например, отказ в обслуживании или выполнение произвольного кода ). Однако отсутствие аварии не указывает на отсутствие уязвимости. Например, программа, написанная на языке C , может аварийно завершиться, а может и не завершиться, когда входные данные вызывают переполнение буфера . Скорее, поведение программы не определено .
Чтобы сделать фаззер более чувствительным к сбоям, отличным от сбоев, можно использовать санитайзеры для внедрения утверждений, которые приводят к сбою программы при обнаружении сбоя. [47] [48] Существуют различные санитайзеры для разных видов ошибок:
Фаззинг также может использоваться для обнаружения «дифференциальных» ошибок, если доступна эталонная реализация . Для автоматизированного регрессионного тестирования [49] сгенерированные входные данные выполняются на двух версиях одной и той же программы. Для автоматизированного дифференциального тестирования [50] сгенерированные входные данные выполняются на двух реализациях одной и той же программы (например, lighttpd и httpd являются реализациями веб-сервера). Если два варианта выдают разные выходные данные для одного и того же входа, то один из них может быть глючным и его следует изучить более внимательно.
Статический анализ программы анализирует программу, фактически не выполняя ее. Это может привести к ложным срабатываниям , когда инструмент сообщает о проблемах с программой, которых на самом деле не существует. Фаззинг в сочетании с динамическим анализом программы может использоваться для попытки сгенерировать входные данные, которые фактически свидетельствуют о выявленной проблеме. [51]
Современные веб-браузеры подвергаются обширному фаззингу. Код Chromium Google Chrome постоянно фаззингится командой Chrome Security Team с 15 000 ядер. [52] Для Microsoft Edge и Internet Explorer Microsoft провела фаззинговое тестирование с 670 машино-лет во время разработки продукта, сгенерировав более 400 миллиардов манипуляций DOM из 1 миллиарда HTML-файлов. [53] [52]
Фаззер производит большое количество входов за относительно короткое время. Например, в 2016 году проект Google OSS-fuzz производил около 4 триллионов входов в неделю. [17] Таким образом, многие фаззеры предоставляют цепочку инструментов , которая автоматизирует в противном случае ручные и утомительные задачи, которые следуют за автоматизированной генерацией вызывающих сбои входов.
Автоматизированная сортировка ошибок используется для группировки большого количества вводов, вызывающих сбои, по первопричине и для приоритизации каждой отдельной ошибки по серьезности. Фаззер создает большое количество вводов, и многие из выводов, вызывающих сбои, могут эффективно выявлять одну и ту же программную ошибку . Только некоторые из этих ошибок являются критическими для безопасности и должны быть исправлены с более высоким приоритетом. Например, Координационный центр CERT предоставляет инструменты сортировки Linux, которые группируют вводы, вызывающие сбой, по полученной трассировке стека и перечисляют каждую группу в соответствии с вероятностью их эксплуатации . [ 54] Исследовательский центр безопасности Microsoft (MSEC) разработал инструмент "!exploitable", который сначала создает хэш для ввода, вызывающего сбой, чтобы определить его уникальность, а затем присваивает рейтинг эксплуатируемости: [55]
Ранее не сообщенные, сортированные ошибки могут быть автоматически сообщены в систему отслеживания ошибок . Например, OSS-Fuzz запускает крупномасштабные, долгосрочные кампании фаззинга для нескольких критически важных для безопасности программных проектов, где каждая ранее не сообщенная, определенная ошибка сообщается непосредственно в баг-трекер. [17] Баг-трекер OSS-Fuzz автоматически информирует сопровождающего уязвимого программного обеспечения и регулярно проверяет, была ли ошибка исправлена в последней версии, используя загруженные минимизированные входные данные, вызывающие сбои.
Автоматическая минимизация ввода (или сокращение тестового случая) — это автоматизированная техника отладки , позволяющая изолировать ту часть ввода, которая на самом деле вызывает сбой. [56] [57] Если ввод, вызывающий сбой, большой и в основном неправильно сформирован, разработчику может быть сложно понять, что именно вызывает ошибку. Учитывая ввод, вызывающий сбой, автоматизированный инструмент минимизации удалит как можно больше байтов ввода, при этом все еще воспроизводя исходную ошибку. Например, дельта-отладка — это автоматизированная техника минимизации ввода, которая использует расширенный алгоритм двоичного поиска для поиска такого минимального ввода. [58]
Ниже приведен список фаззеров, описанных в академической литературе как «популярные», «широко используемые» или аналогичные. [59] [60]