25 августа 2025 года при содействии Cantina и Seal911 протокол Panoptic провел операцию «белого хакера», обеспечив сохранность активов на сумму около 400 тысяч долларов [1]. Первопричиной стала уязвимость в реализации функции s_positionsHash: протокол использовал операцию XOR для агрегации Keccak256-хэшей идентификаторов позиций в единый «отпечаток». Хотя сами по себе Keccak256-хэши устойчивы к коллизиям, математическая линейность операции XOR делает составной отпечаток небезопасным. Злоумышленник может подобрать набор поддельных идентификаторов позиций, хэши которых через XOR дадут в сумме целевой отпечаток, тем самым обходя проверку протокола и выводя залог без погашения долга.
Общие сведения
Panoptic — это децентрализованный протокол торговли бессрочными опционами на базе Ethereum, который позволяет пользователям торговать опционами пут и колл.
Функция withdraw() принимает параметр positionList, состоящий из набора tokenId. Каждый tokenId представляет собой позицию. Функция withdraw() проверяет долговой статус каждой позиции на основе s_positionsHash, а затем возвращает залог пользователя.
Для экономии газа протокол не хранит каждую позицию (то есть tokenId) пользователя по отдельности. Вместо этого он рассчитывает «отпечаток» на основе всех tokenId пользователя и записывает его в s_positionsHash. Старшие 8 бит каждого s_positionsHash представляют numLegs (количество сегментов), а остальные 248 бит — user position hash (хэш позиции пользователя).
Для каждого переданного tokenId протокол вычисляет его хэш, берет младшие 248 бит и обновляет user position hash путем выполнения побитовой операции XOR с младшими 248 битами текущего s_positionsHash.
Одновременно с этим countLegs корректируется в зависимости от численного диапазона tokenId: он остается неизменным, если tokenId меньше , и увеличивается на 1, 2 или 3 в диапазонах , и соответственно. Эта величина записывается в старшие 8 бит s_positionsHash для обновления numLegs.
Анализ уязвимости
Первопричиной является изъян в алгоритме формирования s_positionsHash, а именно использование операции XOR для агрегации результатов хэширования Keccak256. В то время как сама функция хэширования остается безопасной, математическая линейность XOR делает весь алгоритм формирования отпечатка (т.е. сумму хэшей по XOR) небезопасным [2].
Линейность означает, что злоумышленнику не нужно взламывать саму функцию хэширования (т.е. находить tokenId по хэшу). Вместо этого он может использовать комбинаторную стратегию: генерировать большое количество случайных tokenId, вычислять их Keccak256(tokenId) и из этих значений выбирать конкретное подмножество так, чтобы их XOR-сумма в точности совпадала с целевым отпечатком жертвы.
Это позволяет злоумышленнику при вызове withdraw() передать набор поддельных tokenId, пройти проверку состояния и вывести весь залог, соответствующий данному s_positionsHash.
Теория
Предположим, что отпечаток позиции пользователя s_positionsHash имеет user position hash равный и numLegs равный , с набором tokenId . Таким образом:
Здесь каждое 248-битное хэш-значение можно рассматривать как 248-мерный вектор.
Следовательно, находится в 248-мерном пространстве, состоящем из 0 и 1 (). В этом пространстве операция XOR эквивалентна сложению векторов (Приложение I).
Цель злоумышленника — найти 248-мерных векторов из всех доступных векторов таких, чтобы младшие 248 бит их XOR-суммы равнялись . Таким образом, задачу злоумышленника можно сформулировать как систему линейных уравнений:
В частности, нам не нужно пытаться построить напрямую. Вместо этого мы выбираем линейно независимых хэш-векторов (где равно размерности 248) и используем их как столбцы для формирования матрицы :
Задача сводится к поиску коэффициентов вектора таких, что:
Где , и .
Согласно теории линейной алгебры, если матрица имеет полный ранг, она охватывает все -мерное пространство. Это означает, что для любого целевого система уравнений имеет единственное решение. После решения уравнения для мы просто оставляем те векторы , для которых ; их XOR-сумма будет в точности равна .
Пример
Для простоты понимания продемонстрируем этот процесс построения на кейсе. Допустим, векторы трехмерны, состоят только из 0 или 1, а целевое хэш-значение равно 101, т.е. .
Сгенерируем три случайных хэш-значения:
Формируем матрицу из этих трех векторов-столбцов и составляем уравнение :
Решая методом Гаусса, получаем . Это означает, что нам нужно выбрать и (соответствующие ), и их XOR-сумма будет в точности равна целевому .
Выбор n линейно независимых векторов
Как упоминалось ранее, для решения нам нужно построить матрицу полного ранга. Поскольку мы работаем с крайне большим векторным пространством (), возникает главный вопрос: как быстро выбрать линейно независимых векторов из этого огромного пространства?
Рассмотрим простейший метод: случайная генерация идентификаторов tokenId и выбор результатов их хэширования в качестве векторов.
В поле вероятность того, что случайно выбранных -мерных векторов образуют матрицу полного ранга, составляет:
Когда велико (в данном примере ), эта вероятность сходится к константе (Приложение II):
Это означает, что каждая случайная попытка имеет вероятность успеха примерно 28.9%. В среднем злоумышленнику нужно около 3.5 попыток, чтобы найти набор линейно независимых векторов. Таким образом, вычислительные затраты крайне низки, и злоумышленник может быстро построить матрицу , удовлетворяющую условиям.
Чтобы контролировать countLegs в старших 8 битах, нужно лишь корректировать диапазон случайно генерируемых tokenId в соответствии с целевым показателем countLegs.
Например, если мы хотим, чтобы numLegs в поддельном отпечатке равнялось 0, мы просто следим за тем, чтобы все случайно сгенерированных tokenId были меньше . Поскольку приращение сегмента для tokenId в этом диапазоне равно 0, то независимо от того, какие векторы будут выбраны при решении системы методом Гаусса, итоговое накопленное значение numLegs неизбежно будет 0.
Анализ атаки
Белый хакер инициировал несколько транзакций спасения. Для простоты обсудим одну из них [3].
Основная логика состоит из 5 шагов:
- Взять в долг 0.23e8 WBTC и 28e18 WETH через флэш-кредит (flash loan) в Aave.
- Внести 0.23e8 WBTC и 28e18 WETH в контракты poWBTC и 0x1f8d_poWETH.
- Вызвать
mintOptions()контрактаPanopticPoolс обычным спискомpositionIdList, чтобы занять средства и открыть позицию с кредитным плечом. - Вызвать
withdraw(), передав поддельныеtokenId. Поскольку эти поддельные позиции имеютpositionSize, равный 0, функция возвращаетtokenRequiredкак 0, что означает, что общий объем залога, требуемый для всех позиций, ошибочно рассчитывается как нулевой. При этомs_positionsHash, сгенерированный на основе этихtokenId, в точности совпадает с таковым из шага 3, что позволяет «спасателю» вывести весь залог, не погашая долг. - Погасить флэш-кредит и выполнить следующий раунд.
Заключение
Этот инцидент подчеркивает две взаимосвязанные проблемы, которые в сочетании позволили несанкционированно вывести залог.
- XOR — небезопасная функция агрегации для хэшей. Keccak256 устойчива к коллизиям, но линейность XOR приводит к тому, что сумма нескольких хэшей по XOR не обладает этим свойством. Поиск набора входных данных, хэши которых при XOR дают целевое значение, сводится к решению системы линейных уравнений над , что вычислительно тривиально. Композиция хэшей требует операций, сохраняющих устойчивость к коллизиям, например, конкатенации с последующим повторным хэшированием.
- Отсутствие валидации идентификаторов позиций. Протокол не проверял, соответствуют ли переданные
positionIdреальным позициям опционов. Значения меньше имеютcountLegsравный 0 иpositionSizeравный 0, а значит, поддельные позиции не несут долговой нагрузки. Это позволило злоумышленнику пройти проверку здоровья с нулевым требованием к залогу, при этом подобрав хэш-отпечаток нужного значения.
Ссылки
Приложение
Следующие два приложения содержат математическое пояснение и доказательство утверждений из основного текста, а именно: что операция XOR эквивалентна сложению векторов (Приложение I) и какова вероятность того, что случайная матрица имеет полный ранг (Приложение II).
Приложение I
В конечном поле сложение определяется как сложение по модулю 2:
Легко видеть, что это идентично логической операции «Исключающее ИЛИ» (XOR, символ ).
Для -мерного векторного пространства (в данном случае ) сложение двух векторов и определяется как покомпонентное сложение по модулю 2:
Таким образом, в векторном пространстве сложение векторов эквивалентно побитовой операции XOR компонентов вектора.
Приложение II
Чтобы матрица имела полный ранг, эти векторов должны быть линейно независимыми.
Первый вектор может быть любым вектором в кроме нулевого, что дает вариантов; второй вектор не должен находиться в подпространстве, натянутом на , которое содержит векторов, оставляя вариантов; следуя этой логике, -й вектор не может находиться в подпространстве, натянутом на предыдущие векторов, что дает возможных вариантов.
Общее количество таких матриц (т.е. порядок ) равно:
Общее количество всех возможных матриц равно .
Таким образом, вероятность составляет:
При это выражение можно переписать как:
При это произведение сходится к константе:
Это означает, что для больших вероятность того, что случайная матрица будет иметь полный ранг, составляет примерно 28.9%.
О компании BlockSec
BlockSec — поставщик услуг комплексной безопасности блокчейна и соблюдения нормативных требований в криптосфере. Мы создаем продукты и сервисы, которые помогают клиентам проводить аудит кода (смарт-контрактов, блокчейн-проектов и кошельков), перехватывать атаки в режиме реального времени, анализировать инциденты, отслеживать незаконные средства и соблюдать требования AML/CFT на протяжении всего жизненного цикла протоколов и платформ.
BlockSec опубликовала множество статей по безопасности блокчейна на престижных конференциях, сообщила об обнаружении нескольких уязвимостей нулевого дня в DeFi-приложениях, предотвратила ряд взломов, сохранив более 20 миллионов долларов, и обеспечила безопасность криптовалютных активов на миллиарды долларов.
-
Официальный сайт: https://blocksec.com/
-
Официальный Twitter: https://twitter.com/BlockSecTeam



