Открыть сервис

Фаззинг

Фаззинг (от англ. fuzz — «пух», «нечёткий») — это автоматизированная техника тестирования программного обеспечения, заключающаяся в подаче на вход программы некорректных, неожиданных или случайных данных (фазз-данных) с целью выявления ошибок, уязвимостей и сбоев в работе. Фаззинг относится к классу методов динамического анализа и широко применяется в области информационной безопасности, разработки и обеспечения качества ПО.

История

Истоки фаззинга восходят к началу 1950-х годов, когда в ходе разработки ранних компьютерных систем программисты случайно обнаруживали ошибки при вводе нестандартных данных. Однако систематическое применение метода началось в 1988 году, когда американский учёный Бартон Миллер из Висконсинского университета в Мадисоне разработал первую программу-фаззер. Миллер тестировал утилиты командной строки UNIX, подавая на их вход случайные символы, и обнаружил, что до 25–33 % стандартных утилит (например, cat, grep, ls) аварийно завершались или зависали при обработке такого ввода. Это исследование, опубликованное в 1990 году, заложило основы современного фаззинга.

В 1990-е годы фаззинг использовался преимущественно в академической среде и в рамках тестирования коммерческого ПО, но не получил широкого распространения. Перелом произошёл в 2000-х годах, когда рост числа кибератак и уязвимостей в популярных приложениях (например, в Microsoft Office, Adobe Reader, браузерах) стимулировал интерес к автоматизированным методам поиска ошибок. В 2002 году компания Microsoft начала внедрять фаззинг в процесс разработки Windows, а в 2004 году исследователь Дэвид Айтел создал открытый фаззер zzuf, который модифицировал системные вызовы для тестирования приложений.

Значительный прогресс произошёл в 2010-х годах с появлением эффективных алгоритмов, таких как AFL (American Fuzzy Lop), разработанный Михалом Залевски в 2013 году. AFL использовал генетические алгоритмы и покрытие кода для генерации более релевантных тестовых данных, что позволило находить уязвимости в десятках тысяч программ. В 2014 году команда Google запустила проект OSS-Fuzz — платформу для непрерывного фаззинг-тестирования открытого ПО, которая к 2024 году выявила более 30 000 уязвимостей в тысячах проектов. С 2020-х годов фаззинг стал стандартной практикой в разработке критически важного ПО, включая ядра операционных систем, веб-браузеры и криптографические библиотеки.

Классификация

Фаззинг классифицируется по нескольким критериям: степени знания о структуре входных данных, способу генерации тестов и типу целевого приложения.

По степени знания о входных данных

  • Мутационный фаззинг (mutational fuzzing) — основан на модификации существующих корректных входных данных (сидов). Программа-фаззер случайным образом изменяет биты, байты, строки или структуры в сидах, создавая новые варианты. Этот метод прост в реализации, но может быть неэффективен для форматов со сложными проверками (например, ZIP-архивов с контрольными суммами).
  • Генеративный фаззинг (generational fuzzing) — создаёт входные данные с нуля, используя формальное описание формата (например, грамматику, BNF-нотацию, схемы). Такой подход позволяет генерировать синтаксически корректные, но семантически некорректные данные, что повышает вероятность прохождения проверок и достижения глубоких участков кода. Примеры: фаззеры для протоколов HTTP, SQL-запросов, файлов PDF.
  • Гибридный фаззинг (hybrid fuzzing) — комбинирует мутацию и генерацию, часто с использованием символьного выполнения (concolic testing) для решения сложных ограничений на пути выполнения. Например, фаззер Driller (2016) чередует мутационный поиск и символьное выполнение для обхода проверок.

По способу генерации тестов

  • Случайный фаззинг (random fuzzing) — генерирует данные полностью случайным образом. Прост, но имеет низкую эффективность для сложных форматов.
  • Покрытие-ориентированный фаззинг (coverage-guided fuzzing) — использует информацию о покрытии кода (например, количество выполненных базовых блоков, ветвей) для выбора наиболее перспективных мутаций. AFL, libFuzzer и Honggfuzz — типичные представители. Этот метод доминирует в современном фаззинге.
  • Фаззинг на основе грамматики (grammar-based fuzzing) — генерирует данные по заданной грамматике, что позволяет тестировать парсеры и интерпретаторы.
  • Фаззинг на основе мутации протокола (protocol fuzzing) — модифицирует пакеты сетевых протоколов, сохраняя их структуру.

