Back to Blog

~$704万损失:GiddyDefi、Volo Vault等 | BlockSec周报

Code Auditing
April 29, 2026
24 min read
Key Insights

在过去一周(2026/04/19 - 2026/04/26),BlockSec 检测并分析了八起攻击事件,估计总损失约为 704 万美元。下表总结了这些事件,详细分析请参阅后续各节。

日期 事件 类型 估计损失
2026/04/19 自定义再平衡合约 任意调用 ~$64K
2026/04/20 REVLoans (Juicebox) 验证不当 ~$50.7K
2026/04/22 Volo Vault / Navi 密钥泄露 ~$3.5M
2026/04/22 Kipseli Router 验证不当 ~$72.35K
2026/04/23 GiddyDefi 签名验证不完整 ~$1.3M
2026/04/25 Purrlend 密钥泄露 ~$1.5M
2026/04/26 SingularityFinance Oracle配置错误 ~$413K
2026/04/26 Scallop 记账缺陷 ~$142.7K

开始使用 Phalcon Explorer

深入分析交易,明智决策

立即免费试用

本周亮点:GiddyDefi

攻击者并未破解签名,未使用闪电贷,也没有操纵任何价格。他们重放了一个合法的签名,将其未签名的字段替换为自己的合约。“利用你自己的签名对付你”是展示部分 EIP-712 覆盖如何将有效签名变成通用许可的最清晰证明。

2026年4月23日,以太坊上的 GiddyVaultV3 被利用,损失约130万美元。签名方案仅覆盖 SwapInfo.data,而将 aggregatorfromTokentoTokenamount 排除在 EIP-712 哈希之外,因此可以重放一个有效签名,并篡改这些字段。攻击者将 aggregator 指向一个恶意合约,并将 fromToken 指向策略的 LP 代币,从而盗走了约130万美元。

背景

GiddyVaultV3(0x5f0a...4318)是一个收益耕作金库合约,用户通过 deposit()withdraw() 存取资金。每项操作都必须携带由后端签名的 VaultAuth 授权结构,其中包含 EIP-712 签名和描述代币兑换路径的 SwapInfo[ ] 数组。在执行兑换时,合约调用 GiddyLibraryV3.executeSwap(),该函数对 swap.fromToken 执行 forceApprove,授权给 swap.aggregator,然后通过 aggregator.call(swap.data) 执行兑换。之后,策略合约根据其配置的策略管理资金。

EIP-712 是用于签名结构化链下数据的标准:消费该签名的协议在链上重建相同的结构,在其约定的域分隔符下进行哈希,并恢复签名者的地址。因此,任何 EIP-712 流的安全性都取决于链上哈希是否覆盖了影响执行的每个字段。在 Giddy 的设计中,后端签名一个 VaultAuth,其中包含用户的意图和任何所需兑换的路由指令,然后 _validateAuthorization() 重建该结构以在允许策略移动资金之前验证签名。

漏洞分析

漏洞存在于 GiddyVaultV3 的 _validateAuthorization() 函数中。在构建签名负载时,只对每个 SwapInfodata 字段进行了哈希处理;aggregatorfromTokentoTokenamount 都被排除在签名之外。这意味着任何拥有有效签名的人都可以自由替换 SwapInfo 的其余字段,同时仍然通过签名验证。

每个被排除的字段都是签名留下的一个独立杠杆:aggregator 通过 forceApproveaggregator.call(swap.data) 成为支出者和调用目标;fromToken 选择被授权的策略资产;amount 设置了授权上限;toToken 只用于满足 returnAmount > 0 检查。签名过的 data 并不约束这些字段,因为其中任何一个目标都不会在 data 中被引用。

攻击分析

