За прошедшую неделю (19.10.2026–25.10.2026) в этом отчёте отслеживались два инцидента безопасности с оценочными потерями приблизительно в 5,57 млн USD. Подробный анализ посвящён захвату управления X100 Freezer в Ethereum.
| Дата | Инцидент | Тип | Оценочные потери* |
|---|---|---|---|
| 21.10.2026 | NEM Token | Ошибочная бизнес-логика | ~820 тыс. USD |
| 24.10.2026 | X100 Freezer | Ошибочная архитектура управления | ~4,75 млн USD |
*Потери указаны приблизительно. Показатель для X100 Freezer относится к репрезентативной транзакции, проанализированной ниже, и совпадает с первоначальной оценкой Phalcon; в общую сумму не включены отдельно опубликованные сведения о последующих выводах средств. Для NEM указана дата транзакции в блокчейне; публичное предупреждение было опубликовано 22 октября [1].
- X100 Freezer: Инцидент показывает совокупный риск, связанный с немедленным получением права участвовать в управлении и отсутствием задержки перед исполнением: злоумышленник использовал средства, полученные через флеш-кредит, чтобы купить и заморозить токены, сразу получил большинство мест в Сенате, а затем одобрил и исполнил вредоносное предложение по операциям с казначейством в рамках одной транзакции.
Лучший аудитор безопасности для Web3
Проверьте архитектуру, код и бизнес-логику перед запуском
Главный инцидент недели: X100 Freezer
24.10.2026 была эксплуатирована уязвимость X100 Freezer — системы стейкинга и управления ликвидностью на Ethereum. Оценочные потери в рассматриваемой здесь транзакции составили приблизительно 4,75 млн USD. Из-за ошибочной архитектуры управления недавно приобретённые и замороженные токены сразу давали право на места в Сенате, а между одобрением предложения и его исполнением не было обязательной задержки. Злоумышленник получил два из трёх мест, направил обмен средств казначейства через подконтрольный ему пул и продал часть возвращённых токенов, чтобы погасить флеш-кредит и получить прибыль [2].
Предыстория
Пользователи X100 замораживают токены X в многоуровневых стейкинговых позициях для получения вознаграждений. Контракт Freezer по адресу 0x79f3...2a46 также управляет ликвидностью проекта на Uniswap V3/V4 и PancakeSwap V3. Он наследует контракт управления Senate через DexTreasury; Сенат принимает решения об операциях с казначейством посредством предложений и голосований.
Публичная точка входа freeze() переводит токены пользователя в контракт, после чего вызывает закрытую функцию _freeze(), чтобы создать стейкинговую позицию и обновить её учётные данные. _tierTotal[owner][tier] хранит данные о замороженной сумме каждого аккаунта на каждом уровне. Функция _freeze() обновляет эти балансы и уведомляет систему управления через _onStakeChanged(owner):
function _freeze(address owner, uint8 tier, uint256 amount) private returns (uint256 id) {
_settle(owner);
Account storage account = _accounts[owner];
if (account.firstFreezeAt == 0) {
account.firstFreezeAt = block.timestamp;
}
id = _create(owner, tier, amount, block.timestamp, block.timestamp);
_tierTotal[owner][tier] += amount;
totalFrozen += amount;
totalFrozenIn[tier] += amount;
_onStakeChanged(owner);
emit Frozen(id, owner, tier, amount);
}
Чтобы попасть в список кандидатов в Сенат, аккаунт вызывает applyForSenate(). При этом вызывается _register(), которая регистрирует аккаунт, взимает плату за выдвижение и вызывает тот же хук обновления стейка:
function _register(address account) internal {
require(!_registered[account], AlreadyRegistered(account));
_registered[account] = true;
_collectCandidacyFee(account);
emit Registered(account);
_onStakeChanged(account);
}
Хук пропускает незарегистрированные аккаунты. Для зарегистрированных аккаунтов он поддерживает список, ранжированный по замороженным балансам, добавляя или перемещая аккаунты через _link(). Функция isSenator() определяет первые senateSize аккаунтов в этом списке как сенаторов. Система поддерживает от 3 до 21 мест, каждое из которых даёт один голос. Для замороженных токенов действует срок до вывода, который _unfreezeAt() рассчитывает на основе временной метки frozenAt позиции и уровня стейкинга.
Сенатор выносит разрешённую операцию с казначейством на рассмотрение через propose(), которая регистрирует предложение и автоматически засчитывает голос автора в его поддержку. Другие сенаторы голосуют через vote(), а _tally() подсчитывает голоса в поддержку среди обладателей текущих мест. Для предложения фиксируются время создания и крайний срок, определяемый votingPeriod, значение которого по умолчанию составляет три дня.
Функции казначейства, такие как v3Swap(), указаны в DexTreasury._isProposable() как допустимые цели предложения. Их модификатор onlySenate требует, чтобы вызов исходил от самого контракта. При исполнении такой вызов самого себя выполняется с данными одобренного предложения. После этого Freezer._afterExecute() проверяет, что оставшийся баланс X покрывает totalFrozen, сохраняя совокупную сумму замороженных основных средств.
Анализ уязвимости
В ошибочной архитектуре управления Freezer не было ни задержки перед предоставлением полномочий Сената на основании недавно приобретённого стейка, ни обязательной задержки перед исполнением одобренного предложения.
Во-первых, для ранжирования используются непосредственно текущие замороженные балансы. Функция _stakeOf() вызывает frozenOf(), которая суммирует _tierTotal, не проверяя возраст стейка и не считывая исторический баланс:
function _stakeOf(address account) internal view override returns (uint256) {
return frozenOf(account);
}
function frozenOf(address owner) public view returns (uint256 total) {
total = 0;
for (uint8 tier = 0; tier < TIERS; ++tier) {
total += _tierTotal[owner][tier];
}
}
Показанные выше пути внесения токенов и регистрации обновляют право на место в рамках одной последовательности вызовов. Хотя _freeze() записывает временные метки позиции, при ранжировании и проверке статуса сенатора они не используются для соблюдения минимального срока владения. Поэтому новый кандидат с достаточным объёмом средств может получить место и воспользоваться правом выдвигать предложения и голосовать в рамках одной транзакции. Срок до вывода токенов не откладывает предоставление этих прав на участие в управлении.
Во-вторых, трёхдневный период голосования определяет только момент истечения срока предложения. Функция _state() возвращает Open сразу после создания предложения; оно остаётся открытым до исполнения, отмены или истечения срока. Проверка наличия большинства не зависит от прошедшего времени:
function _state(Proposal storage proposal) private view returns (ProposalState) {
if (proposal.executed) {
return ProposalState.Executed;
}
if (proposal.cancelled) {
return ProposalState.Cancelled;
}
if (block.timestamp > proposal.deadline) {
return ProposalState.Expired;
}
return ProposalState.Open;
}
function _isPassed(uint256 support) private view returns (bool) {
return support * 2 > senateSize;
}
Функция execute() принимает открытое предложение, как только число голосов в его поддержку превышает половину senateSize. Перед вызовом операции с казначейством она не проверяет ни минимальный интервал после createdAt, ни наличие задержки после одобрения:
function execute(uint256 id) external {
Proposal storage proposal = _requireOpen(id);
uint256 size = senateSize;
(uint256 support, address[] memory seats) = _tally(id);
require(_isPassed(support), NotPassed(id, support, size));
proposal.executed = true;
(bool ok, bytes memory result) = address(this).call(proposal.data);
if (!ok) {
assembly ("memory-safe") {
revert(add(result, 32), mload(result))
}
}
_afterExecute(id);
emit ProposalExecuted(id, _msgSender(), support, size, seats);
}
Вызов контракта самого себя удовлетворяет требованию onlySenate, поэтому проверка доступа к казначейству не добавляет задержки перед исполнением. Финальная проверка замороженной основной суммы не ограничивает время исполнения предложения и не устанавливает минимальную сумму, которую должен принести обмен средств казначейства. Эти условия позволяют вновь получившему большинство участнику одобрить и исполнить операцию с казначейством в рамках одной транзакции.
Анализ атаки
Приведённый ниже анализ основан на транзакции 0x38c608...4d81d0.
-
Шаг 1: Злоумышленник использовал средства, полученные через флеш-кредит, для покупки токенов
Xи распределил их между двумя подконтрольными контрактами. Заёмные средства профинансировали покупку токенов для создания двух стейкинговых позиций. -
Шаг 2: Каждый подконтрольный контракт заморозил принадлежащие ему токены
Xв Freezer и вызвалapplyForSenate(). Трассировка показывает, что после регистрации оба контракта получили событиеRanked. Их замороженные балансы обеспечили им два места в Сенате в рамках той же транзакции. На момент атаки в Сенате было три места, поэтому контроль над двумя давал большинство, необходимое для одобрения предложения по операциям с казначейством.

