news 2026/9/10 7:33:11

Sonne Finance被攻击:Compound v2分叉中的未注册市场漏洞解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Sonne Finance被攻击:Compound v2分叉中的未注册市场漏洞解析

最近安全圈的朋友应该都被Sonne Finance被攻击的消息刷屏了。作为Compound v2分叉家族又一起典型的“影子市场”漏洞案例,这次攻击在Optimism上直接造成约2000万美元的损失,攻击手法非常典型,几乎可以说把分叉协议里“未注册市场”这个坑完美踩了一遍。我花了两天时间翻完了公开的审计报告、链上数据和攻击交易记录,决定把整个事件从代码底层到资金链路完整拆一遍,希望能给做DeFi开发、审计或者单纯对链上安全感兴趣的朋友一些参考。

这个案例的核心价值在于:它不是那种复杂到看不懂的数学漏洞,也不是需要深厚密码学背景才能理解的问题,而是一个纯粹的“架构级疏忽”——一个市场被部署出来,却没有被注册到协议的registry里,导致整个清算和流动性检查机制对它完全失明。这类问题在分叉项目中其实非常常见,只是严重程度不同而已。

1. Sonne Finance:一个标准的Compound v2分叉项目

1.1 借贷协议基础:Compound v2分叉的典型架构

要理解这个漏洞,得先弄清楚Sonne Finance的技术底座。Sonne是一个部署在Optimism和Base上的去中心化借贷协议,核心逻辑几乎是从Compound v2分叉来的。在Compound v2的架构中,系统有几个核心组件:

  • Comptroller(控制器):负责风险参数管理、市场注册、账户流动性检查。
  • CToken(借贷市场代币):每个市场对应一个cToken合约,比如cUSDC、cETH。
  • Price Oracle(价格预言机):用于获取底层资产的价格,通常是Chainlink聚合器或者自定义的桥接合约。

在Compound v2的正常运行逻辑里,每个新市场必须经过治理投票,然后由管理员调用Comptroller._supportMarket()将市场注册到控制器中。只有注册过的市场,才会被纳入账户流动性的计算范围。这个流程保证了:当用户借款时,系统能完整地计算其所有资产和负债,确保抵押率健康。

Sonne Finance基本沿用了这套架构,但它在Optimism上部署时,多了一个特殊的存在——SOMM市场。SOMM是Sonne自己的治理代币,项目方为了让SOMM有借贷场景,部署了一个SOMM的cToken市场。但问题就在这里:这个SOMM市场虽然被部署出来了,却始终没有被正式注册到Comptroller的市场列表中。

1.2 攻击事件概述:时间、损失、影响范围

2024年5月15日,攻击者通过一系列精心构造的交易,从Sonne Finance在Optimism上的协议中盗取了约2000万美元的加密资产。主要被抽走的是USDC等稳定币和WETH,攻击完成后协议立即暂停,但资金已经无法追回。根据链上数据回溯,攻击者的整个操作在高频交易下持续了不到二十分钟。

这次攻击的影响范围主要集中在Optimism部署。Base上的Sonne实例并未受影响,因为Base上虽然也有SOMM市场,但它被正确注册到了Comptroller中。这个细节很关键:同一个项目,同样的代码,只是一个注册遗漏的差别,结果天壤之别。

2. 漏洞根源:未注册市场为何会让协议失明

2.1 从代码层面看getAccountLiquidity的盲区

现在我们从代码层面理解这个漏洞的根源。Compound v2的账户流动性检查函数getAccountLiquidity的核心逻辑是遍历该账户涉及的所有市场资产,并计算总额:

