Hack Parity MultiSig кошелька

Eduard Karionov
Cloverr Software Development
6 min readNov 25, 2017

--

7 ноября произошла очередная катастрофа в мире смарт контрактов на Ethereum. После июльского взлома кошелька Parity MultiSig, мы стали свидетелями еще одной серьезной атаки на контракт Parity MultiSig. Предполагаемые потери, по-видимому, составляют порядка 500K + ETH (это примерно 150 миллионов долларов USA), огромная сумма заморожена у проекта PolkaDot , а так же около $ 300 тысяч из команды Web3 Foundation.

У меня уже есть пост о том как разворачивалась события и я хотел бы сосредоточиться на технических деталях проблемы.

Короткое резюме.

Неизвестный хакер удалил библиотеку контракта от которой зависело множество других контрактов кошельков, где лежало очень ощутимая сумма Ether. В результате чего Ether на этих кошельках оказались замороженными и, скорее всего, если не будет предпринято каких-то экстраординарных усилий вроде хардфорка, как было в 2016 году после хака The DAO, эти средства так и останутся навсегда там лежать замороженными и их можно считать потерянными.

Что произошло?

Что бы понять что произошло с точки зрения технологии, давайте сначала разберемся, что вообще такое MultiSig кошелек и зачем он нужен. Вы вероятно знаете, в Ethereum есть два типа аккаунтов — аккаунты, которые контролируется приватным ключом, аналогично адресам в биткоине, в Ethereum они называются Externally Owned Account (EOAs), и аккаунты, которые контролируются смарт контрактом, неким кодом, который находиться в блокчейне по определенному адресу, у такого аккаунта есть свой баланс, и этот баланс может изменяться согласно коду смарт контракта.

Если вы используйте Ether как криптовалюту аналогично Bitcoin, к примеру вы хоте просто получать и отправлять деньги, то в принципе смарт контракты вам не нужны. Вы можете завести себе просто обычный аккаунт (кошелек), получать и отправлять кому-то Ether, в том числе отправлять Ether на контракты и всё будет прекрасно работать. Но здесь есть небольшая проблема и заключается она в том, что если ваш компьютер будет скомпрометирован, вы потеряете приватные ключи или если ваши приватные ключи попадут в руки злоумышленников, то ваш кошелёк может быть опустошен. Поэтому пользователи, у которых на балансе находится существенное количество криптовалюты, обычно используют более сложные методы хранения, в частности MultiSig кошельки, то есть кошельки с множественными подписями. Такая технология не нова, например используя язык Bitcoin scripting, она успешно применяется в Bitcoin и конечно же ее можно реализовать используя смарт контракты в Ethereum.

MultiSig кошелек вместо одного приватного ключа, использует несколько приватных ключей. Чтобы потратить деньги с такого кошелька, нужна подпись нескольких ключей. Одна из наиболее популярных имплементаций MultiSig кошелька для Ethereum реализовала компания Parity под руководством Гэвина Вуда. (Гэвин Вуд сооснователь Ethereum и автор Yellow Paper, изначально работал в Ethereum foundation, но потом основал собственную компанию Parity).

В числе прочего компания Parity разрабатывает клиент Ethereum, клиент Parity, является вторым по популярности после официального клиента GO Ethereum. Помимо имплементаций узла Ethereum, Parity также разрабатывает другие вещи, в том числе MultiSig кошелек

Собственно теперь расскажу как устроен Рarity кошелек, который был взломан. Кошелек Parity MultiSig структурирован как тонкий контракт. Каждый отдельный кошелек не содержит собственной логики, а всего-навсего переправляет все вызовы на другой контракт(библиотеку). И уже в этой библиотеке реализована вся функциональность. Конечно же такой подход, совершенно оправдан, он помогает придерживаться принципа “ не повторяй свой код”. Т.е. мы один раз хорошо пишем функциональность кошелька, деплоим в блокчейн в виде библиотеки, далее все кошельки обращаются к этой библиотеке и всё отлично работает. Parity MultiSig кошелек так и был устроен. Каждый отдельный parity кошелек был просто такой оболочкой, который делегирует все вызовы в основной контракт библиотеки через delegatecall в его резервной функции.

// gets called when no other function matchesfunction() payable {// just being sent some cash?if (msg.value > 0)Deposit(msg.sender, msg.value);else if (msg.data.length > 0)_walletLibrary.delegatecall(msg.data);}

Delegatecall — это механизм вызова внешних контрактов, который исполняет код внешнего контракта, как бы в контексте того контракта, который и вызывает функцию.

То есть, допустим, есть кошелёк Parity, у него есть какой-то баланс, так же у него есть какие-то внутренние переменные. Дальше я вызываю delegatecall и обращаюсь к библиотеки далее код библиотеки выполняется так, как будто он был частью кода моего контракта, т.е. код, который написал в библиотеке он оперирует не с внешним состоянием, а с моим состоянием, балансом и с моими данными.

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

Предполагается, что метод будет вызываться только один раз во время создания контракта кошелька. То есть я хочу завести новый кошелек, я отправляю в Ethereum транзакцию, где написано «Заведи новый кошелек, который ссылается вот на эту библиотеку», и автоматически в момент создания моего контракта, моего кошелька срабатывает функция initWallet в библиотеке, которая назначает меня в качестве владельца этого кошелька.

