[蝴蝶效应] Bug修复引发的复合安全事件

[蝴蝶效应] Bug修复引发的复合安全事件

由 BlockSec 团队(@BlockSecTeam)

在上周,Compound 协议出现了一个会“意外”向用户发送大量 COMP 代币的漏洞。这个漏洞(本博客中的漏洞 2)的根本原因是由于另一个之前发现的漏洞(本博客中的漏洞 1)的修复不当。

在本博客中,我们将详细阐述第一个漏洞的根本原因,以及第一个漏洞的修复如何导致第二个漏洞的产生。

背景

Compound 协议基于 Compound 白皮书。通过 cToken 合约,链上账户向协议提供资金(Ether 或 ERC-20 代币)以获得 cToken,或者通过持有其他资产作为抵押品向协议借入资产。Compound cToken 合约跟踪这些余额并算法化地设定借款人的利率

为了激励用户,为 Compound 提供流动性(供应资金)的用户可以获得利息。具体来说,用户将资产(例如 Ether 或其他 ERC20 代币)存入 Compound,并获得相应的 cToken。当用户将 cToken 退还给 Compound 时,如果没有债务,底层资产(Ether 或 ERC20 代币)和利息将退还给用户。例如,如果一个用户有 1000 Ether,他/她可以通过 cEth.mint(1000) 将资产放入 Compound 以获得 cToken。

cToken 代表了已锁定在 Compound 中的底层资产。用户可以进一步使用 cToken 作为抵押品来借入其他资产。例如,用户可以通过 ceth.mint(1000) 存入 1000 Ether,然后使用获得的 cToken 以 75 Ether 的价值(超额抵押——此数值取决于抵押因子)借入 x Dai,通过 cDai.borrow(x) 实现。

核心逻辑实现在 Comptroller 合约中。它维护着用户的状态,例如用户向 Compound 存入了多少代币,借入了多少代币,以及用户是否可以借入更多代币。此过程中调用的函数包括 getHypotheticalAccountLiquidityInternal()borrowAllowed()mintAllowed() 等。

Compound 还有一个称为 COMP 的治理代币。COMP 代币可用于对提案进行投票。此外,COMP 代币可以在交易所进行交易。目前,COMP 的价格约为 300 美元。

漏洞 1

2021 年 9 月 31 日,Compound DAO 中出现了一个新的提案(提案 62),旨在修复 Comptroller 中的一个漏洞。

该漏洞与 CompSpeed 相关,它表示每个区块可以分配给用户的 COMP 代币数量。

`mint` 函数的流程

接下来,我们将使用 mint 函数来描述此漏洞的原因。mint 函数的调用链是:mintmintInternalmintFresh

mintFresh 函数中,它调用 mintAllowed,然后更新用户的 cToken 余额。

mintAllowed 函数中,它首先调用 updateCompSupplyIndex,然后调用 distributeSupplierComp 来 1)更新市场的 compSupplyState,以及 2)向用户分配 COMP 代币。

updateCompSupplyIndex

updateCompSupplyIndex 函数将更新每个市场的状态,主要是 compSupplyState[cToken]

CompMarketState 结构中,它记录了本次更新的区块号(block)以及影响应分配给用户(持有 cToken 的用户)的 COMP 代币数量的奖金指数(index)。

每个代币的奖金指数(index)是什么? 这是随时间累积的值(如下公式所示)。

这显示了应分配给用户的 COMP 代币数量(对于用户持有的每个 cToken)。

distributeSupplierComp

另一个函数 distributeSupplierComp 负责记录应分配给用户(供应商)的 COMP 代币数量到 compAccrued[supplier]

具体来说,它在 updateCompSupplyIndex 函数中更新全局奖金指数 compSupplyState。然后在 distributeSupplierComp 函数中,supplyIndex 记录当前奖金指数,supplierIndex 显示用户的(供应商)上次奖金指数。差值 (supplyIndex - supplierIndex) 乘以 用户的 cToken 余额,即为应分配给用户的 COMP 代币数量。

漏洞 1 的原因

还有一个函数 setCompSpeed 用于调整市场的 supplySpeedcompSpeeds[address[cToken]])。

这是因为,如果我们为某个市场将 CompSpeed 设置为零,则意味着 COMP 代币不会分配给该市场的用户。因此,如果我们想先禁用某个市场的 COMP 分配,然后再重新启用它,可以按照以下步骤操作:

  • 步骤 I:将 CompSpeed[cToken] 设置为零,以禁用 COMP 代币的分配。
  • 步骤 II:调用 setCompSpeed 函数将 CompSpeed[cToken] 设置为非零值。

步骤 I:对于在步骤 I 中已禁用 COMP 代币分配的市场(supplySpeed == 0),区块号不为零,因为在 updateCompSupplyIndex 中区块号是持续更新的(else if (deltaBlocks > 0))。

步骤 II:在执行步骤 II 中的操作时,setCompSpeedInternal 函数会经过 else if (compSpeed != 0) 语句(第 1083 行)。然后,在第 1088 至 1093 行,有一个检查 if (compSupplyState[address(cToken)].index == 0 && compSupplyState[address(cToken)].block == 0) 来初始化一个新市场indexblock。但是,由于我们正在重新启用一个现有市场的 COMP 代币分配(而不是一个新市场),因此第 1090 和 1091 行的语句不会被执行来初始化 indexblock(因为 compSupplyState[address(cToken)].block 不为零)。

总之,对于一个当前已禁用的市场,index 为零。但是 block 不为零。这意味着当我们通过调用 setCompSpeedCompSpeed[cToken] 设置为非零值来重新启用已禁用的市场时,index不会被重新初始化为 CompInitialIndex (1e36)(第 1090 和 1091 行未执行)。