以下分析基于交易 0x5edb66...5482e5

  • 步骤 1:攻击者从链上获取了一个合法的后端授权 VaultAuth 签名,保持 data 字段不变。由于之前的每次 deposit()withdraw() 调用都会在链上广播完整的 VaultAuth 负载,因此任何历史交易都是可重用签名的免费来源;攻击者只需要一个 data 字段适合预期兑换调用的签名。

  • 步骤 2:使用获取的签名,攻击者保持原始 signaturenoncedata 不变,同时篡改了其余字段。fromToken 被设置为策略合约持有的 LP 代币(真实资产),因此 forceApprove 授权的是协议实际持有的代币。aggregator 被替换为攻击者的恶意合约,因此授权和后续的 aggregator.call() 都被指向攻击者拥有的代码。由于这些字段不在签名验证范围内,_validateAuthorization() 保持不变地接受了篡改后的结构。为了绕过最后的 require(returnAmount > 0, "SWAP_NO_TOKENS_RECEIVED") 检查,恶意聚合器实现了一个铸造函数,将假代币铸造回协议,从而满足检查而无需执行任何真实兑换。

  • 步骤 3:由于在步骤 2 中已授予恶意聚合器授权,攻击者调用 transferFrom 将金库的 LP 代币直接转移到恶意聚合器,完成了盗窃。这一步完全在协议的受保护执行路径之外;当 executeSwap() 返回时,授权已经写入,并且调用后的余额检查已经通过,因此协议没有进一步干预的机会。

结论

此次攻击的根本原因是 EIP-712 签名覆盖不完整。SwapInfo 的核心字段直接控制着资金流,却未受保护,允许攻击者在提供有效签名的情况下替换兑换路线和聚合器地址。集成了外部聚合器的开发者应:

  • 确保 EIP-712 签名覆盖所有影响执行结果的字段,包括 aggregatorfromTokentoTokenamount

  • 强制执行聚合器白名单,以防止调用未经审计的外部合约。

  • toToken 限制为预期的基础代币,以防止假代币绕过余额检查。

更广泛地说,在任何“先授权后调用”架构中的 EIP-712 都必须对影响最终链上状态的每个字段进行哈希,而不仅仅是用户面对的意图。每当后端签名是用户提供参数和特权合约操作之间的唯一守门员时,流入该操作的每个参数(调用目标、资产、金额、接收者)都必须位于签名结构内部。将 data 视为调用身份的代理是一种范畴错误:调用的身份是所有参数的元组,而任何未包含在签名中的参数,根据定义,都由提交交易的人控制。

Web3 最佳安全审计机构

在上线前验证设计、代码和业务逻辑


本周更多事件


自定义再平衡合约

2026年4月19日,Avalanche 上的一款 sAVAX 再平衡合约被利用,从用户的 Aave V3 信用委托中提取了约64,000美元(约7,000 WAVAX)。一个公共函数在仍持有用户委托的情况下执行了任意 target.call(data),因此攻击者可以调用 Aave 的 borrow(),并将 onBehalfOf 设置为受害者。一名白帽机器人抢跑了此次攻击,并在资金被提取之前将其追回。

背景

该再平衡合约(0x7a7b...a8c9)暴露了一个名为 b2a13230() 的函数,旨在对用户的 Aave 头寸进行再平衡。该函数通过 Aave V3 信用委托代表用户操作:用户授予再平衡合约代表其借款的权限,再平衡合约将这些借款与用户提供的资金结合起来调整头寸(例如,借款+存款流程)。

漏洞分析

根本原因是 b2a13230() 函数包含一个 target.call(data) 步骤,其目标和调用数据均由调用者控制。此调用在合约仍在使用用户的 Aave V3 信用委托的情况下执行,因此在该步骤中调用的任何逻辑都继承了用户的借款能力。不存在允许的目标列表,也没有对调用数据的形状进行约束,因此该调用可以调用任何合约方法,包括 Aave 的 borrow(),并将 onBehalfOf 设置为用户。

攻击分析

以下分析基于交易:0xaaa1b2...35001b

  • 步骤 1:攻击者进行了闪电贷,借入了 sAVAXUSDC。然后通过再平衡合约将借来的 USDC 存入 Aave V3,以建立足够的抵押品进行借款。同时,借来的 sAVAX 被直接转移到再平衡合约,为之后的借款后存款步骤做准备。

  • 步骤 2:攻击者调用了 b2a13230() 函数。该函数首先执行了一个正常的借款操作,然后到达了任意调用部分。此时,攻击者构造了直接调用 Aave V3 的 borrow() 函数,并将 onBehalfOf 设置为受害者地址。由于受害者已授予再平衡合约信用委托,因此借款成功。借来的 WAVAX 被转移到再平衡合约中。

  • 步骤 3:攻击者再次调用 b2a13230() 函数,这次使用再平衡合约代表自己借入 WAVAX。然后,合约使用之前借来的 WAVAX(来自受害者的头寸)存入攻击者的头寸并进行偿还,允许攻击者提取利润。