По типу целевого приложения

  • Фаззинг файловых форматов — подача на вход программы модифицированных файлов (изображения, документы, архивы).
  • Фаззинг сетевых протоколов — отправка некорректных пакетов на серверы (например, HTTP, DNS, SSH).
  • Фаззинг API и библиотек — вызов функций с некорректными аргументами (например, через fuzz-тестирование в Rust или Go).
  • Фаззинг ядра ОС — тестирование системных вызовов и драйверов (например, syzkaller для Linux).
  • Фаззинг веб-приложений — подача некорректных данных через HTTP-запросы (SQL-инъекции, XSS).

Устройство и принцип работы

Типичный фаззинг-цикл состоит из следующих этапов:

  1. Выбор целевого приложения — определяется программа или библиотека для тестирования. Для фаззинга требуется, чтобы приложение могло принимать входные данные из внешнего источника (файл, stdin, сокет, аргумент командной строки).
  2. Подготовка сидов — создаётся набор корректных входных данных (например, несколько валидных PNG-файлов для тестирования библиотеки обработки изображений). Сиды служат отправной точкой для мутаций.
  3. Генерация тестовых данных — фаззер применяет мутации (перестановка битов, вставка/удаление байтов, замена строк, слияние фрагментов) или генерирует данные по грамматике. В покрытие-ориентированных фаззерах мутации выбираются на основе предыдущих результатов.
  4. Запуск целевого приложения — фаззер запускает приложение с сгенерированными данными и отслеживает его поведение (аварийное завершение, зависание, превышение лимитов памяти или времени).
  5. Мониторинг и сбор информации — если приложение завершается аварийно (например, с сигналом SIGSEGV, SIGABRT), фаззер сохраняет входные данные, вызвавшие сбой, для последующего анализа. В покрытие-ориентированных фаззерах также собирается информация о покрытии кода (например, через инструментирование бинарного кода или использование аппаратных счётчиков).
  6. Обратная связь — на основе покрытия фаззер выбирает наиболее перспективные входные данные для дальнейших мутаций, что позволяет постепенно углубляться в код.

Современные фаззеры, такие как AFL, libFuzzer и Honggfuzz, используют инструментирование (вставку дополнительных инструкций в бинарный код) для отслеживания покрытия. Инструментирование может быть статическим (на этапе компиляции) или динамическим (через эмуляцию, например, QEMU). В 2020-х годах получили распространение фаззеры на основе аппаратных средств, таких как Intel Processor Trace (PT), которые позволяют записывать трассы выполнения без значительных накладных расходов.

Применение

Фаззинг применяется в различных областях, связанных с разработкой и безопасностью ПО:

  • Поиск уязвимостей — основное применение. Фаззинг позволяет находить ошибки переполнения буфера, использования после освобождения (use-after-free), целочисленные переполнения, ошибки форматирования строк и другие типы уязвимостей, которые могут быть использованы злоумышленниками. Например, в 2014 году с помощью фаззинга была обнаружена уязвимость Heartbleed в OpenSSL, а в 2021 году — уязвимость Log4Shell в библиотеке Log4j.
  • Тестирование качества ПО — выявление крашей, зависаний и некорректного поведения при обработке нестандартных данных. Используется на этапах интеграционного и регрессионного тестирования.
  • Верификация безопасности — в рамках процессов разработки (например, в Microsoft SDL — Security Development Lifecycle) фаззинг является обязательным этапом для критически важных компонентов.
  • Исследование протоколов и форматов — фаззинг помогает выявить скрытые особенности реализации протоколов, которые не описаны в спецификациях.

