本篇文章为《臭名昭著的错误月刊》第2期,探讨了Web3领域内最近的漏洞与安全事件,详细分析了针对叉沙攻击、zkLend黑客事件的教训以及多个未验证合约的漏洞。通过审计发现的问题,比如属性宏遗漏后续属性和UniswapHook的Hook状态覆盖等,强调了安全性在合约开发中的重要性。
合著者:Ionut-Viorel Gingu 和 Victor Xie
欢迎来到《臭名昭著的漏洞月刊#2》——这是对最近Web3漏洞和安全事件的精心编排的洞察汇编。当我们的安全研究人员不进行审计时,他们会投入时间来跟上最新的安全动态,分析审计报告,并剖析链上事件。我们相信这些知识对更广泛的安全社区是无价的,为研究人员提供了一个资源,以提高他们的技能,并帮助新人导航Web3安全的世界。让我们一起探索这一批漏洞吧!
事件分析
用转账费用防御三明治攻击?这次不行
在2月7日,0x2d70d62
合约遭到攻击 [1] [2],原因是其 depositBNB
函数在用 WBNB
购买 ADAcash
代币时没有进行滑点保护。
然而,攻击并不简单。
ADAcash
是一种转账收费代币,每次转账都向代币合约收取15%的费用。因此,如果攻击者简单地用闪电贷进行三明治攻击,综合费用(买入时的15%,卖出时的15%)就能很容易耗尽任何利润。
那么,
ADAcash
收集到的转账费用都发生了什么?合约会进行交换,但不幸的是,没有任何滑点保护。根据代码,当 from
地址不是 AMM(如卖出 ADAcash
时)且收集到足够的费用时,收集的大部分费用会被交换成 ADA
,而约 13% 则转化为 WBNB
以提升 ADAcash/WBNB
池的流动性。
这就是聪明之处:攻击者可以通过对
ADAcash
代币合约本身发动第二次三明治攻击来重新获得这些费用。事情的经过是这样的:
WBNB
的闪电贷。WBNB
购买 ADAcash
,抬高其价格(15% 的 ADAcash
成为费用)。WBNB
购买 ADA
,推动 ADA
的价格上升。0x2d70d62
的 depositBNB
函数。这是第一次夹击交换,进一步抬高 ADAcash
的价格。ADAcash
以从第一次夹击交换中获利。请注意,卖出 ADAcash
也会触发15%的费用。为了最小化费用,攻击者将卖出分为多个交换,因为每次卖出都会利用所有累积的费用进行交换。这些卖出构成了第二次夹击交换,进一步提高了 ADA
的价格。ADA
,以锁定第二个交换的利润。WBNB
的闪电贷并确保利润。故事的教训是什么?确保始终考虑并设置合理的滑点保护值。否则,交换可能以复杂、意想不到的方式被夹击。
从zkLend黑客事件中汲取的教训
在2月12日,zkLend
借贷协议由于 withdraw
函数中的向下取整错误遭到黑客攻击。SlowMist 发布了 详细的分析 这一漏洞的爆发。事情的经过如下:
lending_accumulator
。lending_accumulator
:利用闪电贷,他们偿还了额外费用以抬高 lending_accumulator
。lending_accumulator
和由于向下取整所造成的对应精度损失,每次提取操作消耗的份额与存入操作相同。这种存入和提取的资产之间的不一致为攻击者带来了利润。这种 exploitation 的模式并非偶然,并且在 zkLend
中使用的方法类似于其他借贷协议的攻击:
审计人员的收获
向下取整错误只会在能够被放大的情况下排干协议。没有膨胀的话,你只能每次提取 1 wei——远低于交易费用。
为什么是借贷协议?空市场或保险库是膨胀取整错误的首要目标。这些攻击就像是 保险库膨胀漏洞 的增强版。在大多数设置中,一个空的保险库只会影响第一个存款人。但在借贷协议中,一个空市场允许你从其他市场借贷或提取抵押品,从而排干整个系统。
发现这种漏洞的一个关键问题是“攻击者可以在空市场中膨胀哪些变量?”这些不仅仅是取整错误——它们是 取整 + 膨胀 攻击。上述两个漏洞甚至没有使用错误的取整方向:它们都依赖于在空市场中膨胀一个会计变量。
对于 zkLend
,审计人员可以标记 lending_accumulator
作为可膨胀的。与有利于用户的取整相结合,这就成为了排干的食谱。检查取整方向会显得尤为重要。
这个问题还会挖掘出非取整错误。请考虑 Silo Finance漏洞——它利用捐赠在空保险库中膨胀一个变量,无需任何取整技巧。在 Raft Finance漏洞中,一个有利于用户的取整错误被报告为低严重性问题,并在修复审查中没有得到解决。对一个错误如何被放大的进一步分析可能揭示了关键,并避免这次黑客事件的发生。
未经验证合约中的漏洞
本月,由于薄弱的访问控制、不足的输入验证或未初始化的状态,多份未经验证的智能合约遭到攻击。以下是事件的总结。
合约 0xeffca1:通过未检查的回调调用损失20 ETH [1] [2]
下图高亮显示了一个关键问题:不足的访问控制。受害者是一个 MEV 机器人,未能验证其 uniswapV2
函数的调用者。然而,对反编译代码和 msg.data
的深入分析揭示了更多细节:
uniswapV2
函数——该调用实际上触及了机器人的回调逻辑。delegatecall
的目标地址 (0xcc85e
)、参数(例如,对不存在的 0x24ec8
的 approve
调用)以及后续的 WETH
转移都由 msg.data
控制。delegatecall
的目标在 explo时之前几小时被 列入白名单——这是一个特权行为。这引发了一个问题:该攻击是自动化的吗?一个熟练的攻击者可以绕过 0x24ec8
,直接批准 WETH
,并通过 swapIn
回调提取它。考虑到反编译代码的复杂性,黑客可能已经变异历史 msg.data
,注入自己的地址、资产细节和一个有利可图的返回值。这仍然是推测,但可以信服。
合约 0x378c6:通过假池交换盗取 151 BNB [1] [2]
这一漏洞相对简单。如图所示,0xb9d384fa
函数缺乏访问控制,使得在未经验证的 Uniswap V3 池上进行代币交换成为可能。攻击者用最少的 WBNB 和大量假代币创建了一个池,然后触发 0xb9d384fa
来交换 WBNB 与毫无价值的代币。随后,他们撤回流动性,从而在 WBNB 中获利。一个关键细节是:滑点保护参数 sqrtPriceLimitX96
(不受攻击者控制)被设置为 MIN_SQRT_RATIO,确保以低 WBNB 价格成功交换。
合约 0xd4f1a:由于未初始化状态损失23 BNB [1] [2]
这一案例同样明了。受害者是一个可初始化的合约,部署时并没有调用 _disableInitializers
或 initialize
部署,导致其未初始化。在两天累积费用后,攻击者调用 initialize
来声称所有权,并调用 withdrawFees
来 siphon资金。有趣的是,在攻击后,费用仍持续流入,而攻击者则将这些费用重定向 到 Tornado.cash。
这个漏洞是在我们的 Rust 审计中发现的。它突显了过程宏在 Rust 中是如何工作的。过程宏是一个 编译时代码生成工具:当编译器看到 function doAction
被标记为 macro
时,它会查找 macro
的定义,并根据该定义增加、删除或替换 doAction
函数中的代码。
这为开发像 Solidity 的修饰符这样的功能提供了强大的工具。我们可以创建一个 when_not_paused
过程宏,当它写在一个函数上方时,将复制函数代码并在开头添加额外的逻辑。如果 paused
变量被设置为 true
,则额外逻辑将会失败。
同样,客户端打算创建一个 when_not_paused
过程宏。该宏将函数代码(连同所有注解)解析为 token,并可以通过以下方式访问:
fn_vis
(函数可见性:例如 pub
)fn_sig
(函数签名:例如 fun doAction(number: u32) -> u32
)fn_block
(函数主体:例如 return number;
)在编译时,编译器复制 函数可见性、函数签名和函数主体,并在函数开头添加 openzeppelin_pausable::when_paused(#env_arg);
调用,如下所示:
有什么东西缺失了。但是什么呢?其余的注解!如果我们的函数还被其他过程宏注解(想想
only_owner
),则 when_not_paused
宏将改变代码,从而使得 only_owner
在此过程中丢失。此问题的修复将是 也复制其他注解,因此结果如下:
在我们对 Uniswap Hooks 库的审计中,我们发现了 BaseCustomAccounting hook
的一个关键漏洞。这个Hook旨在完全控制其关联池的流动性,确保所有流动性修改都通过它。因此,它必须永久链接到单一池。否则,流动性可能会变得不可访问。
然而,由于池-Hook意识的不对称,任何有效的Hook地址都可以附加到任何池,除非被Hook自身明确拒绝。BaseCustomAccounting
Hook缺乏防止重新初始化的保护,允许攻击者将其从受害者的池重定向到恶意创建的 Uniswap 池。这实际上锁定了受害者池的流动性,阻止了对资金的访问。
修复确保了,一旦定向到池,一个继承自 BaseCustomAccounting
的Hook将无法被重新初始化并指向另一个池。
重要的是强调,这些内容的意图不是批评或指责受影响的项目,而是提供客观概述,以作为社区学习和更好保护未来项目的教育材料。
- 原文链接: blog.openzeppelin.com/th...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!