Реентрансабельность — атака, которая обманывает смарт-контракт
Суть reentrancy: хакер вызывает функцию контракта до того, как баланс обновится. Логика и защита.
Обновлено: 2026-08-13
Суть reentrancy: хакер вызывает функцию контракта до того, как баланс обновится. Логика и защита.
Обновлено: 2026-08-13
В 2016 году атакующий унёс из проекта The DAO 3,6 миллиона ETH — примерно $50 млн по курсу того дня. Способ не был сложным и не требовал взлома ключей. Контракт честно отдал деньги. Просто атакующий заставил его отдать их дважды. Речь о реентрансабельности (reentrancy) — одном из старейших и до сих пор живущих классов багов в смарт-контрактах.
В нормальном сценарии контракт «Жертва» хранит баланс пользователя, получает запрос на вывод и делает две вещи по очереди: пересылает эфир на кошелёк вызывающего и списывает сумму с внутреннего баланса. Реентрансабельность возникает, когда атакующий вклинивается между этими шагами.
У ethereum-кошельков есть «функция приёма денег» — fallback() (в новом синтаксисе receive()). Когда Жертва пересылает эфир на контракт-атакующий, этот контракт получает управление. Его fallback тут же снова зовёт withdraw у Жертвы. А та к этому моменту ещё не списала баланс — деньги ушли, но учётная запись об этом не знает. Кошелёк отдаёт ещё один эфир. И ещё. Пока стек вызовов не упрётся в лимит газа.
The DAO работал именно так: атакующий разворачивал контракт, вкладывал туда ETH, получал «долю» в DAO и дёргал функцию разделения, которая переводила ETH обратно. Перевод запускал fallback, тот звал разделение снова. Так повторялось, пока не кончался газ — за каждый проход атакующий получал сумму депозита заново, а внутренний баланс у Жертвы так и не доходил до обновления.
Перечисленный механизм — самый известный, но разновидностей больше. Если злоумышленник не зовёт ту же функцию, а зовёт другую публичную функцию до завершения первой — это cross-function reentrancy. Если он попадает в логику стороннего протокола через токен-перевод — cross-contract. Если повторно входит в constructor контракта при развёртывании — read-only, которая не крадёт деньги напрямую, но искривляет состояние.
Цифра, которую стоит держать в голове: до сих пор в рамках аудита на приличных проектах находят от одной до пяти reentrancy-уязвимостей в год. Баг жив, хотя паттерн известен с 2016-го.
Симпатичного универсального решения нет, но практики устоялись.
Checks-Effects-Interactions — основной паттерн. Сначала проверить условия (достаточно ли баланса, разрешён ли вывод), потом сразу изменить внутреннее состояние контракта (уменьшить баланс в учётной системе), и только в самом конце — взаимодействовать с внешним контрактом. Если атакующий вклинится на этом последнем шаге, внутренний баланс уже списан, и повторный вызов уйдёт в отказ.
Эффект усилит ReentrancyGuard — обёртка, популярная в OpenZeppelin. Базируется на локальной переменной-замке: функция ставит флаг в начале, снимает в конце. Повторный вход видит флаг занятым и откатывает. Добавляет пару строк кода и немного газа на хранение состояния.
Для пулов, где логика хитрее баланса, применяют pull over push: вместо того чтобы контракт сам пересылал деньги контрагентам, он «помечает» их к выводу, а пользователи сами забирают свои токены отдельной транзакцией. Взять обратно пометку второй раз не получится — её уже нет.
В эпоху автоматических аудитов (Slither, Mythril, Echidna) тривиальную реентрансабельность ловят статически. Но cross-function и read-only требуют ручного разбора — автомат плохо отличает «плохой» повторный вход от легитимного колбэка.
После The DAO разработка стала осторожнее, и «голый» повторный вход на простом контракте-копилке — редкость. Но протоколы усложнились. Несколько контрактов, связанных через токены и оракулы, часто наследуют чужие зависимости. В июле 2023 года платформа Curve потеряла около $70 млн из-за бага в компиляторе Vyper — ошибка в блокировке реентрансабельности в конкретной версии языка позволила повторно войти в пул. Источник реентрансабельности сместился с «контракт-копилка» на «протокол, использующий другой протокол» — и закрыть его простой блокировкой уже нельзя.
Остались вопросы? Спросите ИИ-ассистента — объяснит другими словами.
Спросить