一个负数是怎么掏空整个金库的

核心要点:本周四起 DeFi 安全事件涵盖舍入不对称与不安全类型转换,有缺陷的奖励结算逻辑,无权限任意调用,以及默认开放的访问控制,总损失约 $928.6K,涉及 Linea, BNB Chain, Arbitrum, Optimism, Avalanche 和 Base。每个案例都揭示了代币经济,初始化逻辑或授权流程中的细微实现缺陷如何被利用造成重大经济损失。

在过去一周(2026/04/06 - 2026/04/12),BlockSec 共检测并分析了四起攻击事件,总估计损失约 $928.6K。下表概述了这些事件,后续小节提供了每个案例的详细分析。

*Denaria 事件未被上周报告覆盖,在此补充收录。

从本周开始,我们会在每期报告开头选出一个重点事件进行深入分析。选择依据不一定是损失金额,也可能是协议设计本身的新颖性、攻击手法的巧妙程度,或对社区更广泛的借鉴意义。

本周看点:Denaria Incident

永续 DEX Denaria 引入了以复杂代码逻辑支撑的新型记账机制。然而,一次审计后的重构引入了舍入不对称,可产生一个微妙的边界情况,即负值,最终触发了一次不安全的类型转换,使攻击者得以提取天文数字般的金额。

2026年4月5日,Denaria 在 Linea 上的永续 DEX 被攻击,损失约 $165.6K。根因是 getLpLiquidityBalance() 中两个叠加的缺陷:一次审计后的 LP 余额记账重构引入了舍入不对称,可能产生一个略微为负的中间值,而一处不安全的 int256 到 uint256 类型转换将该负值静默包装为接近最大值的无符号整数,而不是回滚交易。攻击者通过单边 LP 存入和一笔多头交易触发了这一组合缺陷,随后从金库中提取了虚增的 PnL。

背景

Denaria 是一个基于动态虚拟 AMM 构建的 Linea 永续 DEX。它将交易和结算分为两个组件。PerpPair 市场管理用户仓位和 LP 记账,而 Vault 持有抵押品并结算已实现的 PnL。

PerpPair 中的 LP 所有权不是通过显式的代币余额表示的。协议从一个内部记账矩阵重建每个 LP 的状态,该矩阵由两个记账组件组成,这里称为资产侧和稳定侧。资产侧追踪 LP 在池子价格波动敞口中的份额,稳定侧追踪其在池子稳定币计价价值中的份额。这两个组件共同决定了 LP 的价值如何被追踪和结算。

关键在于,这些组件不是以静态快照形式存储的。每次发生交易时,PerpPair 都会更新其全局流动性状态,而每个 LP 的重建余额是通过全局累积值与 LP 入场点快照之间的差值推导出来的。这套记账机制源自一次审计后的重构,将原有的基于份额的累加器替换为直接余额追踪。然而,重构后的减法路径引入了一个舍入不对称,在特定交易序列下可导致重建的 LP 组件变为略微负值。

漏洞分析

存在漏洞的 PerpPair 合约 (0xb683...36ae17) 包含两个叠加的缺陷。

  1. 重构引入的舍入不对称。 审计后的重构将原有的基于份额的累加器替换为直接余额追踪,在减法重建路径中引入了舍入不对称。在特定交易序列下,舍入后的全局累积值可能略小于 LP 的入场点快照,导致减法运算产生一个小的负数而非零。理论上该值应为零或接近零;这种不对称是此次重构实现中的特定缺陷,而非减法记账的固有属性。

  1. 不安全的有符号到无符号类型转换。 在 getLpLiquidityBalance() 中,重建的 LP 余额组件从 int256 转换为 uint256 时没有经过验证。在 Solidity 中,将负的 int256 转换为 uint256 不会回滚,而是将值按 2^256 取模包装,将一个像 -1 这样的小负数变为接近最大值的无符号整数(2^256 - 1)。由于该重建余额被传入 calcPnL() 用于结算,任何负的输入都会被解释为一个巨大的 LP 仓位,攻击者可以实现虚增的利润并从金库中提取资金。

任何一个缺陷单独都不可被利用。舍入不对称只产生一个略微为负的值,在正常的 int256 算术下只是一个可忽略的误差。但一旦该负值到达未受保护的类型转换,它就会被包装为接近最大值的无符号整数。随后的边界检查将包装后的值截断为池子资产侧的总流动性,实际上将溢出转化为对市场整个资产侧的索取权。

攻击分析

以下分析基于攻击交易 0xcb0744...0c606447。

Step 1:攻击者通过 Aave 闪电贷借入 60,000 USDC。

Step 2:一个辅助地址存入 30,000 USDC 作为抵押品,并添加了 19,980 稳定代币的单边 LP 仓位。通过仅在稳定侧存入,该辅助地址确保其资产侧 LP 组件从接近零开始,使其更容易因舍入而跌入负值区间。

Step 3:第二个辅助地址存入 15,000 USDC,并开设了 100,000 名义价值的多头仓位,导致 liquidityM 更新引入了关键的舍入误差。相对于池子流动性而言较大的名义规模放大了每笔交易的舍入影响,将第一个辅助地址的重建资产侧余额推至略低于零。