结论

缺陷是特权上下文中任意外部调用与持有委托信用的结合。任何一层单独都不会造成问题:受约束的外部调用无法滥用委托,而没有委托的任意调用无法移动用户的资金。持有信用委托的合约不应暴露任意外部调用;如果需要此类调用,目标必须固定到白名单,并对调用数据进行形状检查。


REVLoans (Juicebox)

2026年4月20日,Juicebox 上的借贷扩展 REVLoans 在以太坊上被利用,损失约50,700美元。borrowFrom() 接受了调用者提供的会计来源,而未验证其是否已在协议中注册;一个伪造的36位小数上下文触发了同币种的快捷方式,导致余额错误地调整了1e18。两笔交易,一笔用于注入膨胀的会计条目,另一笔以膨胀的份额价格针对合法资金池借款,耗尽了21.77 ETH

背景

Juicebox 是以太坊上的一个混合募资和借贷协议。每个项目都有自己的 ERC20 份额代币(在此称为 REV)和一个分散在一个或多个终端中的金库,终端是实际保管项目资产子集的合约,并作为用户进出的入口点。一个项目可以在 JBDirectory 中注册多个终端,并且每个 (terminal, project, token) 三元组都有一个 JBAccountingContext,声明该代币在该终端内的记账所使用的 (decimals, currency)。因此,REV 是项目所有终端盈余总和的声明,而不是针对任何单个终端的声明。

用户可以通过销毁 REV 作为抵押品,然后针对项目的某个终端提取贷款,之后可以支付 REV 以重新铸造抵押品。借款金额的定价方式与赎回方式完全相同,因此借款在经济上等同于兑现相同的抵押品。

REV 的份额价格为 (totalSurplus + totalBorrowed) / (REV.totalSupply + totalCollateral)。在分子中包含 totalBorrowed 可以使借款/还款价格保持中性;它也意味着膨胀的 totalBorrowed 直接提高了份额价格,并允许少量抵押品兑现不成比例的金额。

漏洞分析

根本原因是 source 参数的输入未经验证。borrowFrom() 接受调用者提供的 REVLoanSource source(一个包含 .terminal.token 字段的结构体),而未检查该对是否已为给定的 revnetId 注册。两个字段都直接流入了兑现数学计算,因此 source.terminal 返回的会计上下文完全由调用者控制。当该上下文的 currency 字段与目标终端的匹配时,协议会采取同币种的快捷方式,跳过价格预言机,并将提供的十进制和余额数字视为权威。

未经验证的 source 然后由 _addTo() 写入 _loanSourcesOf[revnetId]totalBorrowedFrom[revnetId][source.terminal][source.token],该函数同样不执行任何注册检查。

一旦 (source, revnetId) 被记入账簿,_borrowableAmountFrom() 函数就将借款请求转化为应付金额。它通过 _totalBorrowedFrom() 构建 surplus = totalSurplus + totalBorrowed,然后将该盈余与调用者的抵押品数量和份额供应一起传递给 JBCashOuts.cashOutFrom()

十进制错误存在于更深一个层次的 _totalBorrowedFrom() 中。它迭代 _loanSourcesOf 并通过 mulDiv(tokensLoaned, 10**decimals, pricePerUnit) 将每个条目进行折叠。在同币种路径上,pricePerUnit = 10**decimals(目标18位小数精度),因此公式简化为 tokensLoaned 不变,而在36位小数会计下存储的余额会以1e18倍的价格计入18位小数的 ETH 总额。

cashOutFrom() 中发生放大。base = mulDiv(surplus, cashOutCount, totalSupply):由于 surplus 被膨胀的 totalBorrowed 主导,即使是极小的 cashOutCount(抵押品)也映射到一个不成比例的大额支出。

攻击分析

