Чек-лист и отчёт: как сдавать безопасность
- Зачем это нужно
- Пять находок в своём контракте
- Три корзины
- Как оценивают серьёзность
- Чек-лист по контракту
- Чек-лист по интерфейсу
- Из чего состоит находка
- Что показывать на защите
- Типичные ошибки
- Слова главы
- Тренажёр: набрать вслепую
- На чемпионате
- Куда углубиться
- Экзамен по блоку «Безопасность»
- Куда дальше: проверенные ресурсы
Зачем это нужно
Уязвимости разобраны, инструменты запущены. Осталось самое неочевидное: что делать с находками и как о них рассказать.
Список из вывода инструмента — не отчёт. В нём нет главного: что из этого опасно, что «так задумано», и что вы с этим сделали. Ровно про это спрашивают на защите, и ровно здесь видно, понимает человек предмет или просто запустил команду.
В этой главе разбор идёт на нашем собственном контракте — том самом хранилище, которое мы писали, тестировали и разворачивали.
Пять находок в своём контракте
1. setRate не сообщает о смене ставки missing-events-arithmetic
2. событие отправляется после внешнего вызова reentrancy-events
3. низкоуровневый вызов low-level-calls
4. имя параметра с подчёркиванием naming-convention
5. owner можно сделать неизменяемым immutable-states
Первое наблюдение, ради которого всё это показано: ни одна из пяти не даёт украсть деньги. Контракт писался по правилам из главы про уязвимости — порядок операций правильный, права проверяются, — и анализатор это подтвердил молчанием там, где было бы опасно.
Второе: четыре из пяти всё равно стоит исправить. Не потому, что страшно, а потому, что каждая делает контракт чуть хуже: без события о смене ставки внешние наблюдатели не узнают о ней; событие после внешнего вызова может встать в журнале не туда; неизменяемое поле дешевле читать.
── после правок ──
находок: 5 → 1
тестов проходит: 16 из 16
Осталась одна — про низкоуровневый вызов. Она не исправляется: перевод эфира так и делается. Это находка из третьей корзины.
Три корзины
Исправить. Настоящая проблема или просто улучшение, которое ничего не стоит. Большинство находок сюда.
Объяснить. Инструмент прав формально, но так задумано. Низкоуровневый вызов вместо transfer — именно этот случай: transfer устарел из-за жёсткого предела газа. Ответ должен быть готов вслух, а не «ну это нормально».
Отклонить. Ложное срабатывание: инструмент не понял контекст. Редко, но бывает — и тогда в отчёте пишут, почему.
«Исправить всё» — плохая стратегия. Часть находок носит характер «можно и так, и так», и правка ради зелёного вывода иногда портит код. Гораздо ценнее короткий список из двух-трёх пунктов «оставлено сознательно, потому что…».
Как оценивают серьёзность
Серьёзность — не свойство ошибки, а произведение двух вещей: насколько велик ущерб и насколько легко его причинить.
- Критично — деньги уходят, и сделать это может кто угодно.
- Средне — либо ущерб меньше (работа встаёт, но средства целы), либо нужно стечение обстоятельств.
- Мелочь — неудобство, или требуется доступ, который и так всё ломает.
Типичная ошибка новичка — объявлять критичным всё, что теоретически возможно. «Владелец может установить ставку 100 %» — это не уязвимость: владелец и так владелец. А вот «ставку может установить кто угодно» — критично, потому что кто угодно.
Чек-лист по контракту
Проходят сверху вниз, для каждой функции:
- Права. У каждой изменяющей функции есть ограничение? Проверено тестом с запрещённым счётом?
- Порядок. Нет вызова наружу до записи в хранилище?
- Результаты вызовов. Признак успеха проверен везде?
- Начальная настройка. Нельзя вызвать дважды? Нулевой адрес отсекается?
- Арифметика. Деление на ноль? Потеря остатка? Блоки без проверок переполнения?
- Время и случайность. Ничего важного не решается по метке времени блока и не берётся из блока как «случайное»?
- Списки. Нет обхода списка неограниченной длины в изменяющей функции?
- Приём эфира. Что будет, если получатель откажется принимать?
- События. Каждое изменение состояния видно снаружи?
- Обновление. Если есть прокси — раскладка хранилища не съедет? Настройка защищена от повторного вызова?
Чек-лист по интерфейсу
Про эту половину забывают, а спрашивают и о ней:
- Ключей в коде нет. Ни в исходниках, ни в переменных сборки.
- Чужие строки вставляются текстом. Адрес, имя токена, подпись операции приходят из сети.
- Адрес получателя показывается целиком перед подписью.
- Номер сети проверяется перед каждой операцией, меняющей состояние.
- Контрольная сумма адреса проверяется при ручном вводе.
- Кнопка выключается на время операции.
- Отказ пользователя не показывается как ошибка.
- Отказ контракта переводится на человеческий язык.
Из чего состоит находка
### С-03. Событие отправляется после внешнего вызова
**Где:** `Vault.sol:33`, функция `withdraw()`
**Что:** `emit Withdrawn(...)` стоит после `msg.sender.call{value: amount}("")`
**Чем грозит:** при повторном входе порядок событий в журнале перепутается;
внешний наблюдатель увидит операции не в том порядке, в каком они произошли
**Серьёзность:** низкая — средства не затрагиваются
**Как чинить:** перенести `emit` выше вызова
**Статус:** исправлено, тесты проходят
Находка без «чем грозит» и «как чинить» — это не находка, а строчка из вывода инструмента. Именно эти две части показывают, что вы поняли проблему, а не скопировали её.
Что показывать на защите
Порядок, который занимает три минуты и закрывает большинство вопросов:
- Вывод компилятора — пустой. Одна команда.
- Вывод анализатора — и разбор. «Пять находок, четыре исправлены, одна оставлена сознательно, вот почему».
- Тест-нападение. «Вот контракт, который пытается опустошить хранилище. Вот тест, где он не смог».
- Утверждения со случайными данными. «Никто не может забрать больше, чем внёс — проверено на 256 наборах».
- Покрытие. «Сто процентов, непокрытых строк нет».
- Чек-лист по интерфейсу. Восемь пунктов, каждый показывается на экране за секунду.
Это не «рассказ про безопасность» — это демонстрация. Разница видна сразу.
Типичные ошибки
Прикладывают вывод инструмента как отчёт. Без разбора это выглядит как «мы запустили команду».
Исправляют всё подряд. Часть правок ухудшает код; отсутствие пункта «оставлено сознательно» читается как неумение отличать важное.
Называют критичным всё теоретически возможное. Обесценивает настоящие находки.
Пишут название детектора вместо последствия. «reentrancy-events» не объясняет ничего.
Проверяют только контракт. Половина требований — про интерфейс.
Статус находки пустой. Читается как «не разбирались».
Не готовят ответ вслух. На защите спрашивают именно про оставленные находки.
Слова главы
- Разбор находок — три корзины: исправить, объяснить, отклонить.
- Серьёзность — ущерб, умноженный на доступность; не свойство самой ошибки.
- Критично — деньги уходят, и сделать это может кто угодно.
- Оставлено сознательно — находка, на которую есть готовый ответ.
- Строение находки — где, что, чем грозит, насколько серьёзно, как чинить, статус.
- Последствие вместо названия — не имя детектора, а что случится.
- Чек-лист контракта — десять пунктов по функциям.
- Чек-лист интерфейса — восемь пунктов; про них забывают чаще.
Тренажёр: набрать вслепую
Цель: скорость ≥ 95 зн/мин, точность ≥ 88%
npx hardhat build && npx solhint "contracts/**/*.sol"
На чемпионате
Отчёт — одна страница. Таблица находок со статусами плюс два-три абзаца про то, что проверено. Больше не читают.
Оставленные находки — с готовым объяснением. Это тот самый вопрос, который задают.
Тест-нападение показывайте первым. Он производит впечатление сильнее всего остального вместе взятого.
Чек-лист интерфейса пройдите вслух сами. Восемь пунктов, минута времени — и половина вопросов снята заранее.
И общее: безопасность на чемпионате — это не отсутствие уязвимостей, которое всё равно никто не докажет. Это демонстрация того, что вы знаете, где они бывают, и проверили эти места. Разница между «у нас всё безопасно» и «вот шесть мест, где обычно теряют деньги, и вот что мы сделали в каждом» — это разница в оценке.
Куда углубиться
- Публичные отчёты аудиторов — как выглядит настоящий документ на сорок страниц.
- Оценка серьёзности по общепринятым шкалам — и чем они отличаются друг от друга.
- Соревнования по поиску уязвимостей — формат, где находки оценивают деньгами.
- Повторный аудит после правок — почему исправление одной находки иногда создаёт другую.
- Мониторинг после запуска — что делать, когда контракт уже в сети и уязвимость нашлась.
Сводная проверка по всей главе: 8 вопросов, 5:00 на всё, без подсказок по ходу. Вопросы показываются по одному, ответ изменить нельзя. Если время выйдет — экзамен завершится с тем, что успел ответить. Пересдавать можно сколько угодно раз.
Экзамен по блоку «Безопасность»
Три главы: уязвимости, инструменты, отчёт.
Сводная проверка по 3 главам блока: 15 вопросов, 10:00 на всё, по одному вопросу, без возврата. Пересдавать можно сколько угодно раз.
Готовность: экзамены глав сданы 0 из 3. Блок можно сдавать и так, но шанс выше после всех глав.
Куда дальше: проверенные ресурсы
- OpenZeppelin: публичные отчёты аудитов — как выглядит настоящий документ.
- Code4rena: отчёты — находки с оценкой серьёзности и обоснованием.
- Smart Contract Security Verification Standard — развёрнутый чек-лист.
- Solidity: рекомендации по безопасности — от авторов языка.
- Building Secure Contracts: рабочий процесс — порядок применения инструментов.
Комментарии
Обсуждение живёт в GitHub Discussions и грузится с чужого домена — поэтому только по нажатию.