Как проверить смарт-контракт на наличие уязвимостей

Как проверить смарт контракт на наличие уязвимостей

Проверка смарт‑контракта на уязвимости - это сочетание ручного ревью кода, статического анализа (SAST), динамических тестов (фуззинг/символьное исполнение) и аудита зависимостей/конфигурации. Практичный порядок: собрать артефакты и воспроизводимую среду, прогнать несколько сканеров, подтвердить находки тестами и сценариями атак, затем оценить риск и составить план исправлений.

Краткая сводка рисков и ключевых выводов

  • Не полагайтесь на один инструмент: разные анализаторы находят разные классы проблем и дают ложные срабатывания.
  • Сначала обеспечьте воспроизводимость сборки (компилятор, оптимизации, зависимости), иначе результаты будут недостоверны.
  • Критичность определяется не "страшностью" отчёта, а достижимостью в атаке и влиянием (потеря средств, блокировка, захват прав).
  • Динамические проверки обязаны подтверждать эксплуатацию (PoC) на форке/тестнете, а не на проде.
  • Проверяйте не только код: конфиги деплоя, роли, прокси‑паттерны, параметры компилятора и зависимости часто опаснее.

Подготовка окружения и сбор артефактов для проверки

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

Когда не стоит делать самостоятельно: если контракт уже управляет значимыми активами, есть сложные кроссчейн/мультипротокольные взаимодействия, или вы не можете воспроизвести сборку/тесты - лучше привлекать независимый аудит и ограничить изменения (минимальный патч + защитные лимиты).

Что собрать заранее (артефакты)

  • Исходники контрактов и точный список версий компилятора Solidity (pragma + фактическая версия solc).
  • Конфигурации сборки: Hardhat/Foundry/Truffle, параметры оптимизатора, remappings, настройки viaIR (если используются).
  • ABI, bytecode, адреса прокси/реализаций (если уже деплоилось), список ролей/админов, параметры инициализации.
  • Тесты, сценарии деплоя/миграций, скрипты апгрейда, адреса внешних зависимостей (оракулы, токены, роутеры).
  • Для верификации на форке: RPC-эндпоинт, номер блока, состояние (список ключевых аккаунтов/балансов).

Быстрый старт окружения (пример)

  • Foundry: curl -L https://foundry.paradigm.xyz | bash, затем foundryup
  • Hardhat: npm i, затем npx hardhat test
  • Локальная сеть: Anvil anvil или Hardhat Network npx hardhat node
  • Форк сети (только для тестов): anvil --fork-url $RPC_URL --fork-block-number $BLOCK

Анализ исходного кода: чек‑лист типичных уязвимостей

Что понадобится: доступ к репозиторию, возможность собрать проект (npm/pnpm + solc/forge), прогнать тесты, а также понимание модели угроз (кто атакующий, какие активы, какие привилегии и точки входа). Желательно иметь сценарии использования (user stories) и список инвариантов протокола.

Чек‑лист ручного ревью (то, что чаще всего пропускают)

Как проверить смарт-контракт на наличие уязвимостей - иллюстрация
  • Контроль доступа: корректность ролей, модификаторов, owner/admin, отсутствие "забытых" публичных сеттеров, защита инициализаторов в прокси.
  • Внешние вызовы: reentrancy, вызовы в untrusted контракты, порядок "checks-effects-interactions", опасные callback‑механики (ERC777/ERC1155 hooks).
  • Математика и единицы: rounding/precision, несоответствие decimals, переполнение/underflow в кастомных библиотеках, деление до умножения.
  • Логика времени и блоков: зависимости от block.timestamp/block.number (манипулируемы в пределах), окна обновления цен/периодов.
  • Оракулы и цены: отсутствие проверки stale/heartbeat, уязвимость к манипуляции DEX‑ценой, некорректные TWAP/spot.
  • MEV и фронт‑ран: публичные функции, позволяющие перехватить выгодное состояние, отсутствие commit‑reveal там, где нужно.
  • DoS: циклы по динамическим массивам, push‑механики выплат, зависимость от того, что внешний вызов "всегда успешен".
  • Upgradeability: storage layout, gap, коллизии слотов, отсутствие onlyProxy/initializer, неверный admin у прокси.
  • Права на токены: опасные approve/permit, неочевидные allowances, fee-on-transfer/deflationary токены.
  • События и мониторинг: отсутствие событий на критичных изменениях усложняет реагирование и расследование.

Статический анализ и подбор инструментов сканирования

Риски и ограничения перед запуском сканеров

  • Сканер может "найти" проблему, которой нет: подтверждайте находку тестом/PoC и трассировкой.
  • Инструменты зависят от корректной сборки: неверная версия solc, remappings или оптимизации меняют результат.
  • Результаты часто неполные для прокси/апгрейдов: анализируйте реализацию, инициализаторы и сценарий апгрейда.
  • Нельзя безопасно проверять эксплуатацию на mainnet: используйте локальную сеть, форк или тестнет.