此次攻击使用了两笔交易。第一笔交易污染了 REVLoans 的账簿:0xc46cb7...dead1f。第二笔交易以合法终端为代价耗尽了资金池:0x9adbd6...a8f938

  • 步骤 1:攻击者调用 borrowFrom(),将贷款来源中的 terminaltoken 都指向一个假合约,并以少量 REV 作为抵押品。REVLoans 没有检查提供的终端是否已为 revnet 注册,也没有检查它是否识别该代币。
  • 步骤 2:REVLoans 查询假终端以获取会计上下文,该上下文返回一个伪造的 (decimals=36, currency=ETH-code(61166))。由于来源和目标货币匹配,REVLoans 采取了同币种的快捷方式,跳过了价格预言机,然后将合法终端的真实 ETH 盈余重新表示为攻击者36位小数的目标单位,膨胀了1e18倍,执行了兑现计算。
  • 步骤 3:REVLoans 将 (fake terminal, fake token) 注册到 _loanSourcesOf 并将膨胀的数字写入 totalBorrowedFrom。假终端通过确认接收来“支付”,没有真实的 ETH 流动。第一笔交易结束时,totalBorrowed 被操纵向上,只有少量的 REV 抵押品被烧毁。
  • 步骤 4:攻击者再次调用 borrowFrom(),这次将合法的 ETH 终端作为贷款来源,并使用微小的 REV 作为抵押品。兑现计算以实际18位小数的 ETH 单位执行。
  • 步骤 5:在计算 totalBorrowed 时,REVLoans 迭代了 _loanSourcesOf 并遇到了来自步骤 3 的条目。由于该条目的 currency 仍然与 ETH 匹配,因此再次触发了同币种的快捷方式,并将36位小数的存储余额以1e18倍的价格计入了18位小数的 ETH 总额。此时 totalBorrowed 被虚假债务主导,份额价格分子被极度膨胀。
  • 步骤 6:兑现计算返回的借款金额是根据膨胀的分子计算的,攻击者已提前调整该金额,使其略低于合法终端的实际盈余。合法终端支付了该金额,从资金池中几乎全部提取出来给了攻击者。

结论

根本原因是两个复合性的漏洞:(terminal, token) 对在未检查 revnet 注册的情况下被接受,并且同币种的快捷方式在未对十进制差异进行重新标准化的情况下将源余额折叠到目标总额中。任何一个漏洞单独的危害都会小得多;两者结合起来,允许调用者注入任意的 totalBorrowedFrom 条目并以面值兑现。补救措施:检查 (terminal, token) 是否在 revnet 的注册终端中,并在折叠源余额之前通过源存储的十进制比例进行重新标准化。


Volo Vault

2026年4月22日,Sui 上的收益金库 Volo,通过将用户存款路由到 Navi 借贷协议来赚取借贷收益,在操作员私钥泄露后损失了约350万美元。金库合约没有代码级别的错误;攻击者只是利用被盗凭证运行合法的操作员路径,并耗尽了 Volo 在 Navi 的存款。

背景

Volo 是用户面临的金库(0xcd86...27fefa);Navi 是底层借贷协议。金库持有一个 Navi AccountCap(一个授权从 Volo 的 Navi 账户提取的 Sui 能力对象),并将策略操作委托给一个操作员角色。要在 Navi 上存款或取款,操作员需要调用 start_op_with_bag_v2()AccountCap 从金库提升到一个临时袋中,然后 deposit_with_account_cap() / withdraw_with_account_cap_v2() 使用该 cap 来移动资金。

漏洞分析

根本原因是操作/密钥托管失败,而不是合约级别的漏洞。Volo 策略路径将提款授权委托给了持有操作员私钥的人:start_op_with_bag_v2() 只执行两个检查(assert_operator_not_freezed(operation, cap)assert_single_vault_operator_paired(operation, vault.vault_id(), cap)),两者都只验证提供的能力是否为已注册的操作员。然后,withdraw_with_account_cap_v2() 接受任何能够提供已提升的 AccountCap 的调用者。因此,任何持有操作员私钥的人都可以执行与合法操作相同的路径,无法区分。

攻击分析