- Шаг 3: Злоумышленник создал пул
X/USDTна PancakeSwap V3 и предоставил ему ликвидность. Один подконтрольный контракт выдвинул предложение об обмене средств казначейства, автоматически отдав голос в его поддержку; другой отдал второй голос в поддержку. После достижения порога большинства Freezer исполнил предложение 7. В вызовеv3Swap()было указано 400 000 токеновXна входе иamountOutMinimum = 0. Казначейство получило лишь около 35,68USDT. Трассировка фиксируетvotesFor = 2иsenateSize = 3, показывая, что полученное большинство одобрило операцию.

- Шаг 4: Злоумышленник вывел ликвидность из недавно созданного им пула PancakeSwap V3, чтобы получить токены
X, поступившие от казначейства. Часть этих токенов была продана через уже существовавший пулX/USDTна Uniswap V3, после чего злоумышленник погасил флеш-кредит. Источником средств для погашения стала последующая продажа, а не немедленный вывод замороженных стейкинговых позиций.
Начните работу с Phalcon Explorer
Изучайте транзакции, чтобы принимать взвешенные решения
Попробуйте бесплатно прямо сейчасЗаключение
Причиной инцидента стала ошибочная архитектура управления: текущие замороженные балансы немедленно давали полномочия, а одобренные предложения могли исполняться без обязательной задержки. Злоумышленник воспользовался сочетанием этих условий, чтобы получить большинство и применить его до наступления срока погашения флеш-кредита. Затем казначейство продало 400 000 токенов X приблизительно за 35,68 USDT через подконтрольный злоумышленнику пул PancakeSwap V3. Злоумышленник вывел ликвидность из этого пула, чтобы получить токены, поступившие от казначейства, продал часть из них через отдельный, уже существовавший пул X/USDT на Uniswap V3 и направил вырученные средства на погашение флеш-кредита, оставив себе прибыль.
В системе управления следует требовать минимальный срок нахождения токенов в стейкинге либо определять право на место и голосование по снимку исторического баланса, сделанному в блоке до текущей транзакции. Это не позволит позициям, профинансированным в рамках транзакции, немедленно одобрять перемещения средств казначейства. Обязательная задержка между одобрением и исполнением должна препятствовать завершению всей последовательности захвата управления и вывода средств в рамках одной транзакции и давать время на проверку или отмену вредоносного предложения. Эти меры затрагивают разные этапы процесса управления и должны оцениваться совместно при аудите смарт-контракта.
Источники
[1] BlockSec Phalcon: анализ инцидента с NEM Token
[2] BlockSec Phalcon: анализ инцидента с управлением X100 Freezer
Бесплатная проверка безопасности
Быстрая проверка безопасности с помощью нашей собственной системы автоматизированного анализа.
Проверить бесплатноО BlockSec
BlockSec — поставщик комплексных решений в области безопасности блокчейна и соблюдения нормативных требований в криптоиндустрии. Мы разрабатываем продукты и предоставляем услуги, которые помогают клиентам проводить аудит кода (включая смарт-контракты, блокчейны и кошельки), останавливать атаки в реальном времени, анализировать инциденты, отслеживать незаконные средства и выполнять обязательства в сфере ПОД/ФТ на протяжении всего жизненного цикла протоколов и платформ.
BlockSec опубликовала несколько научных работ по безопасности блокчейна на престижных конференциях, сообщила о нескольких атаках нулевого дня на приложения DeFi, заблокировала несколько взломов, сохранив более 20 млн USD, и обеспечила защиту криптовалют на миллиарды долларов.
-
Официальный сайт: https://blocksec.com/
-
Официальный аккаунт в Twitter: https://twitter.com/BlockSecTeam