Я напомню что в июльской атаке на Рarity был использовала тот факт, что метод initWallet был общедоступным и не имел модификаторов,

Поскольку, изначально не было проверки, что это метод вызывается только один раз, не было проверки того, что кошелек еще не был инициализирован. И вместе с тем метод была public. Получалось, злоумышленник просто вызвал initWallet по своим целям, сбросив права собственности на адрес, который он контролировал, а затем перевел средства на свой счет.

Хакер таким образом похитил что-то вроде 30 млн. долларов с кошельков нескольких крупный Ethereum проектов. Интересным путем это проблема разрешилась. Буквально в течение нескольких часов после первого взломанного кошелька, среди разработчиков Ethereum, сформировалась группа так называемых white head hacker, то есть «хороших» хакеров, которые наперегонки с неизвестным злоумышленникам стали с помощью описанной техники взламывать все подряд кошельки, только white head hacker делали это для того, чтобы опередить злоумышленника и похищенные деньги вернуть законным владельцам. Что в конечном итоге и было проделано. И что-то вроде больше половины всех денег, которые содержались на таких кошельках, удалось таким образом спасти.

Как же исправили уязвимость?

Чтобы защитить метод initWallet, был добавлен модификатор, который должен гарантировать, что метод может быть вызван только один раз, во время создания

modifier only_uninitialized { if (m_numOwners > 0) throw; _; }

Таким образом, если злоумышленник пытается вызвать initWallet через механизм delegatecall в любом из тонких контрактов кошелька, он будет отклонен этим модификатором.

То есть такой простой трюк уже не пройдет, нельзя себя назначить владельцем любого кошелька. Но как выяснилось через 4 месяца, уязвимости на этом не закончились.

Новая уязвимость.

Действительно сейчас метод initWallet нельзя было вызвать на конкретных кошельках, но как выяснил некий Ethereum энтузиаст, его можно вызвать на самой библиотеке. Интересные детали заключается в следующем — в Ethereum есть такая вещь, как библиотека — это такая сущность, которая во многом аналогична контракту, но у которой нету своего состояния, и в которой не может быть методов для оплат. То есть это такой чистый кусок функциональности, который не может держать свой баланс и не может запоминать какие-то переменные, это набор функций, которые можно задеплоить в блокчейн, и вызывать их с помощью описанного раньше механизма delegatecall. Конечно, не совсем понятно, почему разработчики Parity решили свой кошелек имплементировать, не в виде именно библиотеки, а в виде контракта, даже сам контракт называется Parity Wallet library contract. Так вот поскольку эта штука имплементирована именно, в виде контракта, это значит, что у неё есть свой баланс и у неё есть свое состояние. И в числе прочего это означает, что ее можно инициализировать из вне как будто это просто кошелек, хотя это библиотека, что собственно и было проделано. Оказалось, что если вызвать initWallet на самой библиотеке, т.е. на контракте который реализует саму функциональность, то он будет проинициализирован, как будто это простой кошелек. И некий пользователь github под ником devops199 это сделал, назначил себя владельцем этой библиотеки. Спрашивается — и что дальше? Казалось бы, никакого вреда он не может нанести. Но не всё так просто. Оказалось, что если ты назначил себя владельцем контракта(библиотеки), ты его можешь убить. И сама атака была простой: убить контракт с библиотекой.

// kills the contract sending everything to `_to`.function kill(address _to) onlymanyowners(sha3(msg.data)) external {suicide(_to); 

Это свою очередь, приводит к тому, что контракт полностью удаляется из блокчейна и больше не на какие транзакции не отвечает, что и было проделано.

В отличии от предыдущего бага, где можно было наперегонки с хакером атаковать кошельки и потом вручную вернуть деньги, здесь кроме хардфорка, видимо, ничего не поможет. Вспоминается, конечно, ситуация с DAO в 2016 году, когда взлом смарт контракта привел к тому, что злоумышленник украл огромное количество эфира. И тогда Ethereum пошел на хардфорк и изменил правила консенсуса, так чтобы по адресу взломанного контрактна появился другой контракт, из которого инвесторы могли вернуть свои похищенные деньги. И как вы, вероятно, знаете, та часть сообщества, которая не согласилась с этим решением продолжила оригинальную цепочку и известна под названием Ethereum Classic. Похоже на то, что никто не хочет повторений такой ситуации.

Вывод

Важно отметить, что метод абстрагирования логики в разделяемой библиотеке может быть весьма полезен. Он помогает избегать повторного использования кода и снижает затраты газа на развертывание контрактов. Однако в этой атаке четко указано, что в экосистеме Ethereum необходим набор передовых методов и стандартов для обеспечения эффективного и безопасного осуществления этих схем кодирования. В противном случае самая невинная ошибка может иметь катастрофические последствия.

--

--

Eduard Karionov
Cloverr Software Development

Crypto / Let’s create a miracle in NTF • Co Founder @anwaNFT & @_CryptoBuddies_ • Composite NFT — It is an NFT consists of other NFTs