Таблица выбора инструментов: точность, скорость, ложные срабатывания

Инструмент Тип Сильные стороны Скорость Ложные срабатывания Когда выбирать
Slither SAST (быстрые правила/анализ графов) Широкое покрытие паттернов, удобные отчёты, быстрый прогон Высокая Средние Базовый "первый проход" на любом проекте
Mythril Символьный анализ Ищет глубже логические баги и пути до ошибок Низкая-средняя Средние Когда важны сложные пути выполнения и условия
Echidna Фуззинг свойств (property-based) Находит реальные контрпримеры при хороших инвариантах Средняя Низкие при корректных свойствах Когда есть чёткие инварианты и тестовая инфраструктура
Manticore Символьное исполнение Глубокий поиск по путям, полезен для PoC Низкая Низкие-средние Точечная проверка подозрительных участков
Foundry (forge) Тестирование/форк/инварианты Быстрые тесты, форк mainnet, удобная трассировка Высокая Зависит от тестов Подтверждение эксплуатации и регрессии после фикса

Пошаговая инструкция (SAST + базовая валидация)

  1. Зафиксируйте сборку и компилятор

    Соберите проект "с нуля", убедитесь, что зависимости подтягиваются детерминированно, а версия solc совпадает с ожидаемой. Сохраните параметры оптимизатора и включение viaIR, если применимо.

    • Foundry: forge --version, forge build -vvv
    • Hardhat: npx hardhat compile
  2. Прогоните Slither как быстрый скрининг

    Slither быстро подсветит типовые паттерны: reentrancy‑риски, неинициализированные переменные, подозрительные вызовы, нарушения best practices. Начните с отчёта по всему проекту, затем уточняйте настройки.

    • Пример: slither .
    • Если проект на Hardhat: slither . --hardhat-artifacts-directory artifacts
  3. Проверьте подозрительные места символьным анализом

    Используйте Mythril (или Manticore) не "на всё подряд", а на критичные контракты и функции: управление ролями, вывод средств, расчёт долей, апгрейды. Это экономит время и снижает шум.

    • Mythril: myth analyze contracts/MyContract.sol
  4. Сведите результаты в единый трекер находок

    Для каждой находки заведите карточку: описание, затронутые функции, условия эксплуатации, предполагаемое влияние, ссылка на конкретные строки/коммиты. Отдельно отмечайте "подтвердилось тестом" и "похоже на ложное".

  5. Подтвердите минимум одну критичную гипотезу тестом на форке

    До глубокого фуззинга выберите 1-2 наиболее рискованных сценария и проверьте их воспроизводимость на локальной сети/форке. Это быстро показывает реальный профиль угроз и помогает правильно приоритизировать остальные проверки.

    • Foundry: forge test -vvv и тесты с RPC‑форком через переменные окружения
    • Hardhat: npx hardhat test + настройка forking в hardhat.config

Динамическое тестирование: фуззинг, символьное исполнение и сценарии атак

Цель динамики - не "получить больше алертов", а доказать достижимость: контрпример для инварианта, PoC для уязвимости, трасса вызовов и минимальный набор условий.

Чек‑лист проверки результата (что должно быть в конце)

  • Для каждой критичной/высокой находки есть воспроизводимый сценарий: тест или скрипт, который падает до фикса и проходит после.
  • Эксплуатация проверена на локальной сети или форке с зафиксированным блоком и входными данными.
  • Инварианты сформулированы явно (например: "сумма долей не превышает 1", "баланс пула не уходит в минус", "роль admin не меняется без события/кворума").
  • Фуззинг покрывает границы: нули, максимумы, повторные вызовы, последовательности вызовов, случайные адреса токенов (включая fee-on-transfer).
  • Есть тесты на reentrancy (в т.ч. через вредоносный контракт‑реентрантер), а не только проверка модификатора.
  • Проверены сценарии отказа внешних зависимостей: revert/return false/пауза оракула/стейл‑данные.
  • Для апгрейдов есть тест: до/после апгрейда значения storage сохранены, инициализаторы не переиспользуются.
  • Для MEV‑рисков есть сценарии "передвинули порядок транзакций", "подменили цену на один блок", "сэндвич вокруг свопа".

Минимальные команды/подходы для динамики

  • Foundry инварианты: пишите handler‑контракт и запускайте forge test --match-test invariant -vvv.
  • Echidna: задайте свойства в Solidity и прогоняйте echidna-test contracts/MyProps.sol --contract MyProps.
  • Символьное исполнение (точечно): берите одну функцию/контракт и ограничивайте пространство ввода, иначе анализ будет слишком долгим.