Step 4:第一个辅助地址随后调用 realizePnL()。在 LP 余额重建过程中,负的 lpAssetBalance 被包装为接近最大值的 uint256。这个大值随后被限制为市场的全部资产侧流动性,并生成了极高的虚假利润。实际效果是,协议认为该 LP 拥有市场的整个资产侧。

Step 5:攻击者从金库中提取了虚增的 PnL。

相同的模式被重复执行,直到金库中剩余的流动性被耗尽。偿还闪电贷后,攻击者实现了约 $165.6K 的利润。

结论

这次攻击的根本原因是有符号到无符号转换缺少边界验证。直接的修复方案很简单:用 SafeCast.toUint256() 替代裸转换,后者在输入为负时会回滚而不是包装。

更广泛地说,这次事件揭示了审计后重构绕过外部审查的风险。根据官方事后报告,舍入不对称是在最终外部审计结束后引入的,当时团队将原有的累加器替换为直接余额追踪。虽然重构解决了一个溢出问题,却引入了一个新的边界情况,内部测试未能捕获。当协议重构核心记账逻辑时,新的代码路径应被视为安全关键并重新接受审计,尤其是当重建的值直接输入 PnL 结算或提款逻辑时。

本周其它事件

HB Token Incident

2026年4月7日,HB,一个 BNB Chain 上内嵌买卖钩子的自定义 ERC-20 代币,被攻击,损失约 $193K。根因是有缺陷的奖励结算逻辑:当触发时,它调用 swapBack(),直接从 PancakeSwap 交易对中移除 HB 储备,然后调用 sync() 重新定价。在攻击者买走交易对中大部分 HB 后,随后的 swapBack() 执行进一步减少了剩余流动性并大幅推高了现货价格。攻击者随后将少量 HB 卖入扭曲的池子,换取不成比例的大量 USDT,反复操作直到池子被耗尽。

背景

HB 是 BNB Chain 上一个自定义的 ERC-20 代币,在 _transfer() 中嵌入了买入和卖出钩子。当用户从 AMM 交易对买入时,_handleBuy() 记录成本基础信息。当用户卖出时,_handleSell() 根据交易对的状态分支到不同的税收和结算路径。

该代币还包含一个奖励结算机制,可以触发 swapBack()。swapBack() 不是通过正常的路由器中介交换,而是直接将 HB 从 PancakeSwap 交易对转移到 PROOF 地址,然后通过 sync() 强制交易对重新同步。这使得合约可以在正常 AMM 交易流之外缩减交易对的 HB 储备,并立即将池子向上重新定价。

漏洞分析

HB 代币合约 (0x62ce...87a4b0) 的核心缺陷在于 swapBack() 通过直接从 AMM 交易对中拉取代币来获取奖励,而不是从资金池或通过路由器中介交换。因为 swapBack() 可以通过奖励结算路径触达,一个非交易代码路径可以直接修改交易对储备并改变现货价格。

当交易对的 HB 储备较低时,swapBack() 的调用会进一步减少剩余的 HB 并放大价格扭曲,使得以极高的价格卖出少量 HB 成为可能。

攻击分析

以下分析基于攻击交易 0x19671f...d71594ed。

Step 1:攻击者从 Venus 借入大量资金。

Step 2:攻击者将约 1,496 HB 转入代币合约,增加合约的 HB 余额,使后续 swapBack() 能从交易对中拉取更多的 HB。

Step 3:通过向 PancakeSwap 交易对转入少量 HB,攻击者触发了 _swapAndLiquify(),该函数将代币合约持有的约 4,163 HB 交换为 10 USDT,并增加了攻击者可领取的 HB 奖励。

Step 4:攻击者随后花费 72,117,360 USDT 买入 73,608,753 HB,使交易对仅剩很少的 HB 流动性。

Step 5:接下来,攻击者触发了奖励不足路径。为满足奖励需求,代币调用了 swapBack(),从 PancakeSwap 交易对中拉取额外的 HB 并强制执行 sync(),大幅推高了 HB 价格。

Step 6:攻击者直接将 USDT 转入交易对以补充其 USDT 储备,然后以扭曲的价格仅卖出 0.000582 HB 就换得 37,582,322 USDT。

通过重复 Step 6 以扭曲的价格卖出 HB 代币,攻击者提取了池子中几乎所有的 USDT。

结论

HB 代币事件说明了允许奖励逻辑直接修改 AMM 储备的危险性。影响储备的函数不应从奖励结算路径可达,协议应避免在安全敏感的控制流中混淆内部代币余额与 AMM 储备记账。任何在内部扰动池子后依赖 AMM 现货定价的设计都天然容易受到价格操纵攻击。

Squid Multicall Incident

2026年4月7日,一名 Squid 用户在一次授权相关事件中跨多条链损失约 $517K。该用户错误地将授权给予了 SquidMulticall 合约,而非预期的 Squid Router。这使得攻击者可以调用无权限限制的 SquidMulticall.run() 函数执行任意外部调用。攻击者因此可以利用任何授权给该合约的额度,对授权用户执行代币 transferFrom() 调用。