以下分析基于交易 AQw9wM...3RUS

  • 步骤 1:攻击者使用泄露的操作员密钥调用 @volosui/volo-vault::operation 上的 start_op_with_bag_v2,将 Navi AccountCap 提升到一个临时袋中。
  • 步骤 2:攻击者使用 bag::remove 从临时袋中提取了 AccountCap

  • 步骤 3:攻击者调用 @navi-protocol/lending::incentive_v3 上的 withdraw_with_account_cap_v2,使用了提取的 AccountCap,从 Navi 提取了 Volo 的存款。

  • 步骤 4:攻击者使用 bag::addAccountCap 重新放回,关闭了操作,并将资金转移出去。

结论

缺陷是结构性的:一个操作员密钥,拥有完整的提款权限,没有二次检查。三个更改可以减少密钥泄露造成的损害。将操作员角色分割成多重签名或阈值方案,意味着泄露的密钥无法单独授权提款。为出金提款添加时间锁,可以在结算前为异常调用提供一个可争论的窗口。将操作员的权限范围限制为仅存款和再平衡,并通过一个单独的路径处理面向用户的提款,可以防止操作员角色根本无法触及用户资金。


Kipseli Router

2026年4月22日,Base 上的 Kipseli Router 被利用,损失约72,350美元。该路由器使用外部 USDC 专用报价器返回的报价作为原始输出代币转账金额,而未验证输出代币是否等于报价代币。攻击者在报价器实际不支持的路径上,将0.04 WETH 兑换为 cbBTC,收到了报价器的 USDC 缩放返回值(92,610,395),作为原始 cbBTC 单位(≈0.926 cbBTC)。

背景

Kipseli Router(0x579f...9a07)是一个由外部报价系统支持的兑换执行合约。该合约未开源;以下分析基于其反编译的字节码,这也是为什么函数名称显示为4字节选择器(0xcce096f3()0x592()0xd88())。它不直接从链上 AMM 池计算兑换价格,而是查询报价器以获取输出金额(amountOut),然后根据该金额执行代币转账。在正常操作中,用户将 tokenIn 发送到协议钱包,然后路由器从同一钱包中提取 tokenOut 并将其转发给接收者。该协议配置为只有一个 QUOTE_TOKEN,并且报价逻辑以6位小数的 USDC 计价;该系统仅设计用于支持 USDC 计价的报价。

漏洞分析

缺陷跨越两个层级并相互叠加。在路由器方面,函数 0xcce096f3() 通过报价函数 0x592() 获取报价 v0,并将其不变地传递给 0xd88() 作为 tokenOut.transferFrom(_wallet, receiver, v0)。路由器从不检查 tokenOut 是否等于协议的 QUOTE_TOKEN,因此以 USDC 为单位的价值(6位小数精度)被传输,就好像它是 cbBTC 的数量(8位小数精度)一样。在报价器方面,底层 PropAMM AMM 专为代币对 USDC 而设计,但接受不支持的路由路径(WETHcbBTC)而不回退,并默默忽略 tokenIn,返回一个 USDC 缩放的价值,就好像兑换有效一样。

攻击分析

以下分析基于交易 0x96edee...3db3bb

  • 步骤 1:攻击者调用路由器,将 tokenIn=WETHtokenOut=cbBTC。底层 AMM 不支持此路径,但没有回退,并且报价器 0x592() 返回了一个 USDC 缩放的价值 92,610,395(≈92.61 USDC)。
  • 步骤 2:路由器直接使用该值作为 cbBTC 的转账金额。0.04 WETH(≈$95)通过 transferFrom 流入;92,610,395 个原始 cbBTC 单位(≈0.926 cbBTC,≈$72.35K)从协议钱包流出到攻击者。

结论

由于报价器调用双方存在两个未经验证的假设,因此导致了此次利用。报价器假设其输出在自身的 USDC 6位小数框架内被消费;路由器假设报价器返回的任何内容都以请求的 tokenOut 计价。任何一项修复都可以消除此漏洞:

  • 在路由器上:断言 tokenOut == QUOTE_TOKEN,或通过预言机将 USDC 缩放的报价转换为 tokenOut 单位,然后再进行转账。

  • 在报价器上:在路由路径的代币未在支持的对集注册时回退,而不是默默返回 USDC 缩放的后备值。


Purrlend

2026年4月25日,Purrlend,一个在 HyperLiquid 和 MegaETH 上的借贷协议,在私钥泄露后损失了约150万美元。攻击者接管了桥梁角色并铸造了未背书的 pTokens(Purrlend 的类 Aave 收据代币),然后使用这些 pTokens 作为抵押品,从资金池中借出了真实资产。