漏洞 1 的影响

我们进一步深入研究负责分配 COMP 代币的 distributeSupplierComp 函数。

supplierIndexcompInitialIndex。然而,由于漏洞,supplyIndex 仍然为零,这将导致 Double memory deltaIndex = sub_(supplyIndex=0, supplierIndex=1e36) 发生下溢。

漏洞 2:由漏洞 1 的修复引入

为了修复这个漏洞,项目方更改了代码逻辑。具体来说,它在新市场初始化时立即将 index 初始化为 compInitialIndex

由于全局奖金指数(index)已初始化为 compInitialIndex,用户奖金指数也应初始化为该值。让我们看一下 distributeSupplierComp 函数。

即使 supplierIndex == 0,第 1234 行的 if 条件也无法满足,因为 supplyIndex 等于(而不是大于)compInitialIndex (1e36)。这导致 supplierIndex 没有正确初始化为 compInitialIndex(其值为 0)。那么 deltaIndex (supplyIndex - supplierIndex) 将等于 compInitialIndex,而不是零。如果用户的 cToken 余额不为零,supplierTokens 将会变成一个很大的值。

总之,如果用户碰巧在漏洞 1 修复之前执行了 mint 操作,那么他/她将拥有 cToken,并且 supplierIndex 将变为零(因为 COMP 代币已被分配)。然后,在漏洞 1 修复之后(引入了漏洞 2),当用户再次调用 mint 函数时,他/她就可以获得大量的 COMP 代币(1e36*ctoken.balanceOf(user))。

实际案例

我们在下文中展示了受影响的市场:

0xF5DCe57282A584D2746FaF1593d3121Fcac444dC: cSAI 0x12392F67bdf24faE0AF363c24aC620a2f67DAd86: cTUSD 0x95b4eF2869eBD94BEb4eEE400a99824BF5DC325b: cMKR 0x4B0181102A0112A2ef11AbEE5563bb4a3176c9d7: cSUSHI 0xe65cdB6479BaC1e22340E4E755fAE7E509EcD06c: cAAVE 0x80a2AE356fc9ef4305676f7a3E2Ed04e12C33946: cYFI

对于用户(0xa7b95d2a2d10028cc4450e453151181cbcac74fc),在此交易中获得了 4,466.542459954989867175 COMP 代币(0x6416ed016c39ffa23694a70d8a386c613f005be18aa0048ded8094f6165e7308)。

对该交易的进一步调试表明,由于漏洞 2,deltaIndex 为 1e36,并且用户当时恰好拥有 cToken。

漏洞 2 的修复

对漏洞 2 的修复很简单。它更改了 distributeSupplierComp 函数中的 if 条件。

教训

  • 这是一个由另一个漏洞的修复所引起的漏洞。如何彻底审查高调项目的代码更改仍然是一个悬而未决的问题。
  • DAO 可以消除中心化的风险。然而,它也使得安全事件的响应过程变得缓慢。
  • 高调的 DeFi 项目可以借鉴传统程序中的良好安全实践,例如部署一个高效的模糊测试系统,并进行持续的测试过程。

关于 BlockSec

BlockSec 是一家开创性的区块链安全公司,由一群享誉全球的安全专家于 2021 年创立。公司致力于为新兴的 Web3 世界增强安全性和可用性,以促进其大规模采用。为此,BlockSec 提供智能合约和 EVM 链安全审计服务,用于安全开发和主动阻止威胁的Phalcon平台,用于资金追踪和调查的MetaSleuth平台,以及供 Web3 构建者在加密世界高效冲浪的MetaSuites扩展。

截至目前,该公司已为 MetaMask、Uniswap Foundation、Compound、Forta 和 PancakeSwap 等 300 多家知名客户提供服务,并在两轮融资中获得了来自 Matrix Partners、Vitalbridge Capital 和 Fenbushi Capital 等杰出投资者的数千万美元投资。

官方网站:https://blocksec.com/

官方 Twitter 账号:https://twitter.com/BlockSecTeam

Sign up for the latest updates
Weekly Web3 Security Incident Roundup | Feb 9 – Feb 15, 2026

Weekly Web3 Security Incident Roundup | Feb 9 – Feb 15, 2026

During the week of February 9 to February 15, 2026, three blockchain security incidents were reported with total losses of ~$657K. All incidents occurred on the BNB Smart Chain and involved flawed business logic in DeFi token contracts. The primary causes included an unchecked balance withdrawal from an intermediary contract that allowed donation-based inflation of a liquidity addition targeted by a sandwich attack, a post-swap deflationary clawback that returned sold tokens to the caller while draining pool reserves to create a repeatable price-manipulation primitive, and a token transfer override that burned tokens directly from a Uniswap V2 pair's balance and force-synced reserves within the same transaction to artificially inflate the token price.

Top 10 "Awesome" Security Incidents in 2025

Top 10 "Awesome" Security Incidents in 2025

To help the community learn from what happened, BlockSec selected ten incidents that stood out most this year. These cases were chosen not only for the scale of loss, but also for the distinct techniques involved, the unexpected twists in execution, and the new or underexplored attack surfaces they revealed.

#10 Panoptic Incident: XOR Linearity Breaks the Position Fingerprint Scheme

#10 Panoptic Incident: XOR Linearity Breaks the Position Fingerprint Scheme

On August 29, 2025, Panoptic disclosed a Cantina bounty finding and confirmed that, with support from Cantina and Seal911, it executed a rescue operation on August 25 to secure roughly $400K in funds. The issue stemmed from a flaw in Panoptic’s position fingerprint calculation algorithm, which could have enabled incorrect position identification and downstream fund risk.