背景

在 Squid 的正常流程中,用户应将授权给予 Squid Router,而 SquidMulticall 仅作为执行辅助工具。该辅助合约设计为作为路由逻辑的一部分执行批量调用,但它不应成为用户直接授权代币转移的花费者。

因为 ERC-20 额度检查仅针对花费者地址进行,任何将用户授权与不受限制的任意调用能力结合的合约都会创建一个危险的授权汇聚点:一旦获得授权,如果任何人可以控制它发起的调用,该合约就可以成为通用的代币提取工具。

漏洞分析

本次事件并非由智能合约漏洞引起。损失源于两个条件的同时出现:错误地将授权给予了 SquidMulticall 而非 Router,以及 run() 的无权限设计接受来自任何调用者的任意目标和 calldata。

SquidMulticall 设计为在 Router 流程中作为下游步骤执行批量调用,其输入由受信任的路由逻辑构造。按预期使用时,无权限设计不会带来风险。但一个错误的授权改变了一切:一个 MEV 机器人检测到了有效的额度,调用 run() 并使用构造的 calldata 调用 transferFrom(victim, attacker, amount),在无需受害者进一步操作的情况下跨链提取了授权的代币。

该事件影响了一名用户,涉及 BNB Chain, Arbitrum, Optimism, Avalanche 和 Base。以下分析基于攻击交易 0x81d0c4...9b1301e9。

Step 1:受害者 (0xacc0...f40e98) 错误地将授权给予了 SquidMulticall 而非预期的 Squid Router。

Step 2:一个 MEV 机器人检测到该额度,并使用构造的 calldata 调用了 SquidMulticall.run()。

Step 3:通过任意调用,SquidMulticall 调用了 transferFrom(victim, attacker, amount),将授权的资产从受害者钱包中转出。

本次事件说明了无权限任意调用合约与面向用户的授权流程共存的风险。直接原因是一次错误的授权,而因为 SquidMulticall 接受不受限制的调用者,任意目标和任意 calldata,任何错误指向它的授权都会立即被完全利用。使用执行辅助合约的协议应考虑添加调用者限制或约束此类函数允许的调用目标,以防止错误的授权被轻易武器化。

XBIT Incident

2026年4月11日,BNB Chain 上的 XBIT 代币被攻击,损失约 $53K。根因是 XBITVault 中默认开放的访问控制缺陷:transfer() 函数的授权检查是有条件的,仅在 xbitContract 为非零时才校验 msg.sender == xbitContract,否则直接放行。由于 bindXBIT() 从未被调用来完成初始化,该缺陷被永久暴露,允许任意调用者移动任何地址的 XBIT 余额,包括 XBIT/USDT PancakeSwap 交易对。攻击者利用这一点从交易对中提取 XBIT,然后反复将少量 XBIT 卖回池子以换取不成比例的大量 USDT。

背景

XBITVault 不是一个被动的资金库合约。它是 XBIT 代币系统的余额和授权后端,暴露了 transfer(),approve() 和 mintForXBIT() 等类代币函数。在预期设计中,所有者必须先调用 bindXBIT() 来初始化金库,设置 xbitContract,pancakePair,pairContract 和 xbitDecimals。初始化后,敏感的状态变更函数应仅由绑定的 XBIT 合约可调用。换言之,金库的安全模型依赖于在公开使用前成功完成初始化。

漏洞分析

存在漏洞的 XBITVault 合约 (0xc879...42391a) 的关键缺陷在于访问控制是有条件的。transfer() 函数仅在 xbitContract != address(0) 时才检查 msg.sender == xbitContract。这意味着当 xbitContract 未设置时,函数不会回滚,而是变为任何人都可公开调用。因为余额存储在 _balances 中,任何外部调用者都可以将 XBIT 从任何地址移动到任何其他地址,只要源地址有足够的余额。

预期的初始化路径是 bindXBIT(),但因为它从未被调用,金库一直处于未初始化和默认开放状态。其结果实际上是一个不受限制的任意余额转移接口。

以下分析基于攻击交易 0xbc877f...4df1b694。

Step 1:通过不受限制的 transfer() 函数,攻击者将 1,526,216.569 XBIT 从 XBIT/USDT 交易对中移出,而未提供任何对应的 USDT。

Step 2:攻击者调用 sync() 将交易对的 XBIT 储备压缩至仅 1-2 个单位。

Step 3:在交易对仅剩接近零的 XBIT 流动性后,攻击者反复卖出 XBIT,从交易对中提取了约 53,112 USDT。

本次事件由一个依赖初始化的访问控制检查默认开放导致。transfer() 在 xbitContract 未设置时不受限制,而因为 bindXBIT() 从未被调用,金库永久暴露了一个公开的任意余额转移接口。特权函数应在初始化完成前回滚,部署时的绑定步骤应是链上强制的前置条件,而非运营层面的假设。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值