背景

Purrlend(0x81d5...a702)是一个具有类 Aave 会计模型的借贷协议。当用户将资产存入协议时,他们会收到相应的 pTokens,类似于 Aave 的 aTokens,这些代币代表其存款头寸,并可用作借入其他资产的抵押品。

该协议还包括特权角色,如 pool adminrisk adminbridge。桥梁角色用于跨链会计:它可以铸造 pTokens 来镜像在相对链上发生的存款。其他管理员角色修改风险参数并配置可借贷资产。

漏洞分析

直接触发原因是特权密钥泄露:攻击者获得了控制 Purrlend 管理员和桥梁角色的密钥。合约级别的设计缺陷放大了泄露:bridge 角色的 pToken 铸造路径未绑定到任何可验证的跨链托管证明。该函数允许拥有桥梁角色的调用者向任何地址发行任意数量的 pTokens,而无需检查是否发生了相应的存款。在协议的其他地方,pTokens 被视为有效抵押品,并且在借款时,借款路径不会重新检查背书。因此,未经授权的桥梁角色铸造直接转化为借款能力,中间没有第二个门来控制从铸造到资产提款。

攻击分析

以下分析基于 MegaETH 上的交易 0xb96cff...dbbf24

  • 步骤 1:攻击者通过 GnosisSafeProxy 持有的泄露的特权密钥,使用 MultiSendCallOnly 批处理,通过 ACLManager 将自己设置为 pool adminrisk adminbridgeemergency admin,然后启用 WETH 作为可借贷资产,并将其 BorrowCap 设置为200。
  • 步骤 2:攻击者以桥梁的身份,向自己的地址铸造了大量 pTokens。桥梁铸造路径未验证跨链托管,因此新的 pTokens 没有底层资产支持。

  • 步骤 3:攻击者使用未背书的 pTokens 作为抵押品。由于借款路径将任何 pToken 余额视为有效存款头寸,而无需重新检查背书,因此抵押品检查通过,并从资金池中借出了 WETH

结论

此次攻击是由一个合约级别的设计缺陷放大的特权密钥泄露。泄露的密钥只给了攻击者桥梁角色预期的权限,但该权限包括无限制的 pToken 铸造,这直接转化为可借贷的抵押品。每一层都可以独立加强。在操作层面,将桥梁角色分割成多重签名或阈值方案,以便单一密钥泄露无法行使该角色。在合约层面,要求桥梁铸造携带可验证的托管证明(例如,来自受信任的跨链验证器的消息承诺),并在未提供证明时回退。在铸造时验证证明是更持久的修复,因为它消除了对密钥托管的依赖。


SingularityFinance

2026年4月26日,Base 上的 SingularityFinance 的 dynBaseUSDCv3 金库损失了约413,000美元。该金库配置了一个无效的 Uniswap V3 费用等级(42,V3 中不存在),因此每个非 USDC 资产的价格预言机都解析为一个不存在的资金池。定价函数静默返回0而不是回退,金库将非 USDC 储备估值为零,攻击者通过存入少量 USDC 铸造了几乎全部的份额供应,然后赎回了实际的底层资产。

背景

dynBaseUSDCv3 金库(0x67b9...4dcd)持有多种收益型代币,并通过 Uniswap V3 定价非 USDC 储备。定价助手 getPrice(base, fee, quote, amount) 通过工厂解析 (base, quote, fee) 元组到一个 Uniswap V3 池,然后读取该池的 TWAP。金库的 totalAssets() 聚合了定价储备;份额铸造和赎回比例由此总数推导而来。

漏洞分析

缺陷在于 getPrice() 的早期返回分支。当 IUniswapV3Factory.getPool(base, quote, fee) 返回 address(0)(对于提供的费用等级不存在资金池)时,函数会继续执行并返回其零初始化的 price 变量,而不是回退。金库部署时 fee=42,这不是 Uniswap V3 的支持等级之一(500/3000/10000),因此每个非 USDC 代币的查找都会命中此分支。因此,totalAssets() 只汇总了金库的 USDC 余额,而实际的收益型代币贡献为零。依赖于 totalAssets() 的铸造和赎回比例是针对这个接近零的分母计算的。

