Главная
» Знания
»
Пошаговое руководство по аудиту смарт-контракта криптопроекта
Пошаговое руководство по аудиту смарт-контракта криптопроекта
Аудит смарт-контракта — это структурированная попытка выявить, как контракт может дать сбой, быть использован не по назначению или управляться способом, не ожидаемым пользователями. Это не то же самое, что запуск одного сканера, чтение аудиторского значка или подтверждение проверки исходного кода. Используйте описанный ниже рабочий процесс для проверки развернутого контракта EVM или кодовой базы, прежде чем доверить им значительные средства.
Важно: это практическая схема проверки, а не гарантия безопасности проекта и не инвестиционная рекомендация. Протокол, имеющий существенную ценность и предназначенный для использования в производственной среде, должен пройти независимую проверку опытными специалистами по безопасности. Изображения интерфейса в этом руководстве носят иллюстративный характер и не должны рассматриваться как доказательство безопасности какого-либо конкретного проекта или развертывания.
Краткий обзор контрольного списка аудита
Шаг
Основной вопрос
Полезные доказательства
1. Область применения
Проверяю ли я именно тот договор, по которому звонят пользователи?
Адрес, цепочка, байт-код, прокси и реализация
2. Точки входа
Что может сделать каждый звонивший?
Общедоступные/внешние функции, изменения состояния, граф вызовов
Сохраняется ли это поведение при неожиданных входных данных и последовательностях?
модульные, нечеткие, инвариантные и форк-тесты
8. Составление отчетов
Может ли другой человек воспроизвести и повторно проверить результат?
Результаты, последствия, доказательства, исправление и статус повторного тестирования
Шаг 1: Подтвердите область действия и развернутый артефакт.
Представление проверки контракта, отображающее проверки сети, адреса, версии компилятора и соответствия байт-кода, которые необходимо записать перед анализом.
Начните с точной цепочки и адреса. Запишите адрес развертывания, хеш транзакции, номер блока, версию компилятора, настройки оптимизатора, аргументы конструктора и номер коммита или релиза, который, по словам команды, развернут. В проекте может быть несколько адресов для токена, маршрутизатора, хранилища, прокси, реализации, оракула или тестового развертывания. Проверка неверного адреса не имеет практической ценности.
Проверьте, воспроизводит ли проверенный исходный код обозревателя развернутый байт-код. Проверка полезна, поскольку позволяет изучить исходный код и ABI, но это лишь проверка подлинности: она не доказывает безопасность бизнес-логики. Если контракт подлежит обновлению, определите как прокси, так и его текущую реализацию. Прочитайте адрес реализации из документированного механизма прокси или информации обозревателя, а затем подтвердите, что это именно та реализация, которую вы собираетесь проверить. В официальном руководстве Etherscan по проверке Foundry описана проверка новых и существующих контрактов.
Также определите границы. Включите импортированные библиотеки, унаследованные контракты, связанные библиотеки, развернутые вспомогательные контракты, адаптеры оракулов, токены, полученные от пользователей, и привилегированные компоненты вне блокчейна. Запишите, что выходит за рамки обзора и почему. Это предотвратит ошибочное принятие узкого обзора за обзор всей системы.
Шаг 2: Создайте карту точек входа и активов.
Список функций, который разделяет общедоступные и внешние точки входа, прежде чем рецензент сможет отследить изменения их состояния.
Перечислите все публичные и внешние функции, включая унаследованные функции и обработчики резервных или приемных запросов. Для каждой из них укажите, может ли она:
перемещать собственную валюту или токены;
чеканить монеты, сжигать, брать взаймы, ликвидировать или изменять бухгалтерский учет;
изменить оракул, комиссию, лимит, роль, состояние паузы или реализацию;
совершить внешний вызов, делегировать вызов или вызов низкого уровня; или
считывать данные, от которых зависит другая функция, изменяющая состояние.
Затем определите активы и границы доверия. Проследите за внесением пользователем средств на счет хранения, включая ценообразование и расчет доли, до момента вывода средств. Идентифицируйте каждый адрес, предоставленный вызывающей стороной, и каждый адрес, загруженный из хранилища. Выясните, какие значения считаются достоверными: оракул, токен, сообщение-мост, хранитель, получатель обратного вызова или администратор. Наиболее ценными объектами для анализа являются функции, которые сочетают в себе ввод данных, контролируемый пользователем, привилегированное состояние, арифметические операции и внешний вызов.
Шаг 3: Выполните чистую компиляцию и статический анализ.
Статический анализ позволяет быстро выявить потенциальные проблемы, которые еще предстоит подтвердить на основе фактического кода и модели угроз.
Воспроизведите сборку проекта с указанной версией Solidity, версиями зависимостей, конфигурацией оптимизатора и предположениями целевой цепочки. Рассматривайте предупреждения компилятора как пункты проверки, а не как безобидный шум. В целях безопасности Solidity настоятельно рекомендуется серьезно относиться к предупреждениям, сохранять понятность контрактов и проверять известные проблемы компилятора. Обращайтесь к официальному списку известных ошибок компилятора Solidity, если версия компилятора или затронутые шаблоны кода делают это актуальным.
Для проектов типа Hardhat, Foundry или аналогичных запустите Slither из корневой папки проекта. В официальной документации инструмент описывается как статический анализатор Solidity и Vyper и приводится распространенная команда:
slither .
Сохраните результаты и проанализируйте каждый из них по степени влияния и достоверности. Внимательно изучите результаты, касающиеся произвольной отправки токенов, незащищенных обновлений, повторного входа, непроверенных возвращаемых значений, опасных вызовов делегатов, tx.origin, слабой случайности и некорректных интерфейсов. Детектор может выдать ложноположительный результат, пропустить специфическую для проекта экономическую ошибку или отметить код, который намеренно ограничен в других местах. Статический анализ сужает область поиска; он не заменяет ручное рассуждение. В репозитории и документации Slither также указаны принтеры для точек входа, авторизации, графов вызовов и сводок контрактов, которые помогают организовать проверку.
Шаг 4: Вручную отследите внешние вызовы и повторное подключение.
Перед обновлением баланса выделен внешний вызов, иллюстрирующий вопрос, который рецензент должен проверить на каждом этапе вывода средств.
Для каждого внешнего вызова остановите и отследите состояние до, во время и после вызова. Вызываемым объектом может быть вредоносный контракт, токен с перехватчиками, получатель обратного вызова или другой протокол, изменяющий общую зависимость. В документации Solidity объясняется, что взаимодействие с другим контрактом может передать управление этому контракту, и рекомендуется использовать шаблон «Проверки-Эффекты-Взаимодействия»: сначала проверка, затем обновление состояния этого контракта, и последнее взаимодействие с внешними объектами.
Не ограничивайте поиск очевидными переводами Ether. Проверьте хуки в стиле ERC-777, коллбэки ERC-1155, коллбэки для мгновенных займов, произвольные маршрутизаторы, вызовы оракулов и вызовы, выполняемые через унаследованные библиотеки. Проверьте реентрантность между функциями и контрактами: коллбэк может войти в другую функцию, которая считывает промежуточное состояние. Убедитесь, что каждый низкоуровневый вызов проверяет результат успешного выполнения и корректно обрабатывает возвращаемое значение. Спросите, может ли получатель, которому не удалось осуществить перевод, навсегда заблокировать вывод средств или замкнуть цикл.
Для каждой возможной ситуации запишите конкретную последовательность действий злоумышленника. Например: злоумышленник вносит депозит, начинает вывод средств, получает обратный вызов, повторно вводит данные для вывода средств и только после этого позволяет завершить первый вызов. Если последовательность не может быть реализована из-за какого-либо конкретного инварианта или условия, запишите эту причину. Это позволит проверить вывод, а не делать его предположительным.
Шаг 5: Проверка арифметических и бизнес-инвариантов
Контрольный список, учитывающий арифметические вычисления и бизнес-логику, выделяет крайние случаи, которые часто упускаются из виду при обычных тестах "счастливого пути".
Проверьте значение каждой единицы измерения и преобразования: вей против эфира, десятичные дроби токенов, базисные пункты, акции против активов, значения со знаком и единицы времени. Следуйте указаниям округления. Деление, округляемое в пользу вкладчика, заемщика, ликвидатора или получателя комиссии, может привести к потере стоимости при повторном выполнении. Перед делением проверьте умножение, минимальные и максимальные суммы, ограничения на размер комиссии, устаревшие цены, нулевое предложение, нулевой баланс и первого вкладчика или последнего снимающего средства.
В Solidity 0.8 и более поздних версиях обычно обнаруживаются арифметические ошибки переполнения и недополнения, но код внутри uncheckedблока намеренно изменяет это поведение. Проверенные арифметические операции также могут привести к возврату протокола к исходному состоянию или сделать его непригодным для использования, если ограничения заданы неправильно. Проверьте оба исхода: кражу или некорректный учет, а также отказ в обслуживании, вызванный значением, которое никогда не может быть обработано.
Прежде чем превращать инварианты в тесты, напишите их простым языком. Примеры включают: «общее количество акций соответствует активам согласно указанному правилу округления», «пользователь не может вывести больше, чем указано в его требованиях», «общее количество токенов равно сумме балансов, к которым применяется данная модель», и «комиссия не может превышать установленный лимит». Сравнивайте балансы хранилища с фактическими балансами токенов, поскольку токены могут быть отправлены непосредственно в контракт или вести себя иначе, чем предполагаемая реализация ERC-20.
Шаг 6: Проверка прав доступа и возможности обновления.
При проверке привилегий необходимо связать каждую роль с её адресом, разрешенными действиями, процессом передачи и путем обновления.
Создайте матрицу привилегий. Для каждой административной функции определите требуемую роль, текущего владельца, механизм передачи, задержку, мультиподпись или контроль управления, а также поведение в чрезвычайных ситуациях. Обратите особое внимание на выпуск токенов, приостановку, изменение комиссий, изменение источников оракулов, спасение средств, обновление кода и изменение адресов доверенных токенов или маршрутизаторов. В документации OpenZeppelin по контролю доступа различаются простое владение и разрешения на основе ролей, а принцип наименьших привилегий описывается как полезная практика обеспечения безопасности.
Разграничьте утверждения «код позволяет администратору сделать это» и «это может сделать любой пользователь». Первое может представлять собой явный риск управления или хранения данных; второе — уязвимость авторизации. Убедитесь, что проверки ролей охватывают все конфиденциальные пути, включая внутренние вспомогательные функции, доступные из общедоступных функций. Проверьте, может ли администратор по умолчанию предоставлять себе или другим дополнительные полномочия, и может ли передача прав собственности быть случайно отправлена на неиспользуемый адрес.
Для прокси-серверов следует изучить инициализатор, авторизацию реализации, задержку обновления, структуру хранилища и план отката или аварийного восстановления. В руководстве OpenZeppelin по обновляемым контрактам объясняется, почему конструкторы не инициализируют хранилище прокси-сервера, почему инициализаторы должны быть защищены, почему реализация не должна оставаться неинициализированной и почему изменение порядка или типов хранилища может привести к сбою обновления. Рассматривайте ключ администратора прокси-сервера как часть границы безопасности протокола, а не как деталь реализации.
Шаг 7: Протестируйте систему с помощью фаззинга, инвариантов и форков.
Проходящие мимо кампании являются полезным доказательством, в то время как контрпример показывает, какая именно последовательность требует исследования.
Проведите модульные тесты для проверки ожидаемого поведения, затем добавьте негативные тесты для неавторизованных вызовов, нулевых значений, максимальных значений, просроченных подписей, устаревших данных оракула, неудачных передач и повторяющихся операций. Используйте фаззинг для проверки входных данных, а не ограничивайтесь тестированием нескольких выбранных чисел. Включите несколько действующих лиц и вредоносные контракты получателей там, где это позволяет архитектура обратных вызовов.
Используйте инвариантное тестирование для свойств, которые должны оставаться истинными после множества случайных вызовов. В документации Foundry по инвариантному тестированию описаны случайные последовательности, фаззированные входные данные, запуски, глубина, целевые контракты и целевые отправители. Настройте обработчики таким образом, чтобы вызовы были осмысленными; если каждый фаззированный депозит отменяется, потому что у тестового актора нет токенов, прохождение инвариантного тестирования может просто означать, что полезное состояние не изменилось.
По возможности используйте форк целевой сети для проверки развернутых адресов, текущей конфигурации, поведения токенов и маршрутизации через прокси. Тесты форка должны быть безопасными и доступными только для чтения, если вы не используете изолированный локальный форк. Минимизируйте количество ошибок и сохраняйте контрпример, адреса вызывающих сторон, контекст блока, балансы и соответствующие значения хранилища. Успешный тест свидетельствует о проверенных путях, а не о всех возможных путях.
Шаг 8: Опишите выявленные недостатки, которые можно исправить и повторно проверить.
Полезный отчет связывает степень тяжести и статус с имеющимися данными, конкретным решением и условиями повторного тестирования.
Для каждого вопроса используйте одну запись. Практическое заключение должно содержать:
Название и местоположение: контракт, функция, файл и ссылка на строку или код.
Последствия: что может быть украдено, заморожено, завышено, обойдено или искажено.
Предварительное условие: необходимые разрешения, балансы, сроки или конфигурация.
Воспроизведение: короткая последовательность транзакций, проверка, отслеживание или подтверждение.
Рекомендация: внесение конкретных изменений в код или операционные процессы с учетом компромиссов.
Статус: открыто, исправлено, смягчено, риск принят или не воспроизводится.
Повторное тестирование: точное тестирование или наблюдение, подтверждающее разрешение проблемы.
Степень серьезности должна отражать реальное воздействие и возможность эксплуатации уязвимости, а не то, насколько тревожно выглядит шаблон кода. Объясните допущения. Низкоуровневый вызов может быть безопасным благодаря строгому инварианту; казалось бы, обычное изменение параметра может быть критическим, если оно управляет оракулом или обновлением. После исправления просмотрите различия, повторно запустите соответствующий тест, повторно запустите полный набор тестов и проверьте наличие регрессий. Если развернутый адрес уже был обновлен или изменен, повторно протестируйте фактическую реализацию и конфигурацию в блокчейне.
Распространенные ошибки при проведении аудита, которых следует избегать.
«Исходный код проверен, поэтому он безопасен». Проверка устанавливает соответствие между исходным кодом и байт-кодом; она не подтверждает корректность проекта.
«Сканер ничего не обнаружил, значит, ошибок нет». Инструменты наиболее эффективны при выявлении известных закономерностей, в то время как экономические и междоговорные недостатки часто требуют анализа человеком.
«Проект имеет аудиторский отчет, поэтому текущая версия развертывания охватывается». Сравните данные из отчета: коммит, область действия, адреса развертывания, исправления и историю обновлений.
«Фазз-тесты пройдены, поэтому инвариант верен». Сначала убедитесь, что инвариант выражает предполагаемое экономическое свойство и что обработчики достигают осмысленных состояний.
«Административный контроль не представляет угрозы безопасности». Возможно, это намеренное предположение о доверии, но пользователи должны иметь возможность видеть, кто может создавать новые файлы, приостанавливать их выпуск, изменять параметры или обновлять.
Перед тем как довериться результату, проведите последнюю самопроверку.
Вы должны быть в состоянии ответить утвердительно на следующие вопросы:
Записал ли я точную цепочку, адрес, байт-код, прокси, реализацию и параметры сборки?
Провел ли я инвентаризацию всех точек входа, изменяющих состояние системы, и активов, на которые они могут повлиять?
Проверил ли я компиляцию на отсутствие ошибок, просмотрел ли предупреждения и обработал ли автоматические результаты?
Проследил ли я каждый внешний вызов, обратный вызов, низкоуровневый вызов и путь сбоя?
Проверял ли я округление, пределы, нулевые значения, устаревшие данные и повторяющиеся действия?
Удалось ли мне настроить все привилегированные роли, ключи, задержки, инициализаторы и пути обновления?
Сохранил ли я осмысленные нечеткие представления и инвариантные контрпримеры?
Может ли независимый эксперт воспроизвести каждое обнаруженное нарушение и проверить каждое исправление?
Если какой-либо ответ отрицательный, пометьте аудит как неполный и укажите недостающие доказательства. Четкое ограничение полезнее, чем расплывчатый вывод о «безопасности». Безопасность смарт-контрактов — это непрерывный процесс: каждое обновление, изменение зависимостей, новая интеграция и изменение привилегий могут создавать новые границы проверки.