2000000$ за найденную уязвимость
Прежде чем анализировать ошибку, важно понять, как работает Plasma и Polygon, по крайней мере, на высоком уровне.
Сначала немного терминологии, кто знаком может пропустить:
Дерево Меркла - полное двоичное дерево, в листовые вершины которого помещены хеши от блоков данных, а внутренние вершины содержат хеши от сложения значений в дочерних вершинах. Корневой узел дерева содержит хеш от всего набора данных, то есть хеш-дерево является однонаправленной хеш-функцией.
Решения второго уровня/слоя (L2-решения) — это проекты, приложения и технологии инфраструктурного программного обеспечения, развернутые поверх базовых блокчейнов.
Сайдчейны — технология, позволяющие токенам и другим цифровым активам одного блокчейна безопасным образом использоваться в другом блокчейне и затем (в случае необходимости) быть возвращенными в оригинальный блокчейн. Изначально концепция сайдчейнов (side chain) была описана в 2014 году в «белой бумаге» (white paper), который написали разработчики компании Blockstream. Сайдчейн (side chain) представляет собой отдельный блокчейн с двусторонней привязкой к родительскому блокчейну. Это обеспечивает взаимозаменяемость активов. Родительский блокчейн обычно называется «основной (главной) цепью» (master chain), дополнительные цепи — сайдчейнами (side-chain). Пользователь родительского блокчейна должен сначала отправить монеты на выходящий адрес, где они «запираются» участниками так называемой «федерации», что призвано исключить возможность их траты в другом месте. По завершении транзакции ее участники получают подтверждение, однако для дополнительной безопасности это происходит после некоторого периода ожидания. После этого эквивалентное количество монет переводится в сайдчейн (side chain), и у пользователя появляется возможность их потратить. При отправке монет из сайдчейна (side chain) в основной блокчейн происходит обратный процесс.
Fraud proof - модель безопасности для решений второго уровня, в которых для повышения скорости из транзакций формируются пакеты и отправляются в Ethereum за одну транзакцию. Они считаются валидными, но могут быть оспорены по определенным правилам. Этот метод увеличивает количество возможных транзакций при сохранении достаточного уровня безопасности.
Plasma — решение второго уровня для масштабирования сети Ethereum, изначально предложенное Джозефом Пуном и Виталиком Бутериным. Технология предусматривает использование смарт-контрактов и деревьев Меркла для создания неограниченного числа дочерних цепей — копий родительской сети Ethereum. Они разгружают основной блокчейн, открывая возможность осуществления быстрых и недорогих транзакций. По принципу работы Plasma похожа на Lightning Network: набор умных контрактов, позволяющий множеству сайдчейнов фиксировать свое состояние в сжатом виде в корневом блокчейне. Plasma применяет экономические стимулы, включая наказание отвергнутого сетью создателя блока для предотвращения мошенничества. В июне 2020 года работающая над масштабированием Ethereum компания OMG Network запустила бета-тестирование OMG Network V1, базирующейся на спецификациях последней версии Plasma — More Viable Plasma (MoreVP). MoreVP масштабирует Ethereum, группируя транзакции и отправляя их через набор смарт-контрактов. Сгруппированные данные проходят верификацию и валидацию в децентрализованной сети хранителей. Группирование позволяет увеличить пропускную способность сети до тысяч транзакций в секунду и значительно сократить их стоимость. Это отдельный блокчейн, который привязан к основной цепочке Ethereum и использует fraud proofs (Optimistic rollups) для разрешения споров.
Matic Network (теперь Polygon) — решение второго уровня с поддержкой фреймворка Plasma и децентрализованной сети PoS-валидаторов. Проект использует сайдчейны (side chain) для офчейн-вычислений. В начале 2021 года состоялся ребрендинг проекта. Сменилось название на Polygon, изменилась стратегия в сторону создания мультичейн-системы, похожей на Polkadot. Команда проекта работает над созданием решений второго уровня на базе Ethereum. Polygon - это протокол и платформа для создания и подключения блокчейн-сетей, совместимых с Ethereum. Платформа предлагает «надежный двусторонний канал транзакций» между Polygon и Ethereum. Этот «блокчейн-мост» использует сеть под названием Plasma для аутентификации и обработки вывода средств. Именно в контракте на Plasma, доверенном лице управляющего депозитами, был обнаружен недостаток.
Архитектура Polygon Network
В архитектуре протокола содержатся 3 абстрактных слоя:
- Polygon Ethereum смарт-контракты - набор смарт-контрактов на Ethereum, которые обрабатывают:
- Стейкинг (Staking) для поддержания Proof-of-Stake;
- Управление делегированием, включая общие ресурсы валидатора;
- Plasma контракты для MoreVP, включающие checkpoints/snapshots для сайдчейна (side chain).
- Heimdall (Proof of Stake layer) – PoS валидатор, который работает на основе Ethereum-контрактов для реализации PoS механизма в Polygon. Он отвечает за проверку блоков, продюсер (block producer).
- Bor (Block producer layer) – ответственный за объединение транзакций в блоки. В настоящее время это базовая реализация Geth с некоторыми изменениями, внесенными в алгоритм консенсуса.
На приведенной ниже диаграмме показан поток пользовательских средств через Polygon network (source):
- Пользователь вносит средства в контракт моста в L1 (основной сети Ethereum);
- После подтверждения транзакции депозита в сети Ethereum, токены доступны в сети Plasma, чтобы пользователи могли их передавать;
- Когда пользователь решает вывести средства обратно на L1, токены необходимо сжечь в Plasma. Нужно дождаться контрольной точки. Затем контрольная точка отправляется в мастер-чейн (master chain);
- Когда контрольная точка пройдена, выпускаются Exit NFT (ERC721);
- Далее необходимо подождать 7 дней;
- После 7-дневного периода проверки средства могут быть выведены обратно пользователю на L1.
Такой механизм типичен для fraud proof систем, поскольку они предполагают, что определенные действия являются действительными, и отменяют их только в том случае, если другой пользователь может доказать, что они были недействительными.
Разбор уязвимости
Логика вывода средств обратно в блокчейн Ethereum реализована, в частности, в контракте ERC20PredicateBurnOnly. Выход можно запустить, вызвав функцию startExitWithBurntTokens из данного контракта. В контракте WithdrawManager, также отвечающего за вывод средств, в функции verifyInclusion в строке 115 происходит проверка, включена ли квитанция в дерево. Уязвимость в том, как Polygon WithdrawManager проверяет включение и уникальность в предыдущих блоках.
Одним из параметров, который содержит доказательство выхода, является branchMask. branchMask должна быть уникальной, так как она используется для генерации идентификатора выхода (Exit ID). Важное свойство - одна транзакция есть один идентификатор выхода, но это свойство может быть нарушено. Если проверки пройдены, то идентификатор выхода (Exit ID) создается на основе метки времени, номера блока контрольной точки и branchMask, см. строку 158.
Идентификатор выхода возвращается и сохраняется в приоритетной очереди, которая упорядочивает все выходы в зависимости от их возраста. Когда выход находится в очереди приоритетов не менее 7 дней с момента транзакции, на которую он ссылается, он может быть обработан, и средства возвращаются пользователю в L1.
Идентификатор выхода частично состоит из branch mask, которая поступает непосредственно из пользовательского ввода, что является слабым местом. Если бы было возможно создать альтернативную branch mask, которая каким-то образом способна создать более одного идентификатора выхода, относящегося к одной и той же транзакции. Более пристальный взгляд на функцию в библиотеке MerklePatricaProof показывает, что что-то происходит с путем до того, как будет пройден цикл из строки 40. В строке 35 branch mask передается в функцию _getNibbleArray.
Проверка функции _getNibbleArray показывает, что branch mask (аргумент функции encodedpath) закодирована (Hex Prefix encoded).
Функция _getNibbleArray проверяет с какого значения начинается переданный набор. Если первая часть байта (цифра) 1 или 3, то учитывается вторая часть первого байта и декодируются все оставшиеся значения, иначе первый байт игнорируется. Давайте рассмотрим на примере и предположим, что branch mask равна 0x00819c. Первый байт отбрасывается, так как он – 0, и его первая часть не равна 1 или 3, а остальные фрагменты расширяются до 0x0801090c.
Также в функции verifyInclusion преобразуется branchMask в uint256.
Так как getNibbleArray отбрасывает часть информации, то получаем, что одному значению декодирования getNibbleArray соответствуют несколько uint256 значений. В общей сложности можно создать 14x16 = 224 различных кодировок для одного и того же пути. Злоумышленник может использовать эту проблему для создания альтернативных выходов для одной и той же транзакции и выполнения двойных трат в сети Polygon.
Влияние
Для успешной атаки существуют требования к капиталу, но они минимальны по сравнению с потенциальным вознаграждением злоумышленника. Давайте рассмотрим пример и поймем, сколько мог бы получить злоумышленник по сравнению со средствами, необходимыми для атаки.
- депонирует токены на сумму 200 000 долларов США в контракт DepositManager;
- запускает процесс вывода выхода (withdrawal);
- ожидает семидневный период проверки;
- запускает вывод, переиспользуя предыдущие данные с модифицированным параметром branch mask.
С помощью описанной выше техники генерируются 223 альтернативных выходных полезных нагрузки, и для каждой из них инициируются выходы. Все выходы получают уникальный идентификатор и добавляются в очередь. После того, как все выходы были запущены и обработаны, злоумышленник получает сумму, в 223 раза превышающую сумму первоначального депозита или токенов на сумму 44,6 миллиона долларов.
Исправление
Уязвимость была устранена путем отклонения любой кодировки, которая не начинается с 0x00. Это не очень элегантно, но исправляет ошибку с двойными расходами, жестко кодируя метасимвол кодировки.