Крупные компании активно используют фаззинг. Google в рамках OSS-Fuzz тестирует тысячи открытых проектов, включая OpenSSL, libpng, FFmpeg, SQLite. Microsoft встроила фаззинг в Visual Studio и Azure DevOps. Apple использует фаззинг для тестирования iOS и macOS. В России фаззинг применяется в разработке защищённого ПО, в том числе в компаниях «Лаборатория Касперского» и «ИнфоТеКС», а также в государственных программах по импортозамещению.

Примеры инструментов

  • AFL (American Fuzzy Lop) — открытый покрытие-ориентированный фаззер, использующий генетические алгоритмы. Поддерживает инструментирование через GCC/Clang и QEMU. Широко используется в академических и коммерческих проектах.
  • libFuzzer — библиотека для фаззинга, входящая в состав Clang/LLVM. Интегрируется непосредственно в тестируемое приложение, что обеспечивает высокую производительность. Используется в OSS-Fuzz.
  • Honggfuzz — открытый фаззер с поддержкой аппаратного трассирования (Intel PT), работающий на Linux, macOS и Windows. Отличается низкими накладными расходами.
  • Syzkaller — фаззер для ядра Linux, разработанный Google. Генерирует системные вызовы с произвольными аргументами и находит уязвимости в драйверах и подсистемах ядра.
  • OSS-Fuzz — не инструмент, а платформа Google для непрерывного фаззинг-тестирования открытого ПО. Предоставляет инфраструктуру для запуска фаззеров на облачных серверах и автоматического уведомления разработчиков о найденных ошибках.
  • Peach Fuzzer — коммерческий фаззер для тестирования протоколов и файловых форматов. Поддерживает создание пользовательских моделей данных.
  • Fuzzilli — фаззер для JavaScript-движков, разработанный Google. Использует мутации на уровне AST и находит уязвимости в V8, SpiderMonkey и JavaScriptCore.

Критика и ограничения

Несмотря на эффективность, фаззинг имеет ряд ограничений:

  • Невозможность гарантировать полноту — фаззинг не может доказать отсутствие ошибок, так как тестирует лишь конечное подмножество входных данных. Для сложных программ с большим пространством состояний (например, компиляторов, СУБД) полное покрытие практически недостижимо.
  • Сложность тестирования программ с проверками — если программа проверяет контрольные суммы, цифровые подписи или сложные инварианты, случайные мутации часто отвергаются на ранних этапах. Гибридные методы (например, с символьным выполнением) частично решают эту проблему, но требуют значительных вычислительных ресурсов.
  • Зависимость от качества сидов — плохой выбор начальных сидов может привести к тому, что фаззер будет «застревать» в поверхностных участках кода, не достигая глубоких ветвей.
  • Высокие ресурсозатраты — для эффективного фаззинга требуется много процессорного времени и памяти. В облачных средах (например, OSS-Fuzz) используются кластеры из сотен машин.
  • Ложные срабатывания — некоторые аварийные завершения могут быть вызваны не ошибками в коде, а, например, переполнением стека из-за рекурсии, что требует ручного анализа.
  • Проблемы с недетерминированным поведением — программы, использующие случайные числа, многопоточность или асинхронные операции, могут вести себя по-разному при одних и тех же входных данных, что затрудняет воспроизведение ошибок.

Интересные факты

  • Термин «фаззинг» (fuzzing) был предложен Бартоном Миллером в 1988 году, первоначально как «fuzz testing». Название отражает «нечёткость» (fuzziness) входных данных.
  • В 2016 году исследователи из Google использовали фаззинг для тестирования компилятора C++ и обнаружили более 1000 ошибок в Clang и GCC.
  • Фаззинг применяется не только для программного обеспечения, но и для аппаратных устройств, например, для тестирования прошивок IoT-устройств или контроллеров автомобилей.
  • В 2020 году проект OSS-Fuzz достиг отметки в 20 000 найденных уязвимостей, а к 2024 году — более 30 000.
  • В России фаззинг активно используется в рамках программы «Цифровая экономика» для тестирования отечественного ПО, в том числе операционных систем «Астра Linux» и «РЕД ОС».

BFOmetr — база данных и аналитика по компаниям России.

На главную BFOmetr →