Аудит зависимостей, библиотек и конфигураций развертывания

Проблемы часто возникают на стыке: несовместимые версии библиотек, неправильная инициализация, ошибочные адреса в прод‑конфиге, неверная модель прав в прокси. Проверьте это так же строго, как и Solidity‑код.

Частые ошибки, которые приводят к инцидентам

  • Непинованные зависимости: плавающие версии пакетов/подмодулей, разные сборки у разных разработчиков.
  • Смешение библиотек и компиляторов: разные minor‑версии solc для разных модулей, неучтённые изменения поведения оптимизатора.
  • Неправильные адреса внешних контрактов: роутер/оракул/токен не той сети, подменённый адрес в конфиге.
  • Отсутствие проверок на chainId/домены: подписи/permit/кроссдоменная логика без доменного разделения.
  • Proxy‑конфигурация: админ прокси у EOA без защиты, неверный порядок деплоя, повторный вызов initializer, открытые функции апгрейда.
  • Роли и мультисиг: доступ к паузе/апгрейду/параметрам у неподходящих аккаунтов, нет задержки/процесса изменения.
  • Параметры компиляции: разные флаги оптимизатора между аудитом и прод‑деплоем, несоответствие верификации.
  • Секреты и ключи: приватные ключи/сид‑фразы в репозитории, CI‑логах, переменных окружения без ограничений.
  • Неполные миграции: забытые вызовы инициализации, неустановленные лимиты, пустые адреса получателей комиссий.

Оценка риска, приоритизация найденных проблем и план исправлений

Приоритизируйте по достижимости и влиянию: какие активы под угрозой, какие роли нужны атакующему, можно ли автоматизировать атаку, как быстро она масштабируется. Результат должен быть планом работ с тестами регрессии и критериями готовности.

Практичная схема приоритизации

  1. Влияние: потеря/блокировка средств, захват прав, нарушение инвариантов протокола, деградация доступности.
  2. Достижимость: публичная функция vs требуется роль, один вызов vs сложная последовательность, нужен ли MEV/манипуляция оракулом.
  3. Детектируемость и обратимость: можно ли остановить (pause), есть ли лимиты, возможен ли откат/апгрейд без ущерба.
  4. Поверхность: сколько точек входа и интеграций затронуто, есть ли аналогичные места в коде.

Варианты действий (когда что уместно)

Как проверить смарт-контракт на наличие уязвимостей - иллюстрация
  • Точечный фикс + регрессионные тесты: уместно, когда баг локализован и легко покрывается тестом/инвариантом; обязательно добавьте тест, который воспроизводит проблему.
  • Защитные ограничения без изменения бизнес‑логики: уместно, когда нужен быстрый risk‑reduction (лимиты, пауза, allowlist, rate‑limit, cap) до полноценного рефакторинга.
  • Рефакторинг модуля/архитектуры: уместно при системной причине (неверная модель прав, опасные внешние вызовы, смешение ответственности) - дороже, но снижает класс рисков.
  • Ограничение фичи/вывод из эксплуатации: уместно, когда исправление рискованнее или сложнее, чем отключение компонента, и есть безопасный обходной путь.

Практические ответы на часто встречающиеся сложности

Можно ли ограничиться одним Slither и считать контракт проверенным?

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

Что делать, если сканер выдаёт много ложных срабатываний?

Сведите находки в трекер и подтверждайте приоритетные пункты тестом/PoC. Для каждого правила фиксируйте контекст: почему это безопасно именно в вашем коде, чтобы не возвращаться к тому же спору.

Как безопасно проверять эксплуатацию, не рискуя реальными средствами?

Запускайте проверки на локальной сети или на форке с фиксированным блоком и тестовыми аккаунтами. Никогда не проводите "эксплуатационные эксперименты" в mainnet и не используйте реальные ключи.

Как проверить контракт с прокси (UUPS/Transparent), если анализатор "не видит" логику?

Анализируйте контракт реализации и отдельно сценарий инициализации/апгрейда. Обязательно проверьте storage layout и защиту initializer, иначе даже корректная логика может быть скомпрометирована.

Нужно ли писать инварианты, если уже есть обычные unit‑тесты?

Да, инварианты ловят неожиданные последовательности вызовов и крайние значения, которые unit‑тесты часто не покрывают. Начните с 3-5 инвариантов вокруг активов, ролей и лимитов.

Как понять, что уязвимость действительно критичная?

Критичная - та, что достигается атакующим с реалистичными правами и приводит к потере/заморозке средств или захвату управления. Если без привилегий можно воспроизвести PoC на форке - приоритет должен быть максимальным.

Прокрутить вверх