function getAccountLiquidity(address account) public view returns (uint, uint, uint) { uint error; uint tokens; uint shortfall; uint liquidity; uint balance; uint borrowValue; uint collateralValue; // 遍历所有已注册市场 address[] memory assets = assetsIn[account]; for (uint i = 0; i < assets.length; i++) { address asset = assets[i]; (error, balance, borrowValue, collateralValue) = getAccountLiquidityInternal(asset, account); if (error != 0) return (error, 0, 0); tokens = tokens.add(balance); collateralValue = collateralValue.add(collateralValue); borrowValue = borrowValue.add(borrowValue); } liquidity = collateralValue.sub(borrowValue); shortfall = borrowValue.sub(collateralValue); return (error, liquidity, shortfall); }

问题一目了然:这个函数只会遍历assetsIn[account]数组中记录的市场,而assetsIn数组是在用户进入市场(enterMarkets)时,由Comptroller根据已注册市场列表来填充的。换句话说,如果SOMM市场压根就不在Comptroller的markets映射里,那么无论用户在这个市场里借了多少钱,只要没有主动调用enterMarkets把SOMM市场加进来,这笔债务在计算账户流动性时就是完全透明的。

当然有人会问:攻击者能不能主动调用enterMarkets把SOMM加进去?答案是可以,但调用后Comptroller会检查markets[sommCToken].isListed,由于SOMM市场未注册,isListed为false,调用会直接失败。所以攻击者根本没有办法“告诉”协议自己在这个市场里有债务。

2.2 为什么清算机制会彻底失效

清算机制是整个借贷协议的“安全网”。正常情况下,当用户抵押率跌破清算阈值时,任何第三方都可以调用liquidateBorrow来代为偿还部分债务,并拿走抵押品作为奖励。这个清算动作依赖的正是getAccountLiquidity来计算借款人的shortfall(缺口金额)。

当SOMM市场未注册时,攻击者的策略变得异常简单:在SOMM市场借入大额资产,这些债务完全不参与任何流动性计算,因此攻击者的账户永远不会有shortfall,清算人根本无法触发清算。这相当于攻击者在一个无人看管的金库里搬钱,而警报系统只盯着隔壁的金库。

这个问题的本质是:协议的“状态记录”与“实际存在”发生了脱节。SOMM市场作为一个合约真实存在、真实运行,用户确实可以往里存钱、借款,但协议的风险控制系统却对这个市场一无所知。这种“影子市场”带来的账实不符,是分叉协议中最隐蔽也最致命的问题之一。

3. 攻击全过程复盘:三个关键阶段

3.1 第一幕:在影子市场中悄悄积累债务

攻击的第一步是利用SOMM市场的存在,在不触发协议警报的情况下借出大量资产。根据链上分析,攻击者先通过一个无抵押的新地址,向SOMM市场存入了一小部分抵押品,然后开始借出SOMM代币。

具体来说,攻击者利用flash loan从Base上的Sonne实例或其它去中心化交易平台获取了大量SOMM代币,然后在Optimism的Sonne协议上向SOMM市场注入流动性。由于SOMM市场未注册,借款时的抵押率检查被完全绕过——borrowAllowed中的getHypotheticalAccountLiquidity检查虽然会被调用,但在遍历资产列表时根本找不到SOMM市场,所以它认为这笔借款操作不会产生任何风险。

这一步是整个攻击的关键跳板。如果没有这个未注册的SOMM市场,攻击者在普通市场里借款必然要满足抵押率要求,也就无法凭空创造出巨额债务。

3.2 第二幕:抵押品操纵与闪电贷的完美配合

在影子市场中积累了大量借款额度后,攻击者还需要把这些债务“变现”成真正的资产。这里分为两步:

第一步是借出资产。攻击者在SOMM市场中借出了大量SOMM代币,然后在去中心化交易所上将SOMM代币抛售,换取ETH。这个操作本身就会对SOMM代币的价格造成冲击,但由于SOMM在Sonne上的价格预言机来自一个比较薄弱的流动性池,攻击者甚至可以利用这种价格冲击,进一步压低估资产价格。

第二步是进入正常市场。攻击者在USDC等主流市场中存入一笔抵押品(可能是从其它渠道借来的),然后借出USDC和WETH。由于SOMM市场中的巨额债务没有计入账户总负债,攻击者在正常市场中的抵押率看起来非常健康,协议允许他借出大量资产。

这一阶段用到的一个经典技巧是价格预言机操纵。Sonne的SOMM价格来自Uniswap V3的流动性池,攻击者通过闪电贷向这个池子注入大量资金,从而改变SOMM的瞬时价格。后续的借款和清算计算都会使用这个被操纵的价格,进一步放大了攻击者的盈利空间。

3.3 第三幕:资金提取与链上追踪

攻击者在同一笔交易中完成了从借款到资产提取的全流程。整个攻击交易非常紧凑,大量操作依赖flash loan,在同一个区块内完成。链上数据显示,攻击者最终提取了约6500枚ETH和大量USDC,总价值约2000万美元。

攻击完成后,Sonne Finance迅速暂停了协议,并对攻击地址进行了标记。部分被盗资金被转移到多个地址进行混币操作,但大部分资金在数个小时内就被转移到了多个交易所。目前该项目已经提出了对攻击者的赏金方案,但资金追回的可能性不大。

这次的攻击手法本质上不算复杂,为什么能成功?正是因为漏洞位于系统架构的“盲区”,而攻击者精准地找到了这个盲区并加以利用。

4. 修复方案与Compound v2分叉的通用风险

4.1 官方修复:暂停、注册、隔离

Sonne Finance在攻击发生后立即暂停了所有市场操作,这是止损最快的手段。随后团队紧急提交修复方案,核心逻辑是在Comptroller中将SOMM市场正式注册,并设置合理的风险参数。

但这里暴露了另一个问题:虽然注册市场看起来简单,但一旦SOMM市场价格被严重操纵,注册后反而可能让攻击者通过另一种方式获利。因此修复方案还必须包含价格源加固、抵押率重新设定、债务上限限制等配套措施。

从公开的修复代码来看,Sonne团队直接将SOMM市场从市场列表中隔离,并设置isListed = false来彻底禁用该市场。听起来奇怪,但这确实是实用主义做法:既然这个市场曾经造成问题,且SOMM代币的流动性不足,干脆禁用整个市场,避免类似问题再次发生。

4.2 分叉协议审计中的常见盲区

Sonne Finance这个案例给所有Compound v2分叉项目敲响了警钟。在审计类似分叉协议时,有几个盲区特别容易被忽视:

  • 治理操作的完整性:很多时候市场部署和注册分为两步操作,如果两步之间存在时间窗口,或者最终只有一步被提交上链,市场就会变成“影子市场”。
  • 新模块与旧逻辑的兼容性:Sonne Finance可能在原版Compound v2基础上新增了SOMM市场支持模块,但这个模块没有完全遵守原有“先注册后交易”的规范。
  • 多链部署一致性:Sonne在Optimism和Base上有两套部署,但因为部署时间和流程不同,一条链上注册了SOMM市场,另一条链上却漏掉了,导致同一项目在不同链上的表现完全不同。

在审计实操中,除了检查代码逻辑,还需要用脚本遍历所有CToken合约,比对实际部署状态与注册状态是否有差异。这个动作听起来很基础,但很多审计机构在实际操作中并不会做如此彻底的核查,特别是当项目方只把核心市场代码交给审计而忽略了已经部署的附加市场时。

5. DeFi安全从业者的反思与实操建议

5.1 排查清单:给项目方和安全团队的五条建议

经过这次事件,我整理了适用于所有分叉借贷协议的风险排查清单,强烈建议项目方和审计团队按这几条做一轮自查:

  • 核查已部署市场与注册市场的一致性:可以用脚本遍历所有cToken合约地址,逐一确认它们是否在Comptroller的markets映射中isListed为true。这一步必须作为上线前强制检查项。
  • 清理长期未使用的市场:如果一个市场已经部署但长期没有交易,或者从未正式注册,应该直接通过治理投票将其禁用,而不是让它在链上“裸奔”。
  • 监控新增市场交易行为异常:建立链上监控系统,对未注册但存在资金流动的合约地址进行告警,尤其是在借贷协议的上下文环境中。
  • 严格的多链部署流程管理:每次在多链部署时,使用相同的部署脚本和验证工具,确保各链状态完全一致,并做一次跨链对比校验。
  • 对治理操作进行时间锁保护:任何涉及新增市场、修改风险参数的操作都应该通过时间锁执行,给予社区和安全团队足够的观察窗口。

5.2 为什么这个案例值得反复研究

我刚接触这个案例时,第一反应是“这不就是个简单的注册遗漏吗”。但深入拆解后会发现,这次攻击其实展现了DeFi漏洞的三个层次:第一层是代码层面的遗漏,第二层是架构层面的错配,第三层是业务流程层面的风险识别不足。

对于安全研究人员来说,这个案例是学习分叉代码审计绝佳的教材。它不涉及复杂的数学难题,纯粹靠对系统结构的深入理解就能发现,这也说明DeFi安全的核心不只是代码审计,更是架构审计和治理审计。很多漏洞不是写出来的,而是“搭”出来的。

在我自己的审计工作中,每次看到分叉项目时,现在都会多问一句:这个项目有没有在标准市场之外部署过任何非标准市场?有没有通过治理投票创建但最终没有完成注册的资产?这些问题看起来简单,但确实是许多重大漏洞的根源。希望这次Sonne Finance的教训能给整个行业提个醒。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 7:33:09

SpringBoot注解实战:核心原理、常见坑与最佳实践

写SpringBoot项目这事&#xff0c;干了几年之后回头再看&#xff0c;注解就是整个框架的骨骼。很多人一开始觉得注解这东西玄乎&#xff0c;写几个就能跑&#xff0c;但报错的时候完全不知道去哪找原因。实际上注解的本质没那么神秘&#xff0c;它就是在编译或运行阶段给程序打…

作者头像 李华
网站建设 2026/9/10 7:31:01

RPCS3 自动更新完全指南:为什么它不自动更新、卡住了怎么办

RPCS3 自动更新完全指南&#xff1a;为什么它不自动更新、卡住了怎么办 【免费下载链接】rpcs3 PlayStation 3 emulator and debugger 项目地址: https://gitcode.com/GitHub_Trending/rp/rpcs3 在 Linux 上打开 RPCS3&#xff0c;你会发现它长时间运行也不弹任何更新提…

作者头像 李华
网站建设 2026/9/10 7:30:56

AI文本如何改出人味?一份实战向的Humanizer改写指南

1. 为什么“一眼认出机器写的字”正在变成一门手艺 前阵子团队内部做了一次盲测&#xff0c;让几位编辑从十篇匿名文章里挑出“真人写的”&#xff0c;结果很有意思&#xff1a;大家公认最像真人的两篇&#xff0c;恰好是所有人花时间最少的&#xff1b;而被一致判定为“AI写的…

作者头像 李华
网站建设 2026/9/10 7:30:55

Humanizer是什么:AI文字去机器味的人文化改写核心技巧与实践

1. Humanizer是什么&#xff1a;AI文字"去机器味"的核心逻辑这两年AI写作工具铺天盖地&#xff0c;ChatGPT、Claude、Gemini&#xff0c;随便一个都能在几秒内吐出一篇结构工整、观点齐全的文章。但只要你读得稍微多一点&#xff0c;就会发现这些文字有一个共同特点&…

作者头像 李华