攻击分析

以下分析基于交易 0x00b949...8d3732

  • 步骤 1:攻击者进行了约10万 USDC 的闪电贷。

  • 步骤 2:攻击者将 USDC 存入了金库。由于 totalAssets() 只计算了 USDC 余额,因此金库的估值约等于存款金额,攻击者获得了近100%的份额供应。

  • 步骤 3:攻击者赎回了份额,这会将底层储备按比例分配给份额持有者。攻击者获得了金库持有的每种收益型代币的大部分。

  • 步骤 4:攻击者偿还了闪电贷,并保留了盗取的收益型代币作为利润。

结论

缺少两项检查。部署时未将 fee=42 与 Uniswap V3 的支持等级(500/3000/10000)进行验证;getPrice() 在资金池不存在时返回0而不是回退。任何一项修复都足够:在配置时验证预言机参数,或在 getPool() == address(0) 时回退。作为纵深防御,份额铸造逻辑在接受存款之前应通过外部参考来验证 totalAssets()


Scallop

2026年4月26日,Sui 上的 Scallop 的质押奖励计划损失了约142,700美元。更新用户累积奖励的函数未验证传入的奖励跟踪对象是否与用户的账户匹配,导致攻击者从一个被弃置的、长期不活跃的奖励跟踪对象中提取了虚假的积分余额,并一直兑换合法的奖励池,直到余额被耗尽。

背景

Scallop 是 Sui 上的一个借贷协议。在其借贷产品之上,Scallop 运行一个(spool)程序:用户将一种资产存入 Scallop 的市场以获得 MarketCoin<T>(市场代币,对于 SUI 存款,这是 MarketCoin<SUI>,代表“sSUI”的链上表示),然后将该 MarketCoin 质押到 Spool 中,随时间赚取协议积分,之后这些积分可兑换成配对的 RewardsPool 以获得实际的奖励代币。每个 Spool 是一个 Sui 共享对象,跟踪一个全局的每股 index;每个用户持有一个个人 SpoolAccount,记录其质押的余额和累积的 points

漏洞分析

缺陷存在于 spool::user::update_points 函数中:该函数不检查 account.spool_id == object::id(spool)(也不检查 account.stake_type == spool.stake_type)。同级的条目 stakeunstakeredeem_rewards 都在入口处执行了该绑定检查;只有 update_points 跳过了它。没有该检查,spool_account::accrue_points 会计算 account.points += stake * (spool.index - account.index) / 1e9,针对传入的任何 Spool,就好像其 index 是该账户自己的奖励流一样。

由于 Sui 从不垃圾回收共享对象,因此该路径变得可利用:一个被弃置的 Scallop Spool,其 stakes 已经衰减成尘土,会持续累积奖励份额(每期增量 1e9 * reward / stakes),因此其 index 会随时间累积增加,并可以达到任意大的值。由于缺少绑定检查,update_points 可以使用这个膨胀的 index 将一个巨大的积分增量写入任何账户。然后,这些被污染的 points 会以 1:1 的比例兑换目标池的 RewardsPool,因为账户与该目标池合法绑定,并且 redeem_rewards 自身的绑定检查也通过了。

攻击分析

以下分析基于交易 6WNDjC...NfVL

  • 步骤 1:攻击者以0.2 SUI 作为诱饵,铸造了 MarketCoin<SUI>,然后调用 new_spool_account + stake 针对目标池,创建了一个合法绑定的 SpoolAccount,其中 account.spool_id = target_spool

  • 步骤 2:攻击者调用 update_points<MarketCoin<SUI>>(donor_spool, account, clock),其中 donor_spool 设置为一个被弃置的 Spool。捐助者的 index(≈8.91e14)作为 points 被写入账户:points = stake * (8.91e14 - 1.19e9) / 1e9 ≈ 1.62e14

  • 步骤 3:攻击者调用 redeem_rewards<MarketCoin<SUI>, SUI>(target_spool, target_rp, account)。绑定断言接受了目标绑定的账户,内部重计算提前返回,而被污染的 points 以 1:1 的汇率在奖励池的余额上限内进行了转换:rewards = 150,098,061,595,978 原始 SUI

  • 步骤 4:攻击者调用 unstakeredeem 来收回0.2 SUI 的诱饵,然后通过 TransferObjects 将所有东西转移出去。

