Skip to content
Yieldo

Parity Multisig

$150.0M
Украдено средств
Возвращено средств
09 Nov 2017
Дата инцидента
Техника
Uninitialized Proxy
Классификация
Access Control
Тип цели
DeFi Protocol
Затронутые сети
Ethereum
Источник
Нет публичной ссылки на источник

6 ноября 2017 года аккаунт в сети Ethereum вызвал самоуничтожение общего библиотечного контракта, на котором держались мультисиг-кошельки Parity. Поскольку сотни отдельных кошельков делегировали свою логику одному и тому же контракту через delegatecall, его гибель мгновенно заблокировала все зависимые кошельки. По данным TechCrunch, писавшего об этом на следующий день, оказалось заморожено около 513 000 ETH — на тот момент около $150 млн — в почти 600 кошельках, включая значительную часть средств, собранных Web3 Foundation на ICO. Ничего не было украдено: средства просто стали навсегда недоступны.

Как устроена атака

Кошельки Parity были «тонкими» контрактами без собственной логики: каждый вызов через delegatecall уходил в один общий библиотечный контракт, который и выполнял проверку владельцев, подсчёт подписей и переводы средств. Эта архитектура уже подводила проект за четыре месяца до этого: в июле 2017 злоумышленник напрямую вызвал функцию initWallet библиотеки, стал единственным владельцем трёх конкретных кошельков и вывел из них около 153 000 ETH — механизм разобран в постмортеме OpenZeppelin по тому инциденту.

Патч, выпущенный после июльского взлома, закрыл повторную инициализацию уже настроенных кошельков — но не тронул саму библиотеку, которая никогда не была инициализирована и потому формально имела ноль владельцев. В ноябре кто-то вызвал initWallet напрямую у библиотеки: проверка на «уже есть владелец» прошла тривиально, потому что владельца никогда не было, и вызывающий стал владельцем библиотеки. Дальше он, уже как владелец, вызвал функцию самоуничтожения контракта. Поскольку весь исполняемый код лежал именно в библиотеке, а зависимые кошельки хранили только ссылку на неё, они остались обращаться к адресу без кода — навсегда потеряв возможность двигать средства, как описывает второй постмортем OpenZeppelin. Это и есть паттерн «неинициализированный контракт»: уязвимым состоянием оказался не кошелёк пользователя, а базовая библиотека, которую все молча считали уже закрытой.

Что было дальше

Parity предложила EIP-999 — разовое изменение протокола, которое бы заново развернуло байт-код уничтоженной библиотеки по старому адресу, не перемещая никаких средств. Сообщество Ethereum восприняло это не как техническую починку, а как прецедент, достойный форка. Сигнальное голосование дало 330 голосов против и 300 за, а основатели Parity публично исключили попытку раскола сети ради этого — по данным CoinDesk. После того как предложение отклонили, никакого спасения на уровне кода не последовало — ETH до сих пор заблокирован на адресе мёртвой библиотеки.

Что из этого следует

  • Любая архитектура, где логика вынесена в общий контракт-библиотеку, вызываемый через delegatecall, делает уязвимой именно эту библиотеку, а не отдельные кошельки — её собственный контроль доступа должен быть не слабее, чем у любого кошелька.
  • Патч, закрывающий один конкретный путь эксплуатации без аудита всех остальных публичных функций того же контракта, может оставить открытым другой, не связанный с первым путь — июльский фикс закрыл одну дверь, оставив незапертой парадную.
  • У держателей средств не было видимого сигнала опасности: владельцы кошельков не могли знать, что их деньги зависят от состояния контракта, с которым они сами никогда напрямую не взаимодействовали.
  • Реакция управления Ethereum показала: «восстановить уничтоженный код хардфорком» сообщество восприняло принципиально иначе, чем более ранние вмешательства вроде форка The DAO — код-как-закон оказался устойчивее, когда острота непосредственного кризиса уже спала.

On-chain DeFi несёт риск смарт-контрактов. Держите основные средства на регулируемой бирже.