结论

修复方法是在 update_points 的入口处添加与 stakeunstakeredeem_rewards 已经执行的相同的 assert!(account.spool_id == object::id(spool)) 检查。作为纵深防御,协议还可以限制单次 accrue_points 调用接受的 index 增量(拒绝大于配置上限的增量),这样即使将来再次绕过绑定检查,单次调用也无法为账户充值与其实际质押时间不成比例的 points 数量。

开始使用 Phalcon Security

检测所有威胁,警报重要事件,并阻止攻击。

立即免费试用

关于 BlockSec

BlockSec 是一家全栈区块链安全和加密合规提供商。我们构建产品和服务,帮助客户在协议和平台的整个生命周期中执行代码审计(包括智能合约、区块链和钱包)、实时拦截攻击、分析事件、追踪非法资金,并满足 AML/CFT 义务。

BlockSec 已在著名会议上发表了多篇区块链安全论文,报告了多个 DeFi 应用的零日攻击,阻止了多次黑客攻击并挽救了超过2000万美元,并保护了数十亿美元的加密货币。

Sign up for the latest updates
~$7.04M Lost: GiddyDefi, Volo Vault & More | BlockSec Weekly
Security Insights

~$7.04M Lost: GiddyDefi, Volo Vault & More | BlockSec Weekly

This BlockSec weekly security report covers eight attack incidents detected between April 20 and April 26, 2026, across Ethereum, Avalanche, Sui, Base, HyperLiquid, and MegaETH, with total estimated losses of approximately $7.04M. The highlighted incident is the $1.3M GiddyDefi exploit, where the attacker did not break any cryptography or use a flash loan but simply replayed an existing on-chain EIP-712 signature with the unsigned `aggregator` and `fromToken` fields swapped out for a malicious contract, demonstrating how partial signature coverage turns any historical signature into a generic permit. Other incidents include a $3.5M Volo Vault operator key compromise on Sui, a $1.5M Purrlend privileged-role takeover, a $413K SingularityFinance oracle misconfiguration, a $142.7K Scallop cross-pool index injection, a $72.35K Kipseli Router decimal mismatch, a $50.7K REVLoans (Juicebox) accounting pollution, and a $64K Custom Rebalancer arbitrary-call exploit.

The Decentralization Dilemma: Cascading Risk and Emergency Power in the KelpDAO Crisis
Security Insights

The Decentralization Dilemma: Cascading Risk and Emergency Power in the KelpDAO Crisis

This BlockSec deep-dive analyzes the KelpDAO $290M rsETH cross-chain bridge exploit (April 18, 2026), attributed to the Lazarus Group, tracing a causal chain across three layers: how a single-point DVN dependency enabled the attack, how DeFi composability cascaded the damage through Aave V3 lending markets to freeze WETH liquidity exceeding $6.7B across Ethereum, Arbitrum, Base, Mantle, and Linea, and how the crisis forced decentralized governance to exercise centralized emergency powers. The article examines three parameters that shaped the cascade's severity (LTV, pool depth, and cross-chain deployment count) and provides an exclusive technical breakdown of Arbitrum Security Council's forced state transition, an atomic contract upgrade that moved 30,766 ETH without the holder's signature.

Weekly Web3 Security Incident Roundup | Apr 13 – Apr 19, 2026
Security Insights

Weekly Web3 Security Incident Roundup | Apr 13 – Apr 19, 2026

This BlockSec weekly security report covers four attack incidents detected between April 13 and April 19, 2026, across multiple chains such as Ethereum, Unichain, Arbitrum, and NEAR, with total estimated losses of approximately $310M. The highlighted incident is the $290M KelpDAO rsETH bridge exploit, where an attacker poisoned the RPC infrastructure of the sole LayerZero DVN to fabricate a cross-chain message, triggering a cascading WETH freeze across five chains and an Arbitrum Security Council forced state transition that raises questions about the actual trust boundaries of decentralized systems. Other incidents include a $242K MMR proof forgery on Hyperbridge, a $1.5M signed integer abuse on Dango, and an $18.4M circular swap path exploit on Rhea Finance's Burrowland protocol.

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit